一、HTTP协议的弊端

将HTTP协议的主要弊端总结如下:

  • (1) 半双工协议:可以在客户端和服务端2个方向上传输,但是不能同时传输。同一时刻,只能在一个方向上传输。
  • (2) HTTP消息冗长:相比于其他二进制协议,有点繁琐。
  • (3) 针对服务器推送的黑客攻击,例如长时间轮询。

现在很多网站的消息推送都是使用轮询,即客户端每隔1S或者其他时间给服务器发送请求,然后服务器返回最新的数据给客户端。HTTP协议中的Header非常冗长,因此会占用很多的带宽和服务器资源。

比较新的技术是Comet,使用了AJAX。虽然可以双向通信,但是依然需要发送请求,而且在Comet中,普遍采用了长连接,也会大量消耗服务器的带宽和资源。

为了解决这个问题,HTML5定义的WebSocket协议。

二、WebSocket协议介绍

在WebSocket API中,浏览器和服务器只需要一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道,两者就可以直接互相传送数据了。

WebSocket基于TCP双向全双工协议,即在同一时刻,即可以发送消息,也可以接收消息,相比于HTTP协议,是一个性能上的提升。

特点:

  •   单一的TCP连接,全双工;
  •   对代理、防火墙和路由器透明;
  •   无头部信息、Cookie和身份验证;
  •   无安全开销;
  •   通过"ping/pong"帧保持链路激活;
  •   服务器可以主动传递消息给客户端,不再需要客户端轮询;

拥有以上特点的WebSocket就是为了取代轮询和Comet技术,使得客户端浏览器具备像C/S架构下桌面系统一样的实时能力。

浏览器通过js建立一个WebSocket的请求,连接建立后,客户端和服务器端可以通过TCP直接交换数据。

因为WebSocket本质上是一个TCP连接,稳定,所以在Comet和轮询比拥有性能优势,如图所示:

三、WebSocket连接

3.1 连接建立

client端发送握手请求,请求消息如图所示:

  • 这个请求和普通的HTTP请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: Websocket"表明这是一个申请协议升级的HTTP请求。
  • 服务器尝试解析这个信息,然后返回应答信息给客户端,因此客户端和服务器端的WebSocket连接就建立起来了,双方可以通过这个连接通道自由的传递信息。
  • 这个连接会持续到某一方主动断开连接。

服务端的应答请求如图所示:

client消息中的"Sec-WebSocket-Key"是随机的,服务器端会用这些数据来构造一个"SHA-1"的信息摘要,把"Sec-WebSocket-Key"加上一个魔幻字符串。使用"SHA-1"加密,然后进行BASE64编码,将结果作为"Sec-Webscoket-Accept"头的值。

3.2 生命周期

  • 握手成功,连接建立后,以"Messages"的方式通信。
  • 一个消息由一个或者多个""组成。
  • 帧都有自己的类型,同一消息的多个帧类型相同。
  • 广义上,类型可以是文本、二进制、控制帧如信号。

3.3 连接关闭

  • 安全方法是关闭底层TCP连接以及TLS会话。
  • 底层的TCP连接,正常情况下,应该由服务器先关闭。
  • 异常时(比如合理的时间内没有接收到服务器的TCP Close),可以由客户端发起TCP Close。因此,在client发起TCP Close时,服务器应该立即发起一个TCP Close操作;客户端则等待服务器的TCP Close;
  • 关闭消息带有一个状态码和可选的关闭原因,它必须按照协议要求发送一个Close控制帧。

四、协议开发

官方demo: http://netty.io/4.1/xref/io/netty/example/http/websocketx/server/package-summary.html

功能介绍:

