经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
netty 与 webSocket
来源:cnblogs  作者:灬浣溪沙灬  时间:2018/11/1 9:42:35  对本文有异议

netty 与 webSocket

起因

有个需求需要用到webSocket ,然后最近又正好在学netty,然后合起来走一波。写篇文章记录一下,做一个念想。

协议格式

  1. 0 1 2 3
  2. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  3. +-+-+-+-+-------+-+-------------+-------------------------------+
  4. |F|R|R|R| opcode|M| Payload len | Extended payload length |
  5. |I|S|S|S| (4) |A| (7) | (16/64) |
  6. |N|V|V|V| |S| | (if payload len==126/127) |
  7. | |1|2|3| |K| | |
  8. +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
  9. | Extended payload length continued, if payload len == 127 |
  10. + - - - - - - - - - - - - - - - +-------------------------------+
  11. | |Masking-key, if MASK set to 1 |
  12. +-------------------------------+-------------------------------+
  13. | Masking-key (continued) | Payload Data |
  14. +-------------------------------- - - - - - - - - - - - - - - - +
  15. : Payload Data continued ... :
  16. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
  17. | Payload Data continued ... |
  18. +---------------------------------------------------------------+
  19. 具体每一bit的意思
  20. FIN 1bit 表示信息的最后一帧
  21. RSV 1-3 1bit each 以后备用的 默认都为 0
  22. Opcode 4bit 帧类型,稍后细说
  23. Mask 1bit 掩码,是否加密数据,默认必须置为1
  24. Payload 7bit 数据的长度
  25. Masking-key 1 or 4 bit 掩码
  26. Payload data (x + y) bytes 数据
  27. Extension data x bytes 扩展数据
  28. Application data y bytes 程序数据
  1. OPCODE4
  2. 解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。
  3. 0x0表示附加数据帧
  4. 0x1表示文本数据帧
  5. 0x2表示二进制数据帧
  6. 0x3-7暂时无定义,为以后的非控制帧保留
  7. 0x8表示连接关闭
  8. 0x9表示ping
  9. 0xA表示pong
  10. 0xB-F暂时无定义,为以后的控制帧保留

开始

我们先写一个什么都不加的 service 热热手,话不多说,代码如下

  1. import io.netty.bootstrap.ServerBootstrap;
  2. import io.netty.channel.*;
  3. import io.netty.channel.nio.NioEventLoopGroup;
  4. import io.netty.channel.socket.SocketChannel;
  5. import io.netty.channel.socket.nio.NioServerSocketChannel;
  6. import io.netty.handler.codec.string.StringDecoder;
  7. import io.netty.handler.codec.string.StringEncoder;
  8. import io.netty.handler.logging.LogLevel;
  9. import io.netty.handler.logging.LoggingHandler;
  10. /**
  11. * @author Sean Wu
  12. */
  13. public class ServiceMain {
  14. public static void main(String[] args) throws Exception {
  15. NioEventLoopGroup boss = new NioEventLoopGroup(1);
  16. NioEventLoopGroup worker = new NioEventLoopGroup();
  17. ServerBootstrap b = new ServerBootstrap();
  18. b.group(boss, worker)
  19. .channel(NioServerSocketChannel.class)
  20. .handler(new LoggingHandler(LogLevel.DEBUG))
  21. .childHandler(new ChannelInitializer<SocketChannel>() {
  22. protected void initChannel(SocketChannel ch) throws Exception {
  23. ChannelPipeline p = ch.pipeline();
  24. p.addLast(new StringEncoder()).addLast(new StringDecoder()).addLast(new ChannelInboundHandlerAdapter() {
  25. @Override
  26. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  27. super.channelRead(ctx, msg);
  28. System.out.println(msg.toString());
  29. }
  30. });
  31. }
  32. });
  33. ChannelFuture f = b.bind(8866).sync();
  34. f.channel().closeFuture().sync();
  35. boss.shutdownGracefully();
  36. worker.shutdownGracefully();
  37. }
  38. }

常规的netty入门示例,加了个String的编码和解码器,还加了一个打印消息的 Handler,并不是什么太复杂的代码。

