一、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. Mock原理学习

    同事搓蛋问了我一个问题,mock的原理是啥,没怎么想出来,于是花了点时间学习了一下. 从Moq这个库入手:https://github.com/moq/moq4 Moq用到了Castle的库用于Dyn ...

  2. 如何给非AppCompatActivity添加Toolbar?--关于5.0新特性兼容5.0以下设备的探索

    Android支持包22.1引进了AppCompatDelegate 最新22.1版本的支持包引入了大量酷炫的新特性,这些特性将允许我们轻易地将材料设计/API 21+的特性应用到之前的那些老的,不兼 ...

  3. 二分法-C++

    对于一个非线性方程f(x)=0求改方程的根,我们的思路可以这么想: 1.根的存在性.若该方程没有根,何必徒劳想法设法去求它的解呢?对于一个方程,我们怎么去找他的根,有连续函数零点定理可知:若有f(a) ...

  4. AngularJS1

    Ⅰ.AngularJS的点点滴滴--引导   AngularJS已经被很多人像炒冷饭一样炒过啦,大部分都是直接复制官方文档没有说明一些注意事项,不过什么都要从头开始吧 ###页面引导实例化 * * * ...

  5. .Net 4.5 异步编程初试(async和await)

    .Net 4.5 异步编程初试(async和await) 前言 最近自己在研究Asp.Net Web API.在看到通过客户端来调用Web API的时候,看到了其中的异步编程,由于自己之前没有接触过, ...

  6. EntityFramework5提供的迁移工具

    目录 背景之前是如何做的?EntityFramework5提供了更好的选择备注 背景返回目录 刚毕业做项目的时候,没有用“迁移”这个概念,系统发布和更新的过程让人非常痛苦,在学习 Ruby On Ra ...

  7. 对象池化技术 org.apache.commons.pool

    恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率.Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以 ...

  8. Make Things Move -- Javascript html5版(一)文件目录结构和工具方法准备

    从这一篇开始,就来开始我们的make things move之旅吧 在此之前,要知道ActionScript(AS)的语法和JS是不一样的,AS是相对于JS而言更好的支持了面向对象的特性,所以我们可以 ...

  9. K2 BPM项目 基于COM组件调用SAP RFC 问题

    K2 BPM项目 基于COM组件调用SAP RFC 问题 问题前景: 环境:Win 2008 R2 64bit 最近项目中有支流程需求中需要在会计入账环节回写SAP的会计凭证. SAP组给我们提供.N ...

  10. IS动态左侧菜单-01

    <%@ Page Language="C#" CodeFile="Default3.aspx.cs" Inherits="Default3&qu ...