netty实现webSocket协议
2017-06-13 16:38
405 查看
1、http协议现状
一直以来http协议一直都是请求/响应的模式,如果服务器有新的数据变化要推送给客户单,此时需要客户端发送一个请求由服务器进行响应。要想实现服务端立即推送到客户端,可以使用轮训的机制或Comet,但这里有一个共同的问题是:由于http协议的开销,导致他们不适用于低延迟。http协议弊端如下:
http为半双工模式,不能同时在2个方向上进行传输,某一个时刻只能在一个方向上进行传输。
http消息冗长繁琐。http消息包含消息头、消息体、换行符等。
2、webSocket协议
为了解决这些问题,WebSocket就产生了,是html5的协议规范。WebSocket的将网络套接字引入到了客户端和服务端,浏览器和服务端之间可以通过套接字建立持久的连接,双方随时都可以互发数据,而不是http的控制请求--应答模式。其特点如下:单一的tcp连接,采用全双工模式通信;
无头部信息、Cookie和身份验证;
通过 “ping/pong”帧保持链路激活;
服务器可以主动传递消息给客户端,不用客户端轮训。
在本例子中进行抓包,websocket协议如下:
Host: localhost:8080
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8
Sec-WebSocket-Key: JF9jXfxS2FBxTXpR8KksBg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
服务端返回信息如下:
15:35:22.339956 IP6 (flowlabel 0x06a10, hlim 64, next-header TCP (6) payload length: 161) ::1.8080 > ::1.58558: Flags [P.], cksum 0x00a9 (incorrect -> 0x779f), seq 1705481743:1705481872, ack 1497439652, win 12728, options [nop,nop,TS
val 1949078939 ecr 1949078834], length 129: HTTP, length: 129
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: D4W/u92AQ1egKtfmiBvX9OXodyg=
`.j....@....................................e...YA....1........
t,..t,.2HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: D4W/u92AQ1egKtfmiBvX9OXodyg=
3、Netty实现webSocket协议
websocketServer代码如下:package zou; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; 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.stream.ChunkedWriteHandler; public class WebSocketServer { public void run(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("http-codec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); pipeline.addLast("http-chunked", new ChunkedWriteHandler()); pipeline.addLast("handler", new WebSocketServerHandler()); } }); Channel ch = b.bind(port).sync().channel(); System.out.println("web socket server started at port " + port); System.out.println("open you browser and navigate to http://localhost:" + port + "/"); ch.closeFuture().sync(); } catch (Exception e) { } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args != null && args.length > 0) { try { port = Integer.parseInt(args[0]); } catch (Exception e) { } } new WebSocketServer().run(port); } }这里添加HttpServerCodec,将请求和应答消息编码或者解码为http消息。HttpObjectAggregator的作用是将http消息的多个部分组合成一条完整的http消息;ChunkedWriteHandler来向客户端发送html5文件,它主要用于浏览器和服务端进行websocket通信;最后增加WebSocket服务端的handler。
handler代码如下:
package zou; import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; import static io.netty.handler.codec.http.HttpHeaders.setContentLength; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import io.netty.util.CharsetUtil; import java.util.logging.Logger; public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName()); private WebSocketServerHandshaker handshaker; @Override protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { // 传统的http接入 if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, (FullHttpRequest) msg); } // websocket接入 else if (msg instanceof WebSocketFrame) { handleWebSockerFrame(ctx, (WebSocketFrame) msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { //http码返回失败 if (!req.getDecoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); return; } WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080", null, false); handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), req); } } private void handleWebSockerFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { //判断是否是关闭链路的指令 if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } //判断是否是ping 消息 if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } //本例仅支持文本,不支持二进制消息 if (!(frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName())); } //返回应答消息 String request = ((TextWebSocketFrame) frame).text(); ctx.channel().write(new TextWebSocketFrame(request + ",欢饮使用Netty WebSocket服务,现在时刻:" + new java.util.Date().toString())); } private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { //返回应答给客户端 if (res.getStatus().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8); res.content().writeBytes(buf); buf.release(); setContentLength(res, res.content().readableBytes()); } ChannelFuture f = ctx.channel().writeAndFlush(res); //如果是非keep-Alive,关闭连接 if (!isKeepAlive(req) || res.getStatus().code() != 200) { f.addListener(ChannelFutureListener.CLOSE); } } }
页面代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Netty WebSocket时间服务器</title> </head> <body> <br> <script type="text/javascript"> var socket; if(!window.WebSocket){ window.WebSocket=window.MozWebSocket; } if(window.WebSocket){ socket=new WebSocket("ws://localhost:8080/websocket"); socket.onmessage=function(event){ var ta=document.getElementById('responseText'); ta.value=""; ta.value=event.data }; socket.onopen=function(event){ var ta=document.getElementById('responseText'); ta.value='打开websocket服务正常,浏览器支持websocket!'; }; socket.onclose=function(event){ var ta=document.getElementById('responseText'); ta.value=""; ta.value="websocket关闭!"; }; }else{ alert("抱歉,您的浏览器不支持Websocket协议"); } function send(message){ if(!window.WebSocket){ return ; } if(socket.readyState==WebSocket.OPEN){ socket.send(message); }else{ alert("websocket连接没有成功"); } } </script> <form onsubmit="return false;"> <input type="text" name="message" value="Netty 最佳实践"/> <br> <input type="button" value="发送 websocket请求消息" onclick="send(this.form.message.value)"> <hr color="blue" /> <h3>服务端返回的应答消息</h3> <textarea id="responseText" style="width: 500px;height: 300px"></textarea> </form> </body> </html>
相关文章推荐
- 基于Websocket草案10协议的升级及基于Netty的握手实现
- 基于Websocket草案10协议的升级及基于Netty的握手实现
- Netty使用websocket协议实现汽车行驶轨迹追踪demo
- Jmeter实现WebSocket协议的接口和性能测试方法
- Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
- [转载]websocket最新协议的握手实现
- WebSocket不同版本的三种握手方式以及一个Netty实现JAVA类
- WebSocket不同版本的三种握手方式以及一个Netty实现JAVA类
- nodejs之socket.io模块——实现了websocket协议
- Websocket协议之php实现
- Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
- websocket协议实现验证码
- [转] websocket新版协议分析+python实现 & websocket 通信协议
- Netty 实现 WebSocket 聊天功能
- websocket最新协议的握手实现
- HTML5实现WebSocket协议原理浅析
- WebSocket不同版本的三种握手方式以及一个Netty实现JAVA类
- .NET平台下websocket协议的实现!
- websocket新版协议分析+python实现 - 小小的世界
- node.js之websocket协议的实现