添加Http的支持

websocket 协议作为 http 协议的一种升级,最好么我们先顺手添加一下对 Http 协议的支持。首先我们写一个 HTTPRequestHandler,话不多说,代码如下

  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.buffer.Unpooled;
  3. import io.netty.channel.ChannelHandlerContext;
  4. import io.netty.channel.SimpleChannelInboundHandler;
  5. import io.netty.handler.codec.http.DefaultHttpResponse;
  6. import io.netty.handler.codec.http.FullHttpRequest;
  7. import io.netty.handler.codec.http.HttpHeaderNames;
  8. import io.netty.handler.codec.http.HttpResponseStatus;
  9. /**
  10. * @author Sean Wu
  11. */
  12. public class HTTPRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  13. protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
  14. // 创建要返回的内容
  15. byte[] retBytes = "this a simple http response".getBytes();
  16. ByteBuf byteBuf = Unpooled.copiedBuffer(retBytes);
  17. // 由于http并不是我们关心的重点,我们就直接返回好了
  18. DefaultHttpResponse response = new DefaultHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
  19. response.headers().set(HttpHeaderNames.CONTENT_LENGTH, retBytes.length);
  20. ctx.writeAndFlush(response);
  21. ctx.writeAndFlush(byteBuf);
  22. }
  23. }

这个 Handler 对 http 协议做了一个最简单的支持,就是不管客户端传啥都返回一个 this a simple http response。什么keep-alive,Expect:100-Continue都先不管好了,跟我们这次要讲的websocket 并没有什么关系的说。然后我们改一下我们上面的 ServiceMain 这个类,在Channel里添加对http的支持。代码如下。

  1. import com.jiuyan.xisha.websocket.handler.HTTPRequestHandler;
  2. import io.netty.bootstrap.ServerBootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.ChannelPipeline;
  6. import io.netty.channel.nio.NioEventLoopGroup;
  7. import io.netty.channel.socket.SocketChannel;
  8. import io.netty.channel.socket.nio.NioServerSocketChannel;
  9. import io.netty.handler.codec.http.HttpObjectAggregator;
  10. import io.netty.handler.codec.http.HttpServerCodec;
  11. import io.netty.handler.logging.LogLevel;
  12. import io.netty.handler.logging.LoggingHandler;
  13. /**
  14. * @author Sean Wu
  15. */
  16. public class ServiceMain {
  17. public static void main(String[] args) throws Exception {
  18. NioEventLoopGroup boss = new NioEventLoopGroup(1);
  19. NioEventLoopGroup worker = new NioEventLoopGroup();
  20. ServerBootstrap b = new ServerBootstrap();
  21. b.group(boss, worker)
  22. .channel(NioServerSocketChannel.class)
  23. .handler(new LoggingHandler(LogLevel.DEBUG))
  24. .childHandler(new ChannelInitializer<SocketChannel>() {
  25. protected void initChannel(SocketChannel ch) throws Exception {
  26. ChannelPipeline p = ch.pipeline();
  27. p.addLast(new HttpServerCodec())
  28. .addLast(new HttpObjectAggregator(65536))
  29. .addLast(new HTTPRequestHandler());
  30. }
  31. });
  32. ChannelFuture f = b.bind(8866).sync();
  33. f.channel().closeFuture().sync();
  34. boss.shutdownGracefully();
  35. worker.shutdownGracefully();
  36. }
  37. }

可以看到也非常的简单,介绍下我们这里用到的几个Handler

ChannelHandler 作用
HttpServerCodec 对字节码根据http协议进行编码和解码,
HttpObjectAggregator 将一个 HttpMessage 和跟随它的多个 HttpContent 聚合

为单个 FullHttpRequest 或者 FullHttpResponse (取
决于它是被用来处理请求还是响应)。安装了这个之后,
ChannelPipeline 中的下一个 ChannelHandler 将只会
收到完整的 HTTP 请求或响应
HTTPRequestHandler | 处理 HttpObjectAggregator 送过来的 FullHttpRequest 请求

