Netty5.0用法
创建服务器端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class ServerHandler extends ChannelHandlerAdapter { /** * 当通道被调用,执行该方法 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 接收数据 String value = (String) msg; System.out.println("Server msg:" + value); // 回复给客户端 “您好!” String res = "好的..."; ctx.writeAndFlush(Unpooled.copiedBuffer(res.getBytes())); } } public class NettyServer { public static void main(String[] args) throws InterruptedException { System.out.println("服务器端已经启动...."); // 1.创建2个线程,一个负责接收客户端连接, 一个负责进行 传输数据 NioEventLoopGroup pGroup = new NioEventLoopGroup(); NioEventLoopGroup cGroup = new NioEventLoopGroup(); // 2. 创建服务器辅助类 ServerBootstrap b = new ServerBootstrap(); b.group(pGroup, cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024) // 3.设置缓冲区与发送区大小 .option(ChannelOption.SO_SNDBUF, 32 * 1024).option(ChannelOption.SO_RCVBUF, 32 * 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(new StringDecoder()); sc.pipeline().addLast(new ServerHandler()); } }); ChannelFuture cf = b.bind(8080).sync(); cf.channel().closeFuture().sync(); pGroup.shutdownGracefully(); cGroup.shutdownGracefully(); } }
创建客户端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class ClientHandler extends ChannelHandlerAdapter { /** * 当通道被调用,执行该方法 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 接收数据 String value = (String) msg; System.out.println("client msg:" + value); } } public class NettyClient { public static void main(String[] args) throws InterruptedException { System.out.println("客户端已经启动...."); // 创建负责接收客户端连接 NioEventLoopGroup pGroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(new StringDecoder()); sc.pipeline().addLast(new ClientHandler()); } }); ChannelFuture cf = b.connect("127.0.0.1", 8080).sync(); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("itmayiedu".getBytes())); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("itmayiedu".getBytes())); // 等待客户端端口号关闭 cf.channel().closeFuture().sync(); pGroup.shutdownGracefully(); } }
TCP粘包、拆包问题解决方案
什么是粘包/拆包
一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题。
下面可以看一张图,是客户端向服务端发送包:
1. 第一种情况,Data1和Data2都分开发送到了Server端,没有产生粘包和拆包的情况。
2. 第二种情况,Data1和Data2数据粘在了一起,打成了一个大的包发送到Server端,这个情况就是粘包。
3. 第三种情况,Data2被分离成Data2_1和Data2_2,并且Data2_1在Data1之前到达了服务端,这种情况就产生了拆包。
由于网络的复杂性,可能数据会被分离成N多个复杂的拆包/粘包的情况,所以在做TCP服务器的时候就需要首先解决拆包/
解决办法
消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
sc.pipeline().addLast(new FixedLengthFrameDecoder(10)); |
包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
ByteBuf buf = Unpooled.copiedBuffer("_mayi".getBytes()); sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf)); |
将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
序列化协议与自定义序列化协议
序列化定义
序列化(serialization)就是将对象序列化为二进制形式(字节数组),一般也将序列化称为编码(Encode),主要用于网络传输、数据持久化等;
反序列化(deserialization)则是将从网络、磁盘等读取的字节数组还原成原始对象,以便后续业务的进行,一般也将反序列化称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。
序列化协议“鼻祖”
我知道的第一种序列化协议就是Java默认提供的序列化机制,需要序列化的Java对象只需要实现 Serializable / Externalizable 接口并生成序列化ID,这个类就能够通过 ObjectInput 和 ObjectOutput 序列化和反序列化
但是Java默认提供的序列化有很多问题,主要有以下几个缺点:
无法跨语言:我认为这对于Java序列化的发展是致命的“失误”,因为Java序列化后的字节数组,其它语言无法进行反序列化。;
序列化后的码流太大::相对于目前主流的序列化协议,Java序列化后的码流太大;
序列化的性能差:由于Java序列化采用同步阻塞IO,相对于目前主流的序列化协议,它的效率非常差。
影响序列化性能的关键因素
序列化后的码流大小(网络带宽的占用);
序列化的性能(CPU资源占用);
是否支持跨语言(异构系统的对接和开发语言切换)。