服务器端开发:

  1. import io.netty.bootstrap.ServerBootstrap;
  2. import io.netty.channel.Channel;
  3. import io.netty.channel.ChannelInitializer;
  4. import io.netty.channel.ChannelPipeline;
  5. import io.netty.channel.EventLoopGroup;
  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.stream.ChunkedWriteHandler;
  12.  
  13. /**
  14. * @author lilinfeng
  15. * @version 1.0
  16. * @date 2014年2月14日
  17. */
  18. public class WebSocketServer {
  19. public void run(int port) throws Exception {
  20. EventLoopGroup bossGroup = new NioEventLoopGroup();
  21. EventLoopGroup workerGroup = new NioEventLoopGroup();
  22. try {
  23. ServerBootstrap b = new ServerBootstrap();
  24. b.group(bossGroup, workerGroup)
  25. .channel(NioServerSocketChannel.class)
  26. .childHandler(new ChannelInitializer<SocketChannel>() {
  27.  
  28. @Override
  29. protected void initChannel(SocketChannel ch)
  30. throws Exception {
  31. ChannelPipeline pipeline = ch.pipeline();
  32. pipeline.addLast("http-codec",
  33. new HttpServerCodec());
  34. pipeline.addLast("aggregator",
  35. new HttpObjectAggregator(65536));
  36. ch.pipeline().addLast("http-chunked",
  37. new ChunkedWriteHandler());
  38. pipeline.addLast("handler",
  39. new WebSocketServerHandler());
  40. }
  41. });
  42.  
  43. Channel ch = b.bind(port).sync().channel();
  44. System.out.println("Web socket server started at port " + port
  45. + '.');
  46. System.out
  47. .println("Open your browser and navigate to http://localhost:"
  48. + port + '/');
  49.  
  50. ch.closeFuture().sync();
  51. } finally {
  52. bossGroup.shutdownGracefully();
  53. workerGroup.shutdownGracefully();
  54. }
  55. }
  56.  
  57. public static void main(String[] args) throws Exception {
  58. int port = 8080;
  59. if (args.length > 0) {
  60. try {
  61. port = Integer.parseInt(args[0]);
  62. } catch (NumberFormatException e) {
  63. e.printStackTrace();
  64. }
  65. }
  66. new WebSocketServer().run(port);
  67. }
  68. }

HttpServerCodec:将请求和应答消息解码为HTTP消息

HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息

ChunkedWriteHandler:向客户端发送HTML5文件

看上去和HTTP协议的非常类似,下面从Handler中来寻找答案:

  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.buffer.Unpooled;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelFutureListener;
  5. import io.netty.channel.ChannelHandlerContext;
  6. import io.netty.channel.SimpleChannelInboundHandler;
  7. import io.netty.handler.codec.http.DefaultFullHttpResponse;
  8. import io.netty.handler.codec.http.FullHttpRequest;
  9. import io.netty.handler.codec.http.FullHttpResponse;
  10. import io.netty.handler.codec.http.HttpUtil;
  11. import io.netty.handler.codec.http.websocketx.*;
  12. import io.netty.util.CharsetUtil;
  13.  
  14. import java.util.logging.Level;
  15. import java.util.logging.Logger;
  16.  
  17. import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
  18. import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  19.  
  20. /**
  21. * @author lilinfeng
  22. * @version 1.0
  23. * @date 2014年2月14日
  24. */
  25. public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
  26. private static final Logger logger = Logger
  27. .getLogger(WebSocketServerHandler.class.getName());
  28.  
  29. private WebSocketServerHandshaker handshaker;
  30.  
  31. @Override
  32. public void channelRead0(ChannelHandlerContext ctx, Object msg)
  33. throws Exception {
  34. // 传统的HTTP接入
  35. if (msg instanceof FullHttpRequest) {
  36. handleHttpRequest(ctx, (FullHttpRequest) msg);
  37. }
  38. // WebSocket接入
  39. else if (msg instanceof WebSocketFrame) {
  40. handleWebSocketFrame(ctx, (WebSocketFrame) msg);
  41. }
  42. }
  43.  
  44. @Override
  45. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  46. ctx.flush();
  47. }
  48.  
  49. private void handleHttpRequest(ChannelHandlerContext ctx,
  50. FullHttpRequest req) throws Exception {
  51.  
  52. // 如果HTTP解码失败,返回HHTP异常
  53. if (!req.decoderResult().isSuccess()
  54. || (!"websocket".equals(req.headers().get("Upgrade")))) {
  55. sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,
  56. BAD_REQUEST));
  57. return;
  58. }
  59.  
  60. // 构造握手响应返回,本机测试
  61. WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
  62. "ws://localhost:8080/websocket", null, false);
  63. handshaker = wsFactory.newHandshaker(req);
  64. if (handshaker == null) {
  65. WebSocketServerHandshakerFactory
  66. .sendUnsupportedVersionResponse(ctx.channel());
  67. } else {
  68. handshaker.handshake(ctx.channel(), req);
  69. }
  70. }
  71.  
  72. private void handleWebSocketFrame(ChannelHandlerContext ctx,
  73. WebSocketFrame frame) {
  74.  
  75. // 判断是否是关闭链路的指令
  76. if (frame instanceof CloseWebSocketFrame) {
  77. handshaker.close(ctx.channel(),
  78. (CloseWebSocketFrame) frame.retain());
  79. return;
  80. }
  81. // 判断是否是Ping消息
  82. if (frame instanceof PingWebSocketFrame) {
  83. ctx.channel().write(
  84. new PongWebSocketFrame(frame.content().retain()));
  85. return;
  86. }
  87. // 本例程仅支持文本消息,不支持二进制消息
  88. if (!(frame instanceof TextWebSocketFrame)) {
  89. throw new UnsupportedOperationException(String.format(
  90. "%s frame types not supported", frame.getClass().getName()));
  91. }
  92.  
  93. // 返回应答消息
  94. String request = ((TextWebSocketFrame) frame).text();
  95. if (logger.isLoggable(Level.FINE)) {
  96. logger.fine(String.format("%s received %s", ctx.channel(), request));
  97. }
  98. ctx.channel().write(
  99. new TextWebSocketFrame(request
  100. + " , 欢迎使用Netty WebSocket服务,现在时刻:"
  101. + new java.util.Date().toString()));
  102. }
  103.  
  104. private static void sendHttpResponse(ChannelHandlerContext ctx,
  105. FullHttpRequest req, FullHttpResponse res) {
  106. // 返回应答给客户端
  107. if (res.getStatus().code() != 200) {
  108. ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(),
  109. CharsetUtil.UTF_8);
  110. res.content().writeBytes(buf);
  111. buf.release();
  112. HttpUtil.setContentLength(res, res.content().readableBytes());
  113. }
  114.  
  115. // 如果是非Keep-Alive,关闭连接
  116. ChannelFuture f = ctx.channel().writeAndFlush(res);
  117. if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
  118. f.addListener(ChannelFutureListener.CLOSE);
  119. }
  120. }
  121.  
  122. @Override
  123. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
  124. throws Exception {
  125. cause.printStackTrace();
  126. ctx.close();
  127. }
  128. }

