一、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. Manager(管理器)

    Manager(管理器) 索引 意图 结构 参与者 适用性 效果 实现 实现方式(一):Manager 模式的示例实现. 意图 将对一个类的所有对象的管理封装到一个单独的管理器类中. 这使得管理职责的 ...

  2. CLR中的垃圾回收机制

    CLR中采用代(generation)来作为其垃圾回收的一种机制,其唯一的目的是提升程序的性能.基予代的垃圾回收器有以下假设: ·对象越新,其生存周期越短. ·对象越老,其生存周期越长. ·回收堆的一 ...

  3. WCF从零学习之WCF概述(一)

    WCF从零学习之WCF概述(一) 一.WCF概述 我先了解了分布式应用程序开发,所谓分布式应用程序是指应用程序分布在不同计算机上,通过网络来共同完成一项任务.通常为服务器/客户端模式. 在WCF发布之 ...

  4. [转] Building xnu for OS X 10.10 Yosemite

    Source:http://shantonu.blogspot.jp/2014/10/building-xnu-for-os-x-1010-yosemite.html The OS X kernel ...

  5. tcp连接以及网络I/O的几个问题

    这段时间在做一些web方面开发的事情,用的Nginx+fast-cgi,计划深入看一下Nginx的内部实现和架构,以方便理解和调优.后面准备写一篇有关Nginx介绍和深度解析的文章,要深入理解web服 ...

  6. springMVC3学习(十一)--文件上传CommonsMultipartFile

    使用springMVC提供的CommonsMultipartFile类进行读取文件 需要用到上传文件的两个jar包 commons-logging.jar.commons-io-xxx.jar 1.在 ...

  7. selenium webdriver (python)

    selenium webdriver (python) 第一版PDF Posted on 2013-08-30 22:59 虫师 阅读(221) 评论(0) 编辑 收藏 前言 如果你是一位有pytho ...

  8. go语言中的数组切片:特立独行的可变数组

    go语言中的数组切片:特立独行的可变数组 初看go语言中的slice,觉得是可变数组的一种很不错的实现,直接在语言语法的层面支持,操作方面比起java中的ArrayList方便了许多.但是在使用了一段 ...

  9. visio UML用例里面找不到include关系

    今天用Microsoft Visio 2007画用例图时,发现visio UML用例里面找不到include关系,查到一个可行的解决办法:  1)创建一个UML用例图模板:打开Microsoft Vi ...

  10. SVN Access to '/svn/Test/!svn/me' forbidden,不能更新解决办法

    从之前的电脑将Repositorise复制到现在用的PC,出现可以checkout但是不能update的问题.在网上找到以下解决方法,均未能解决. 1.确认URL跟实际一致: 2.确认用户名密码正确: ...