網站首頁 編程語言 正文
一、微信小程序實現WebSocket客戶端程序
1. 界面實現
<input name="url" value="{{url}}" bindinput ="urlInput"/>
<button size='mini' type="warn">斷開連接</button>
<button size='mini' type="primary" bindtap="connectSocket">開啟連接</button>
<textarea placeholder="輸入發送內容" bindinput ="msgInput"></textarea>
<button size='mini' type="primary" bindtap="sendMsg">發送</button>
<view wx:for="{{msgs}}">{{index}}: {{item}}</view>
界面效果:
2. WXS部分
Page({
data: {
url: 'ws://localhost:8888/ws',
msgs: [],
msg: '',
}
// 連接WebSocket服務
connectSocket() {
let _this = this;
// 連接websocket服務
let task = wx.connectSocket({
url: _this.data.url
});
// 監聽websocket消息,并將接收到的消息添加到消息數組msgs中
task.onMessage(function(res) {
_this.setData({
msgs: [..._this.data.msgs, "接收到消息 -> " + res.data]
});
});
// 保存websocket實例
_this.setData({
socketTask: task,
msgs: [..._this.data.msgs,"連接成功!"]
});
},
// 獲取輸入內容,并臨時保存在msg中
msgInput(e) {
this.setData({
msg: e.detail.value
});
},
// 發送消息
sendMsg() {
// 1.獲取輸入內容
let msg = this.data.msg;
// 2.發送消息到WebSocket服務端
this.data.socketTask.send({
data: msg
});
}
})
二、Netty實現WebSocket服務端程序
1. 新建一個Maven工程,并引入Netty依賴
- 項目目錄結構:
- 引入Netty依賴:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.netty</groupId>
<artifactId>NettyWebSocket</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 自定義處理器
- 定義一個專門處理Http協議的處理器,當瀏覽器第一次連接時候會讀取首頁的html文件,并將html文件內容返回給瀏覽器展示。
package io.netty.websocket;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.net.URL;
// 處理Http協議的Handler,該Handler只會在第一次客戶端連接時候有用。
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final String wsUri;
private static final File INDEX;
static {
URL location = HttpRequestHandler.class.getProtectionDomain()
.getCodeSource().getLocation();
try {
String path = location.toURI() + "index.html";
path = !path.contains("file:") ? path : path.substring(5);
INDEX = new File(path);
} catch (URISyntaxException e) {
throw new IllegalStateException("Unable to locate index.html", e);
}
}
public HttpRequestHandler(String wsUri) {
this.wsUri = wsUri;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
// 如果被請求的 URL 以/ws 結尾,那么我們將會把該協議升級為 WebSocket。
if (wsUri.equalsIgnoreCase(request.getUri())) {
// 將請求傳遞給下一個ChannelHandler,即WebSocketServerProtocolHandler處理
// request.retain()會增加引用計數器,以防止資源被釋放
ctx.fireChannelRead(request.retain());
return;
}
handleHttpRequest(ctx, request);
}
/**
* 該方法讀取首頁html文件內容,然后將內容返回給客戶端展示
* @param ctx
* @param request
* @throws Exception
*/
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
// HTTP1.1協議允許客戶端先判定服務器是否愿意接受客戶端發來的消息主體,以減少由于服務器拒絕請求所帶來的額外資源開銷
if (HttpHeaders.is100ContinueExpected(request)) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
ctx.writeAndFlush(response);
}
// 從resources目錄讀取index.html文件
RandomAccessFile file = new RandomAccessFile(INDEX, "r");
// 準備響應頭信息
HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
boolean keepAlive = HttpHeaders.isKeepAlive(request);
if (keepAlive) {
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
ctx.write(response);
// 輸出html文件內容
ctx.write(new ChunkedNioFile(file.getChannel()));
// 最后發送一個LastHttpContent來標記響應的結束
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
// 如果不是長鏈接,則在寫操作完成后關閉Channel
if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 定義一個專門處理WebSocket協議的處理器。
package io.netty.websocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
// 處理WebSocket協議的Handler
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private final ChannelGroup channelGroup;
public TextWebSocketFrameHandler(ChannelGroup channelGroup) {
this.channelGroup = channelGroup;
}
// 用戶事件監聽,每次客戶端連接時候自動觸發
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String content = "Client " + ctx.channel().remoteAddress().toString().substring(1) + " joined";
System.out.println(content);
// 如果是握手完成事件,則從Pipeline中刪除HttpRequestHandler,并將當前channel添加到ChannelGroup中
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
// 從Pipeline中刪除HttpRequestHandler
ctx.pipeline().remove(HttpRequestHandler.class);
// 通知所有已連接的WebSocket客戶端,新的客戶端已經連接上了
TextWebSocketFrame msg = new TextWebSocketFrame(content);
channelGroup.writeAndFlush(msg);
// 將WebSocket Channel添加到ChannelGroup中,以便可以它接收所有消息
channelGroup.add(ctx.channel());
} else {
super.userEventTriggered(ctx, evt);
}
}
// 每次客戶端發送消息時執行
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame msg) throws Exception {
System.out.println("讀取到的消息:" + msg.retain());
// 將讀取到的消息寫到ChannelGroup中所有已經連接的客戶端
channelGroup.writeAndFlush(msg.retain());
}
}
上面userEventTriggered
方法監聽用戶事件。當有客戶端連接時候,會自動執行該方法。而channelRead0
方法負責讀取客戶端發送過來的消息,然后通過channelGroup
將消息輸出到所有已連接的客戶端。
3. 定義初始化器
定義一個ChannelInitializer的子類,其主要目的是在某個 Channel 注冊到 EventLoop 后,對這個 Channel 執行一些初始化操作。
package io.netty.websocket;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class ChatServerInitializer extends ChannelInitializer<Channel> {
private final ChannelGroup channelGroup;
public ChatServerInitializer(ChannelGroup channelGroup) {
this.channelGroup = channelGroup;
}
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// 安裝編解碼器,以實現對HttpRequest、 HttpContent、LastHttp-Content與字節之間的編解碼
pipeline.addLast(new HttpServerCodec());
// 專門處理寫文件的Handler
pipeline.addLast(new ChunkedWriteHandler());
// Http聚合器,可以讓pipeline中下一個Channel收到完整的HTTP信息
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
// 處理Http協議的ChannelHandler,只會在客戶端第一次連接時候有用
pipeline.addLast(new HttpRequestHandler("/ws"));
// 升級Websocket后,使用該 ChannelHandler 處理Websocket請求
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// 安裝專門處理 Websocket TextWebSocketFrame 幀的處理器
pipeline.addLast(new TextWebSocketFrameHandler(channelGroup));
}
}
4. 創建啟動類
package io.netty.websocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.ImmediateEventExecutor;
import java.net.InetSocketAddress;
public class ChatServer {
public void start() {
ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChatServerInitializer(channelGroup));
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8888)).syncUninterruptibly();
System.out.println("Starting ChatServer on port 8888 ...");
future.channel().closeFuture().syncUninterruptibly();
} finally {
channelGroup.close();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ChatServer().start();
}
}
5. 編寫一個html文件
該html文件提供網頁版的WebSocket客戶端頁面。在src/main/resources
目錄下新建一個html文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
<form οnsubmit="return false;">
<h3>WebSocket 聊天室:</h3>
<textarea id="responseText" style="width: 500px; height: 300px;"></textarea><br/>
<input type="text" name="message" style="width: 300px" value="Hello Netty"/>
<input type="button" value="發送消息" onclick="send(this.form.message.value)"/>
<input type="button" value="清空聊天記錄" onclick="clearScreen()"/>
</form>
<script type="text/javascript">
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:8888/ws");
// 注意:使用tls協議通信時候,協議名為wss
// socket = new WebSocket("wss://localhost:8443/ws");
socket.onopen = function(event) {
var ta = document.getElementById('responseText');
ta.value = "連接開啟!";
};
socket.onclose = function(event) {
var ta = document.getElementById('responseText');
ta.value = ta.value + '\n' + "連接被關閉!";
};
socket.onmessage = function(event) {
var ta = document.getElementById('responseText');
ta.value = ta.value + '\n' + event.data;
};
} else {
alert("你的瀏覽器不支持 WebSocket!");
}
function send(message) {
if (!window.WebSocket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert("連接沒有開啟.");
}
}
function clearScreen() {
document.getElementById('responseText').value = "";
}
</script>
</body>
</html>
界面效果:
最終效果:
原文鏈接:https://blog.csdn.net/zhongliwen1981/article/details/127247243
相關推薦
- 2022-02-11 SQL server 數據庫導入(附加)和分離 && 數據庫分離之后位置 &
- 2022-12-10 Python對XML文件實現增刪改查操作_python
- 2023-08-16 使用el-row和el-col實現快速布局
- 2022-04-20 Python基礎筆記之struct和格式化字符_python
- 2022-06-02 Android?Spinner和GridView組件的使用示例_Android
- 2023-07-24 uniapp開發小程序端原生導航欄
- 2022-10-30 系統應用根據Uri授予權限方法詳解_Android
- 2022-02-20 uni-app checkbox全選功能
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支