(1) 第一次握手由HTTP协议承载,所以是一个HTTP消息,根据消息头中是否包含"Upgrade"字段来判断是否是websocket。

(2) 通过校验后,构造WebSocketServerHandshaker,通过它构造握手响应信息返回给客户端,同时将WebSocket相关的编码和解码类动态添加到ChannelPipeline中。

下面分析链路建立之后的操作:

(1) 客户端通过文本框提交请求给服务端,Handler收到之后已经解码之后的WebSocketFrame消息。

(2) 如果是关闭按链路的指令就关闭链路

(3) 如果是维持链路的ping消息就返回Pong消息。

(4) 否则就返回应答消息

五、客户端以及测试

html5中的JS代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. Netty WebSocket 时间服务器
  6. </head>
  7. <br>
  8. <body>
  9. <br>
  10. <script type="text/javascript">
  11. var socket;
  12. if (!window.WebSocket) {
  13. window.WebSocket = window.MozWebSocket;
  14. }
  15. if (window.WebSocket) {
  16. socket = new WebSocket("ws://localhost:8080/websocket");
  17. socket.onmessage = function (event) {
  18. var ta = document.getElementById('responseText');
  19. ta.value = "";
  20. ta.value = event.data
  21. };
  22. socket.onopen = function (event) {
  23. var ta = document.getElementById('responseText');
  24. ta.value = "打开WebSocket服务正常,浏览器支持WebSocket!";
  25. };
  26. socket.onclose = function (event) {
  27. var ta = document.getElementById('responseText');
  28. ta.value = "";
  29. ta.value = "WebSocket 关闭!";
  30. };
  31. }
  32. else {
  33. alert("抱歉,您的浏览器不支持WebSocket协议!");
  34. }
  35.  
  36. function send(message) {
  37. if (!window.WebSocket) {
  38. return;
  39. }
  40. if (socket.readyState == WebSocket.OPEN) {
  41. socket.send(message);
  42. }
  43. else {
  44. alert("WebSocket连接没有建立成功!");
  45. }
  46. }
  47. </script>
  48. <form onsubmit="return false;">
  49. <input type="text" name="message" value="Netty最佳实践"/>
  50. <br><br>
  51. <input type="button" value="发送WebSocket请求消息" onclick="send(this.form.message.value)"/>
  52. <hr color="blue"/>
  53. <h3>服务端返回的应答消息</h3>
  54. <textarea id="responseText" style="width:500px;height:300px;"></textarea>
  55. </form>
  56. </body>
  57. </html>

演示效果大致如下:

