最近在学习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 对象中进行添加。
服务端:处理 BinaryWebSocketFrame 帧,然后返回 BinaryWebSocketFrame对象给前台。
4、针对二进制消息的自定义协议如下:(代码中已删除)
前四个字节表示文件类型,后面的字节表示具体的数据。
在java中一个int是4个字节,在js中使用Int32表示
此协议主要是判断前端是否传递的是 图片,如果是图片就直接传递到后台,然后后台在返回二进制数据到前台直接显示这个图片。非图片不用处理。
5、js中处理二进制数据
见 webSocket.html 文件中的处理。
实现步骤:
1、主要的依赖
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.38.Final</version> </dependency>
2、webSocket服务端编写
package com.huan.netty.websocket; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * netty 整合 websocket * * @author huan.fu * @date 2018/11/7 - 17:25 */ public class WebSocketServer { private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); 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()) // 聚合 websocket 的数据帧,因为客户端可能分段向服务器端发送数据 // https://github.com/netty/netty/issues/1112 https://github.com/netty/netty/pull/1207 .addLast(new WebSocketFrameAggregator(10 * 1024 * 1024)) // 服务器端向外暴露的 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 参数。
3、自定义处理器握手后和文本消息
package com.huan.netty.websocket; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.time.LocalTime; import java.time.format.DateTimeFormatter; /** * 处理 web socket 文本消息 * * @author huan.fu * @date 2018/11/7 - 17:37 */ public class TextWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { private static final Logger log = LoggerFactory.getLogger(TextWebSocketHandler.class); @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) { log.info("接收到客户端的消息:[{}]", msg.text()); // 如果是向客户端发送文本消息,则需要发送 TextWebSocketFrame 消息 InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); String ip = inetSocketAddress.getHostName(); String txtMsg = "[" + ip + "][" + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "] ==> " + msg.text(); ctx.channel().writeAndFlush(new TextWebSocketFrame(txtMsg)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); log.error("服务器发生了异常:", cause); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) { log.info("web socket 握手成功。"); WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt; String requestUri = handshakeComplete.requestUri(); log.info("requestUri:[{}]", requestUri); String subproTocol = handshakeComplete.selectedSubprotocol(); log.info("subproTocol:[{}]", subproTocol); handshakeComplete.requestHeaders().forEach(entry -> log.info("header key:[{}] value:[{}]", entry.getKey(), entry.getValue())); } else { super.userEventTriggered(ctx, evt); } } }
注意:
1、此处只处理文本消息,因此 SimpleChannelInboundHandler 中的范型写 TextWebSocketFrame
2、发送文本消息给客户端,需要发送 TextWebSocketFrame 对象,否则客户端接收不到。
3、处理 握手后 的处理,判断是否是 HandshakeComplete 事件。
4、处理二进制消息
package com.huan.netty.websocket; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 处理二进制消息 * * @author huan.fu * @date 2018/11/8 - 14:37 */ public class BinaryWebSocketFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> { private static final Logger log = LoggerFactory.getLogger(BinaryWebSocketFrameHandler.class); @Override protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws InterruptedException { log.info("服务器接收到二进制消息,消息长度:[{}]", msg.content().capacity()); ByteBuf byteBuf = Unpooled.directBuffer(msg.content().capacity()); byteBuf.writeBytes(msg.content()); ctx.writeAndFlush(new BinaryWebSocketFrame(byteBuf)); } }
注意:
1、此处只处理二进制消息,因此泛型中写 BinaryWebSocketFrame
5、客户端的写法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>web socket 测试</title> </head> <body> <div style="width: 600px;height: 400px;"> <p>服务器输出:</p> <div style="border: 1px solid #CCC;height: 300px;overflow: scroll" id="server-msg-container"> </div> <p> <textarea id="inp-msg" style="height: 50px;width: 500px"></textarea><input type="button" value="发送" id="send"><br/> 选择图片: <input type="file" id="send-pic"> </p> </div> <script type="application/javascript"> var ws = new WebSocket("ws://127.0.0.1:9898/chat"); ws.onopen = function (ev) { }; ws.onmessage = function (ev) { console.info("onmessage", ev); var inpMsg = document.getElementById("server-msg-container"); if (typeof ev.data === "string") { inpMsg.innerHTML += ev.data + "<br/>"; } else { var result = ev.data; var flagReader = new FileReader(); flagReader.readAsArrayBuffer(result); flagReader.onload = function () { var imageReader = new FileReader(); imageReader.readAsDataURL(result); console.info("服务器返回的数据大小:", result.size); imageReader.onload = function (img) { var imgHtml = "<img src='" + img.target.result + "' style='width: 100px;height: 100px;'>"; inpMsg.innerHTML += imgHtml.replace("data:application/octet-stream;", "data:image/png;") + "<br />"; inpMsg.scroll(inpMsg.scrollWidth,inpMsg.scrollHeight); }; } } }; ws.onerror = function () { var inpMsg = document.getElementById("server-msg-container"); inpMsg.innerHTML += "发生异常" + "<br/>"; }; ws.onclose = function () { var inpMsg = document.getElementById("server-msg-container"); inpMsg.innerHTML += "webSocket 关闭" + "<br/>"; }; // 发送文字消息 document.getElementById("send").addEventListener("click", function () { ws.send(document.getElementById("inp-msg").value); }, false); // 发送图片 document.querySelector('#send-pic').addEventListener('change', function () { var files = this.files; if (files && files.length) { var file = files[0]; var fileReader = new FileReader(); fileReader.readAsArrayBuffer(file); fileReader.onload = function (e) { // 获取到文件对象 var result = e.target.result; // 发送数据到服务器端 ws.send(result) } } }, false); </script> </body> </html>
注意:
1、如何处理后端返回的二进制数据。
6、实现效果
完成代码:
代码如下:https://gitee.com/huan1993/netty-study/tree/master/src/main/java/com/huan/netty/websocket
相关推荐
在这个处理器中,我们可以定义打开、关闭、文本或二进制消息的回调方法。 客户端使用WebSocketClientHandshaker与服务器进行握手,并通过WebSocketClientProtocolHandler处理响应。Netty的WebSocketClientBootstrap...
连接建立后,客户端和服务器可以通过发送帧来交互数据,这些帧可以携带文本、二进制或其他类型的数据。 在这个示例项目中,我们可能会遇到以下几个关键部分: 1. **WebSocket 服务器端实现**:Netty 提供了 ...
这两个类实现了帧的拆包和打包,包括控制帧(如 pong、close、ping)和数据帧(二进制或文本)。 3. **WebSocketHandshaker**: WebSocket 握手是通过 HTTP 协议进行的。Netty 的 `WebSocketServerHandshaker` ...
3. **处理消息**:`WebSocketServerHandler` 的 `channelRead0()` 方法会处理接收到的 WebSocket 帧,可以是文本或二进制数据。根据业务需求解析数据,执行相应的操作。 4. **发送响应**:通过 `...
在这个处理类中,你需要解析WebSocket帧,区分文本帧和二进制帧,并根据需要转发消息到其他在线用户。 - 实现私聊功能,需要维护一个用户会话池,每个会话代表一个在线用户,通过用户ID或唯一标识来区分。当接收到...
例如,它可以处理文本帧、二进制帧,以及ping/pong帧等。 通过这种方式,我们就成功地在Netty中修改了WebSocket服务器的单包大小限制,从而解决了不能发送大数据包的问题。这种方法对于需要传输大量数据的场景,如...
5. **WebSocket数据帧**: WebSocket通信是基于数据帧的,有文本帧、二进制帧、ping/pong帧等多种类型。Netty提供了`WebSocketFrame`类来处理这些帧。 6. **Netty的ChannelHandlerContext**: 在Netty中,`...
对于 WebSocket,你可以发送帧(文本或二进制);对于 TCP,你可以直接发送字节流。 通过这种方式,Netty 允许我们灵活地在同一端口上处理多种协议,简化了多协议服务的实现,减少了系统资源的占用。这种设计模式在...
- 支持文本和二进制数据:WebSocket支持发送文本和二进制数据,适合多种应用场景。 3. **Netty实现WebSocket服务器** - 创建WebSocketServerBootstrap,配置NioEventLoopGroup,用于处理网络事件。 - 定义...
WebSocket 帧可以携带两种类型的数据:文本帧(Text frames)和二进制帧(Binary frames)。Netty 提供了 `WebSocketServerMessageHandler` 来处理这两种数据类型。 7. **心跳与保持连接** 为了确保连接的活性,...
WebSocket 协议定义了帧格式,包括文本帧和二进制帧,用于传输不同类型的数据。 3. **Netty 中的 WebSocket 支持**:Netty 提供了 WebSocketServerProtocolHandler 和 WebSocketClientHandler 两个类,用于处理 ...
2. **帧结构**:WebSocket数据通过帧的形式传输,支持文本、二进制等多种类型的数据。 3. **心跳与断线重连**:WebSocket连接保持活跃状态需要心跳机制,检测并处理断线情况,确保连接稳定性。 **Spring框架** ...
6. **消息处理**:在WebSocket连接建立后,服务器可以向客户端发送任意类型的数据,包括文本、二进制等。Netty提供了TextWebSocketFrame和BinaryWebSocketFrame来封装不同类型的消息。 7. **安全性**:WebSocket...
总的来说,Netty-WebSocket是一个非常有用的示例项目,可以帮助开发者快速理解和实现WebSocket通信。通过研究和学习这个项目,你可以掌握如何在Java环境中利用Netty构建高效的WebSocket服务器和客户端。此外,还可以...
消息可以是文本或二进制数据,根据聊天应用的需求进行解析和处理。 3. **连接管理**:维护客户端连接池,记录每个连接的用户信息。当接收到新的连接请求时,为每个客户端分配一个唯一标识,并将连接信息存储起来,...
服务器需要验证握手请求,并处理接收到的文本或二进制帧。 2. **WebSocket 客户端**:通常是一个 JavaScript 应用,使用浏览器提供的 WebSocket API 连接到服务器。客户端可以发送消息到服务器,并监听服务器推送的...
WebSocket协议支持文本和二进制数据传输,可以实现低延迟的实时通信。 3. Netty实现WebSocket 在Netty中,WebSocket的实现分为两个部分:WebSocket服务器和WebSocket客户端。服务器端需要实现...
通过 `netty-websocket-demo-master` 压缩包中的代码,你可以看到一个完整的 Netty WebSocket 服务器和客户端实现,包括如何设置服务器配置、处理器链、以及如何发送和接收 WebSocket 消息。通过研究这些代码,你...
4. **WebSocket帧处理**:WebSocket帧有多种类型,包括文本、二进制、关闭和ping/pong。你需要根据帧的类型进行相应的处理,如发送文本消息或关闭连接。 5. **编解码器**:Netty提供了WebSocketServerFrameDecoder...