一、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

功能介绍:

服务器端开发:

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; /**
* @author lilinfeng
* @version 1.0
* @date 2014年2月14日
*/
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));
ch.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 your browser and navigate to http://localhost:"
+ port + '/'); ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
new WebSocketServer().run(port);
}
}

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

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

ChunkedWriteHandler:向客户端发送HTML5文件

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

 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.HttpUtil;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil; import java.util.logging.Level;
import java.util.logging.Logger; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /**
* @author lilinfeng
* @version 1.0
* @date 2014年2月14日
*/
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
private static final Logger logger = Logger
.getLogger(WebSocketServerHandler.class.getName()); private WebSocketServerHandshaker handshaker; @Override
public void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
// 传统的HTTP接入
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) msg);
}
// WebSocket接入
else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} private void handleHttpRequest(ChannelHandlerContext ctx,
FullHttpRequest req) throws Exception { // 如果HTTP解码失败,返回HHTP异常
if (!req.decoderResult().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/websocket", null, false);
handshaker = wsFactory.newHandshaker(req);
if (handshaker == null) {
WebSocketServerHandshakerFactory
.sendUnsupportedVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), req);
}
} private void handleWebSocketFrame(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();
if (logger.isLoggable(Level.FINE)) {
logger.fine(String.format("%s received %s", ctx.channel(), request));
}
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();
HttpUtil.setContentLength(res, res.content().readableBytes());
} // 如果是非Keep-Alive,关闭连接
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}

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

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

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

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

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

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

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

五、客户端以及测试

html5中的JS代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
Netty WebSocket 时间服务器
</head>
<br>
<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><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协议最基本的演示,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. UVA 141 The Spot Game 斑点游戏。。

     The Spot Game  The game of Spot is played on an NxN board as shown below for N = 4. During the game ...

  2. 欧几里德算法及其扩展(推导&&模板)

    有关欧几里德算法整理: 1.一些相关概念: <1>.整除性与约数: ①一个整数可以被另外一个整数整除即为d|a(表示d整除a,通俗的说是a可以被d整除),其含义也可以说成,存在某个整数k, ...

  3. asp.net打印网页后自动关闭网页【无需插件】

    项目遇需要网页加载自动打印网页后需要自动关闭该网页,但是百度了好久发现都是需要插件什么的 于是就自己摸索摸索,用js弄了个定时器,意外的发现,当打印设置窗口弹出后,定时器就暂停了 不管你点击取消或者打 ...

  4. 企业架构与建模之Archimate视图和视角

    企业架构与建模之Archimate视图和视角 3. ArchiMate的视角与视图 创建.维护一个企业架构是一件非常复杂繁琐的事情,因为这项工作需要面对许多背景.利益各异的干系人,对他们所关注的问题进 ...

  5. 图解Javascript引用类型之数组

    以图说事明理,恰当时候会事半功陪.今天我就尝试着用图的方式讲讲“JavaScript引用类型之数组”.望更多童鞋给我反馈! 好东西分享给大家,但要尊重事实!!!因此特别说明:本图非我本人亲自所作,乃我 ...

  6. 基于“泵”的TCP通讯(接上篇)

    基于“泵”的TCP通讯(接上篇) 上一篇博客中说了基于“泵”的UDP通讯,附上了一个Demo,模拟飞鸽传书的功能,功能不太完善,主要是为了说明“泵”在编程中的应用.本篇文章我再附上一个关于TCP通讯的 ...

  7. slxna,游戏页面切到后台回来后返回sl页面导致sl页面无响应,解决方法。

    slxna在wp7上表现很好,因为那会xna还是微软的亲儿子.但是到wp8时代,微软丢弃xna,不管不问了.但是丢之前没有把兼容搞的完美,以致有很多隐秘的坑,说不定就踩到了. 我这个游戏的sl页面用了 ...

  8. VS中为类,函数代码自动添加版权注释信息

    以web项目为例: 一:给类加注释 1.在visual studio 的安装路径下        如:[盘符]:\Program files\Microsoft Visual Studio 8\Com ...

  9. 实战 ASP.NET MVC Web API

    实战 ASP.NET MVC Web API Web API 框架基于 ASP.NET MVC 框架开发,是一个面向 Http 协议的通信框架.相对于 WCF 而言,Web API 只面向于 Http ...

  10. 黑马程序员:Java基础总结----反射

    黑马程序员:Java基础总结 反射   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 反射 反射的基石:Class类 Class类代表Java类,它的各个实例对象又分别 ...