1. 提前瞭解
Netty 的簡單實例
https://blog.csdn.net/YKenan/article/details/106362104
2. 主方法類
package com.springCloud.netty.WebSocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class WebSocketServer {
public static void main(String[] args) {
// 創建主從線程
EventLoopGroup mainGroup = new NioEventLoopGroup();
EventLoopGroup subGroup = new NioEventLoopGroup();
try {
// 創建服務器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(mainGroup, subGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WebSocketServerInitializer());
ChannelFuture sync = serverBootstrap.bind(8009).sync();
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mainGroup.shutdownGracefully();
subGroup.shutdownGracefully();
}
}
}
3. 通道初始化器
package com.springCloud.netty.WebSocket;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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 WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 獲取管道 (pipeline)
ChannelPipeline pipeline = socketChannel.pipeline();
// Websocket 基於 http 協議, 所需要的 http 編碼器
pipeline.addLast(new HttpServerCodec());
// 在 http 上有一些數據流產生, 有大有小, 我們對其處理, 既然如此, 我們需要使用 netty 對下數據流讀寫提供支持, 這兩個類叫:
pipeline.addLast(new ChunkedWriteHandler());
// 對 httpMessage 進行聚合處理, 聚合成 request和 response
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
// 本 handler 會幫你處理一些繁重複雜的事請, 會幫你處理握手動作: handshaking (close, ping, pong) ping + pong = 心跳, 對於 websocket 來講, 都是以 frame 進行傳輸的, 不同的數據類型對應的 frame 也不同.
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// 自定義的 handler
pipeline.addLast(new ChatHandler());
}
}
4. 消息處理器
package com.springCloud.netty.WebSocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.time.LocalDateTime;
/**
* 用於處理消息的 handler
* 由於它的傳輸數據的載體時 frame, 這個 frame 在 netty 中, 是用於 websocket 專門處理文本對象的, frame 是消息的載體, 此類叫做: TextWebSocketFrame
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用於記錄和管理所有客戶端的 Channel
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
// 獲取客戶端所傳輸的信息
String text = textWebSocketFrame.text();
System.out.println("接收到的數據: " + text);
// 將數據刷新到客戶端上
clients.writeAndFlush(
new TextWebSocketFrame(
"[服務器在: "
+ LocalDateTime.now()
+ "接收到消息, 消息內容爲: "
+ text
+ "]"
)
);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
super.handlerRemoved(ctx);
clients.remove(ctx.channel()); // 這句話沒有必要寫
System.out.println("客戶端斷開, Channel 對應的長 ID 爲: " + ctx.channel().id().asLongText());
System.out.println("客戶端斷開, Channel 對應的短 ID 爲: " + ctx.channel().id().asShortText());
}
}
5. 前端
<template>
<div id="style">
<div class="message">
<el-input
placeholder="請輸入內容"
v-model="sendMessage"
clearable>
</el-input>
<br />
<br />
<el-button type="primary" @click="sendWebSocket(sendMessage)">發送</el-button>
<br />
<br />
<span>{{receiveMessage}}</span>
</div>
</div>
</template>
<script>
import ElInput from '../../../node_modules/element-ui/packages/input/src/input.vue'
import ElButton from '../../../node_modules/element-ui/packages/button/src/button.vue'
export default {
components: {
ElButton,
ElInput
},
props: {},
data () {
return {
receiveMessage: '',
sendMessage: '',
webSocket: null
}
},
mounted () {},
watch: {},
created () {
this.initWebSocket()
},
destroyed () {
// 離開路由之後斷開 webSocket 連接
this.webSocket.close()
},
methods: {
// 初始化 webSocket
initWebSocket () {
// 創建 WebSocket 對象
this.webSocket = new WebSocket('ws://127.0.0.1:8009/ws')
this.webSocket.onopen = this.onOpenWebSocket
this.webSocket.onmessage = this.onMessageWebSocket
this.webSocket.onerror = this.onErrorWebSocket
this.webSocket.onclose = this.closeWebSocket
},
// 連接建立之後執行 send 方法發送數據
onOpenWebSocket () {
console.log('鏈接建立成功!')
this.sendWebSocket('鏈接建立成功')
},
// 連接建立失敗重連
onErrorWebSocket () {
this.initWebSocket()
},
// 數據接收
onMessageWebSocket (e) {
this.receiveMessage = e.data
},
// 數據發送
sendWebSocket (Data) {
this.webSocket.send(Data)
},
// 關閉
closeWebSocket (e) {
console.log('斷開連接', e)
}
}
}
</script>
<style scoped>
#style {
width: 700px;
margin: auto;
}
</style>
6. 瀏覽效果
瀏覽器
控制檯