[分布式] 基于Netty及websocket应用与总结

1、介绍

Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络 IO 程序。

主要特性有以下几点:

统一的 API,适用于不同的协议(阻塞和非阻塞)

基于灵活、可扩展的事件驱动模型

高度可定制的线程模型(Reactor单线程、多线程、主从线程)

更好的吞吐量,低延迟

更省资源

NIO零拷贝(FileChannel.transferTo)

应用于RPC等中间件框架底层网络通信、大数据等领域

2. 网络编程模型(同步阻塞BIO)


通过BIO通信模型的服务端,通常接收到客户端的请求后为每个客户端创建一个新的线程进行链路处理。处理完成后,通过输出流应答返回给客户端,线程销毁,缺点是并发量高时,线程数膨胀,发生线程堆栈溢出。

同步非阻塞NIO


服务器实现模式为一个线程处理多个请求,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O的请求进行处理

异步非阻塞AIO

AIO引入异步通道的概念,采用了proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用

3. 一个简单的http server

public void bind(int port) throws Exception {

EventLoopGroup bossGroup= new NioEventLoopGroup();

EventLoopGroup workerGroup = new NioEventLoopGroup();

try{

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup,workerGroup)

.channel(NioServerSocketChannel.class)

.option(ChannelOption.SO_BACKLOG,1024)

.childHandler(new ChildChanelHandler());

ChannelFuture f = b.bind(port).sync();

f.channel().closeFuture().sync();

}finally {

bossGroup.shutdownGracefully();

workerGroup.shutdownGracefully();

}

}

}


5. Netty粘包/拆包问题


LineBasedFrameDecoder 和StringDecoder 都是Netty自带的类。特别要说下的,就是StringDecoder,它的作用是将接收到ByteBuf二进制数据,转换成字符串。另外,LineBasedFrameDecoder ,是一个非常简单的帧解码器,包含此解码器在内,Netty中比较常用的帧解码器,大致如下:

(1)固定长度帧解码器 - FixedLengthFrameDecoder

适用场景:每个上层数据包的长度,都是固定的,比如 100。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会把底层帧,拆分成一个个长度为 100 的数据包 (ByteBuf),发送到下一个 channelHandler入站处理器。

(2)行分割帧解码器 - LineBasedFrameDecoder

适用场景:每个上层数据包,使用换行符或者回车换行符做为边界分割符。发送端发送的时候,每个数据包之间以换行符/回车换行符作为分隔。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会使用换行分隔符,把底层帧分割成一个一个完整的应用层数据包,发送到下一站。前面的例子,已经对这个解码器进行了演示。

(3)自定义分隔符帧解码器 - DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder 是LineBasedFrameDecoder的通用版本。不同之处在于,这个解码器,可以自定义分隔符,而不是局限于换行符。如果使用这个解码器,在发送的时候,末尾必须带上对应的分隔符。

(4)自定义长度帧解码器 - LengthFieldBasedFrameDecoder

这是一种基于灵活长度的解码器。在数据包中,加了一个长度字段(长度域),保存上层包的长度。解码的时候,会按照这个长度,进行上层ByteBuf应用包的提取。

6. 序列化

MsgPack和Hessian序列化方式的区别

1) Hessian序列化的时候,会写入字段名称,然后字段值,可以想象为一个map。

2) MsgPack序列化的时候,不写入字段名字,会按字段顺序写入值,可以想象为一个数组。

从这里可以看出:

1) Hessian产生的数据包较大,MsgPack产生的数据包较小。网络传输数据更小。

2) 序列化中Hessian的性能较差,MsgPack性能更佳

3) Hessian的扩展性更好,上下兼容时,可以随意添加字段位置(相当于map可以随便赋值);MsgPack的性能更佳,但是上下兼容时,需要保证字段顺序(包括枚举顺序)。

4) 还包括其它一些差异:

例如:Hessian对Map/List等集合的支持就是全变成最普通的Hashmap或者ArrayList,一些指定的类型会丢失(例如LinkedHashMap–>HashMap),但是支持一些匿名的Map/List等集合类;

而MsgPack会保留集合类的类型(例如LinkedHashMap),但是不支持一些匿名集合类(例如List.subList(),Map.keySet(),Collections.emptyList(),Guava的匿名集合类,数据库查询结果直接返回的list等)。

总结: 如果需要支持字段顺序不一样的情况下调用,请使用Hessian序列化;但是为了性能及跨语言兼容性,在保证客户端与服务端的接口类文件保持一致的情况下,请使用MsgPack序列化!!

7. Websocket介绍

WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。

HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器 WebSocket是一种在单个TCP连接上进行全双工通讯的协议。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

8、WebSocket握手请求

WebSocket是应用层协议,是TCP/IP协议的子集,通过HTTP/1.1协议的101状态码进行握手。

客戶端连接

GET / HTTP/1.1

Host: localhost:8080

Origin: http://127.0.0.1:3000

Connection: Upgrade

Upgrade: websocket

Sec-WebSocket-Version: 13

Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==

服务器响应

HTTP/1.1 101 Switching Protocols

Connection:Upgrade

Upgrade: websocket

Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PT

Connection 必须设置 Upgrade,表示客户端希望连接升级

Upgrade 字段必须设置 Websocket,表示希望升级到 Websocket 协议。

Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。

Sec-WebSocket-Version 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均应当弃用。

9、WebSocket 事件

以下是 WebSocket 对象的相关事件。

事件 事件处理程序 描述

open Socket.onopen 连接建立时触发

message Socket.onmessage 客户端接收服务端数据时触发

error Socket.onerror 通信发生错误时触发

close Socket.onclose 连接关闭时触发

10、Netty线程模型

Netty 支持三种线程模型,分别是单线程模型,多线程模型,以及主从线程模型





实践

1、协议异常,断开连接,客户端重连

public class XXXXHandler extends SimpleChannelInboundHandler {

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{

log 一下异常信息


if(ctx.channel().isActive()){
ctx.close();
}


}}

2、不要在EventLoopGroup中做耗时任务,如线程wait/sleep操作、耗时的数据计算、DB查询等,放在业务线程池处理

3、ByteBuf分类的使用:

实际使用中在I/O通信线程的读写使用DirectByteBuf,而在后端消息业务中编解码中使用HeapByteBuf。

4、诊断 byteBuf泄漏,参见 http://netty.io/wiki/reference-counted-objects.html ,基本把编码过程中所有情况都列出,不再赘序。测试阶段中一般使用-Dio.netty.leakDetectionLevel=paranoid,基本可以把大部分泄漏问题代码检测出来。