netty 与 webSocket 起因
有个需求需要用到webSocket ,然后最近又正好在学netty,然后合起来走一波。写篇文章记录一下,做一个念想。
协议格式 0 1 2 3 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 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ 具体每一bit的意思 FIN 1bit 表示信息的最后一帧 RSV 1-3 1bit each 以后备用的 默认都为 0 Opcode 4bit 帧类型,稍后细说 Mask 1bit 掩码,是否加密数据,默认必须置为1 Payload 7bit 数据的长度 Masking-key 1 or 4 bit 掩码 Payload data (x + y) bytes 数据 Extension data x bytes 扩展数据 Application data y bytes 程序数据 OPCODE:4位 解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。 0x0表示附加数据帧 0x1表示文本数据帧 0x2表示二进制数据帧 0x3-7暂时无定义,为以后的非控制帧保留 0x8表示连接关闭 0x9表示ping 0xA表示pong 0xB-F暂时无定义,为以后的控制帧保留 开始我们先写一个什么都不加的 service 热热手,话不多说,代码如下
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * @author Sean Wu */ public class ServiceMain { public static void main(String[] args) throws Exception { NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(boss, worker) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.DEBUG)) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new StringEncoder()).addLast(new StringDecoder()).addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { super.channelRead(ctx, msg); System.out.println(msg.toString()); } }); } }); ChannelFuture f = b.bind(8866).sync(); f.channel().closeFuture().sync(); boss.shutdownGracefully(); worker.shutdownGracefully(); } }常规的netty入门示例,加了个String的编码和解码器,还加了一个打印消息的 Handler,并不是什么太复杂的代码。
添加Http的支持websocket 协议作为 http 协议的一种升级,最好么我们先顺手添加一下对 Http 协议的支持。首先我们写一个 HTTPRequestHandler,话不多说,代码如下
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.DefaultHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; /** * @author Sean Wu */ public class HTTPRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { // 创建要返回的内容 byte[] retBytes = "this a simple http response".getBytes(); ByteBuf byteBuf = Unpooled.copiedBuffer(retBytes); // 由于http并不是我们关心的重点,我们就直接返回好了 DefaultHttpResponse response = new DefaultHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, retBytes.length); ctx.writeAndFlush(response); ctx.writeAndFlush(byteBuf); } }这个 Handler 对 http 协议做了一个最简单的支持,就是不管客户端传啥都返回一个 this a simple http response。什么keep-alive,Expect:100-Continue都先不管好了,跟我们这次要讲的websocket 并没有什么关系的说。然后我们改一下我们上面的 ServiceMain 这个类,在Channel里添加对http的支持。代码如下。
import com.jiuyan.xisha.websocket.handler.HTTPRequestHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; 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.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * @author Sean Wu */ public class ServiceMain { public static void main(String[] args) throws Exception { NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(boss, worker) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.DEBUG)) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new HttpServerCodec()) .addLast(new HttpObjectAggregator(65536)) .addLast(new HTTPRequestHandler()); } }); ChannelFuture f = b.bind(8866).sync(); f.channel().closeFuture().sync(); boss.shutdownGracefully(); worker.shutdownGracefully(); } }