这里只是对WebSocket协议最基本的演示,WebSocket支持多种协议,文本,二进制,控制帧。

netty(4)高级篇-Websocket协议开发的更多相关文章

  1. WebSocket协议开发

    一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的.客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生.在2005年左右,Ajax开始让网络变得更加动态了.但所有的HTT ...

  2. netty websocket协议开发

    websocket的好处我们就不用多说了,就是用于解决长连接.服务推送等需要的一种技术. 以下我们来看一个例子: package com.ming.netty.http.websocket; impo ...

  3. netty(5)高级篇-私有协议栈

    来源:<Netty权威指南>  作者:李林峰 一.私有协议介绍 由于现代软件的复杂性,一个大型软件系统往往会被人为地拆分称为多个模块,另外随着移动互联网的兴起,网站的规模越来越大,业务功能 ...

  4. 真正实现Netty私有协议开发

    首先<Netty权威指南>私有协议开发那一章的样例代码是编译不通过的(但是这丝毫不影响本书的价值)处理方案可以参考:http://www.itnose.net/detail/6112870 ...

  5. Netty 框架学习 —— 添加 WebSocket 支持

    WebSocket 简介 WebSocket 协议是完全重新设计的协议,旨在为 Web 上的双向数据传输问题提供一个切实可行的解决方案,使得客户端和服务器之间可以在任意时刻传输消息 Netty 对于 ...

  6. netty高级篇(3)-HTTP协议开发

    一.HTTP协议简介 应用层协议http,发展至今已经是http2.0了,拥有以下特点: (1) CS模式的协议 (2) 简单 - 只需要服务URL,携带必要的请求参数或者消息体 (3) 灵活 - 任 ...

  7. iOS开发网络篇—HTTP协议

    iOS开发网络篇—HTTP协议 说明:apache tomcat服务器必须占用8080端口 一.URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) ...

  8. iOS开发——高级技术精选&底层开发之越狱开发第一篇

    底层开发之越狱开发第一篇 做越狱开发也有一些时间了,有很多东西想总结一下,希望给他人一些借鉴,也是自己对过去开发经历的一些总结.个人不推荐使用盗版,这里主要以技术介绍为主. 这个系列里面主要介绍怎样进 ...

  9. 02.iOS开发网络篇—HTTP协议

    iOS开发网络篇—HTTP协议 说明:apache tomcat服务器必须占用8080端口 一.URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) ...

随机推荐

  1. 正则表达式(c#)

      只能输入数字:"^[0-9]*$". 只能输入n位的数字:"^\d{n}$". 只能输入至少n位的数字:"^\d{n,}$". 只能输入 ...

  2. ckeditor:复制内容到ckeditor时,只保留文本,忽略其样式解决方法

    打开ckeditor 包下的config.js,在 CKEDITOR.editorConfig= function(config){...}添加一句配置: config.forcePasteAsPla ...

  3. ASP.NET Web API框架揭秘:路由系统的几个核心类型

    ASP.NET Web API框架揭秘:路由系统的几个核心类型 虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分 ...

  4. SQL Server监控清单

    SQL Server监控清单 一. 服务器1. 状态监控(1) 服务器是否可访问?(2) 相应的数据库服务是否启用?(3) 操作系统事件日志中的错误或告警(4) 磁盘可用空间 服务器状态监控,不管使用 ...

  5. Linux 学习 step by step (2)

      Linux 学习 step by step (2) Linux,想要我说爱你真的不容易了,尽管,你是ubutun,尽管,你有蛮界面.但是,操作你,还是没有操作windows那么的如鱼得水了.为了更 ...

  6. Bottle GET method. Request

    python bottle framework #!/usr/bin/python # -*- coding utf-8 -*- from bottle import route, run, debu ...

  7. 使用EasyMock对Servlet进行简单的测试

    这是一个WebProject,但不需配置web.xml,因为EasyMock用不上它 首先是用到的实体类User.java package com.jadyer.model; public class ...

  8. hdu 4515 年月份模拟题

    小Q系列故事——世界上最遥远的距离 Time Limit: 500/200 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) ...

  9. Android开发之Ubuntu上Eclipse不显示手机设备

    一.准备工作   A.开启Android设备,用USB数据线连接到Ubuntu系统.   B.启用设备的USB调试模试    C.启动Eclipse,在Devices栏会现一个有很多???号的不明设备 ...

  10. Android监听WebView滑动到底部

    MainActivity如下: package cn.testwebview; import android.app.Activity; import android.graphics.Bitmap; ...