然后我们运行一下 ServiceMain 然后用浏览器访问一下,正常的话,如图所示。
正常返回

添加对 websocket 的支持

首先,我们在刚才的 HTTPRequestHandler 的 channelRead0 方法里添加对 websocket 接口的特殊处理。修改后的代码如下

  1. /**
  2. * @author Sean Wu
  3. */
  4. public class HTTPRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  5. protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
  6. System.out.println(msg.uri());
  7. // 如果后缀为 ws 的请求,则增加引用计数,将他传给下一个 ChannelInboundHandler
  8. if ("/ws".equalsIgnoreCase(msg.uri())) {
  9. ctx.fireChannelRead(msg.retain());
  10. return;
  11. }
  12. // 之前的代码
  13. }
  14. }

然后我们要加一个处理 websocket 协议的 handler
根据WebSocket 协议,netty 定义了如下六种帧

帧类型 秒速
BinaryWebSocketFrame 充满了二进制数据流的一个帧,大多是多媒体文件
TextWebSocketFrame 充满了文本的一个帧
CloseWebSocketFrame 用来关闭websocket的帧
PingWebSocketFrame 用来探活的的一个帧
PongWebSocketFrame 用来表示自己还活着的一个帧

Netty 里提供了一个叫 WebSocketServerProtocolHandler 的类,他会帮你处理 Ping,Pong,Close之类的服务状态的帧。这里我们只需要简单的用下TextWebSocketFramce就好了。

  1. /**
  2. * @author Sean Wu
  3. */
  4. public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
  5. @Override
  6. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
  7. if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
  8. ctx.writeAndFlush(new TextWebSocketFrame("client " + ctx.channel() + "join"));
  9. }
  10. super.userEventTriggered(ctx, evt);
  11. }
  12. protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
  13. System.out.println(msg.text());
  14. ctx.writeAndFlush(new TextWebSocketFrame("hello" + msg.text()));
  15. }
  16. }

这里我们的例子非常的简单,可以说是网上所有 netty-websocket 的例子里最简单的了。我们只是在收到了客户端的消息之后打印了一下然后原封不动的加个 hello 返回回去。

再然后,我们要改一下我们之前的 ChannelPipeline。添加对 websocket 的支持。改完之后的代码如下

  1. NioEventLoopGroup boss = new NioEventLoopGroup(1);
  2. NioEventLoopGroup worker = new NioEventLoopGroup();
  3. ServerBootstrap b = new ServerBootstrap();
  4. b.group(boss, worker)
  5. .channel(NioServerSocketChannel.class)
  6. .handler(new LoggingHandler(LogLevel.DEBUG))
  7. .childHandler(new ChannelInitializer<SocketChannel>() {
  8. protected void initChannel(SocketChannel ch) throws Exception {
  9. ChannelPipeline p = ch.pipeline();
  10. p.addLast(new HttpServerCodec())
  11. .addLast(new HttpObjectAggregator(65536))
  12. .addLast(new HTTPRequestHandler())
  13. .addLast(new WebSocketServerProtocolHandler("/ws"))
  14. .addLast(new TextWebSocketFrameHandler());
  15. }
  16. });
  17. ChannelFuture f = b.bind(8866).sync();
  18. f.channel().closeFuture().sync();
  19. boss.shutdownGracefully();
  20. worker.shutdownGracefully();
  21. }

运行示例

首先,启动我们的服务器。然后打开刚才的那个页面(http://127.0.0.1:8866/),打开调试模式(f12)。
然后输入如下 js 代码

  1. var ws = new WebSocket("ws://127.0.0.1:8866/ws");
  2. ws.onopen = function(evt) {
  3. console.log("链接建立了 ...");
  4. };
  5. ws.onmessage = function(evt) {
  6. console.log( "收到了消息: " + evt.data);
  7. };

image
可以看到,很完美。然后我们再试着用 ws.send("xisha")发些消息看。发消息的js代码和结果如下。
image
我们也可以打开网络面板查看我们的消息内容。
image
可以看到,只有一个链接。

总结

还有很多没讲到,恩。。。问题不大。下次有机会再说。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号