原文:https://huan1993.iteye.com/blog/2433552
最近在學習netty相關的知識,看到netty可以實現 websoket,因此記錄一下在netty中實現websocket的步驟,主要實現傳遞文本消息和傳遞二進制消息,傳遞二進制消息由於需要傳遞額外信息,因此使用自定義消息協議。
需求:
1、使用 netty 實現 websocket 服務器
2、實現 文本信息 的傳遞
3、實現 二進制 信息的傳遞,如果是圖片則傳遞到后台后在前台直接顯示,非圖片提示。(此處的圖片和非圖片是前端傳遞到后台的二進制數據然后后端在原封不動的直接返回到前台)
4、只需要考慮 websocket 協議,不用處理http請求
實現細節:
1、netty中對websocket增強的處理器
WebSocketServerProtocolHandler
>> 此處理器可以處理了 webSocket 協議的握手請求處理,以及 Close、Ping、Pong控制幀的處理。對於文本和二進制的數據幀需要我們自己處理。
>> 如果我們需要攔截 webSocket 協議握手完成后的處理,可以實現ChannelInboundHandler#userEventTriggered方法,並判斷是否是 HandshakeComplete 事件。
>> 參數:websocketPath 表示 webSocket 的路徑
>> 參數:maxFrameSize 表示最大的幀,如果上傳大文件時需要將此值調大
2、文本消息的處理
客戶端: 直接發送一個字符串即可
服務端: 服務端給客戶端響應文本數據,需要返回 TextWebSocketFrame 對象,否則客戶端接收不到。
3、二進制消息的處理
客戶端:向后台傳遞一個 blob 對象即可,如果我們需要傳遞額外的信息,那么可以在 blob 對象中進行添加,此例中自定義前4個字節表示數據的類型。
服務端:處理 BinaryWebSocketFrame 幀,並獲取前4個字節,判斷是否是圖片,然后返回 BinaryWebSocketFrame對象給前台。
4、針對二進制消息的自定義協議如下:(此處實現比較簡單)
前四個字節表示文件類型,后面的字節表示具體的數據。
在java中一個int是4個字節,在js中使用Int32表示
此協議主要是判斷前端是否傳遞的是 圖片,如果是圖片就直接傳遞到后台,然后后台在返回二進制數據到前台直接顯示這個圖片。非圖片不用處理。
5、js中處理二進制數據
見 webSocket.html 文件中的處理。
實現步驟:
1、主要的依賴
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-all</artifactId>
- <version>4.1.31.Final</version>
- </dependency>
2、webSocket服務端編寫
- @Slf4j
- public class WebSocketServer {
- public static void main(String[] args) throws InterruptedException {
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap bootstrap = new ServerBootstrap();
- bootstrap.group(bossGroup, workGroup)
- .option(ChannelOption.SO_BACKLOG, 128)
- .childOption(ChannelOption.TCP_NODELAY, true)
- .childOption(ChannelOption.SO_KEEPALIVE, true)
- .handler(new LoggingHandler(LogLevel.TRACE))
- .channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline()
- .addLast(new LoggingHandler(LogLevel.TRACE))
- // HttpRequestDecoder和HttpResponseEncoder的一個組合,針對http協議進行編解碼
- .addLast(new HttpServerCodec())
- // 分塊向客戶端寫數據,防止發送大文件時導致內存溢出, channel.write(new ChunkedFile(new File("bigFile.mkv")))
- .addLast(new ChunkedWriteHandler())
- // 將HttpMessage和HttpContents聚合到一個完成的 FullHttpRequest或FullHttpResponse中,具體是FullHttpRequest對象還是FullHttpResponse對象取決於是請求還是響應
- // 需要放到HttpServerCodec這個處理器后面
- .addLast(new HttpObjectAggregator(10240))
- // webSocket 數據壓縮擴展,當添加這個的時候WebSocketServerProtocolHandler的第三個參數需要設置成true
- .addLast(new WebSocketServerCompressionHandler())
- // 服務器端向外暴露的 web socket 端點,當客戶端傳遞比較大的對象時,maxFrameSize參數的值需要調大
- .addLast(new WebSocketServerProtocolHandler("/chat", null, true, 10485760))
- // 自定義處理器 - 處理 web socket 文本消息
- .addLast(new TextWebSocketHandler())
- // 自定義處理器 - 處理 web socket 二進制消息
- .addLast(new BinaryWebSocketFrameHandler());
- }
- });
- ChannelFuture channelFuture = bootstrap.bind(9898).sync();
- log.info("webSocket server listen on port : [{}]", 9898);
- channelFuture.channel().closeFuture().sync();
- } finally {
- bossGroup.shutdownGracefully();
- workGroup.shutdownGracefully();
- }
- }
- }
注意:
1、看一下上方依次引入了哪些處理器
2、對於 webSocket 的握手、Close、Ping、Pong等的處理,由 WebSocketServerProtocolHandler 已經處理了,我們自己只需要處理 Text和Binary等數據幀的處理。
3、對於傳遞比較大的文件,需要修改 maxFrameSize 參數。