引导Bootstrap和ChannelOption
- Bootstrap只能接受一个eventloopgroup,ServerBootstrap可以接受两个eventloopgroup
- 联想reactor模式,第一个group负责和serverChannel绑定,专门负责接受网络连接,第二个专门已接受的子channel进行网络读写
- 这两个合二为一也是可以的
- 同时NioEventLoopGroup是可以指定线程的个数的
- 所以之前介绍的三种反应器模式reactor都是可以实现的,根据要求自己配置就可以了
- bootStrap.childHandler(new ChannelInitializer(){}
- ChannelInitializer复制把一个或者多个handler添加到pipeline当中
ChannelOption
-
配置tcp选项
-
SO_BACKLOG:监听的时候可连接队列(等待队列)
1.建立tcp连接需要三次握手,服务器同一时间只能处理一个客户端的连接请求,此时如果又有一个客户端来请求连接,服务端就会生成一个队列,当前不能处理的请求就会放到服务队列里面排队,SO_BACKLOG就指明了这个队列的大小,一般在linux系统下是5个,jdk里面会改成50个。
2.三次握手成功以后,又会产生一个队列,称为已连接队列
已连接队列一般没有限制,如果服务器性能好,一台服务器支持的连接数达到上百万是没有问题的,
-
SO_REUSEADDR
1.假设tomcat启动后,占用了80端口,由于某些原因,tomcat挂掉了,但是操作系统并不会马上释放80端口,会保留一阵子,之后才会释放80端口;
2.所以马上启动80端口,可能会报错,80端口已经占用,指定这个参数后,就可以实现重用
-
SO_KEEPALIVE:保持tcp连接保活的参数,会定期发送保活的参数到对端,如果对端发送响应报文后,操作系统就会认为连接是存活的。
1.但是一般写网络程序很少用,因为tcp协议里面定义时间长度为2个小时,是网络层面的
2.所以一般是自己实现一个心跳机制,通过应用层来检测对方是否存活,相当于是应用层的
-
SO_RECBUF SO_SNDBUF:接受缓存区和发送缓存区
1.这两个参数分别用来调整这两个缓存区的大小,一般是4k-8k
2.这不是应用程序层面我们自己创建的buffer,这是操作系统自己实现的buffer
-
SO_LINGER:一个网络应用结束后,需要调用close()方法
1.但是可能发送缓存区里面还有数据没有发送完,这是很正常的,一般情况下,操作系统会尽量发缓存区的数据,发多少算多少,发送结果是未知的
2.启用这个参数后,就需要发送完缓存区的数据,才能关闭,相当于阻塞了close方法
-
TCP_NODELAY:与Nagle算法有关
1.缓存区,被写一条,就发一条,或者堆积到一定程度再发
2.缺省时采用批量发送,如果对网络延迟要求很高,启用该字段,就会禁用Nagle,也就是小数据、低延迟情况需要启用
-
TCP_CORK:与nodelay相反
-
ByteBuf
-
网络数据的基本单位总是字节。Java NIO 提供了ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。
-
Netty 的ByteBuffer 替代品是ByteBuf,一个强大的实现,既解决了JDK API 的局限性,又为网络应用程序的开发者提供了更好的API。
- ByteBuf 维护了两个不同的索引,名称以read 或者write 开头的ByteBuf 方法,将会推进其对应的索引,而名称以set 或者get 开头的操作则不会
- 使得不用再flip
- 使用read、write方法才会推动索引,使用get和set方法不会推动索引,就是之前讲nio的buffer中的绝对读和相对读
-
ByteBuf 的使用
-
堆缓冲区,分配在jvm堆上,分配和释放比较快,在网络读写上比直接缓存区慢
-
直接缓冲区,分配在直接内存上,分配和释放比较慢,在网络读写上比堆缓存区快
-
所以跟网络读写相关用直接缓冲区,如果跟业务相关相关,直接读写的,用堆缓冲区
-
复合缓冲区
http协议的消息头和消息体,消息头和消息体有各自的buffer,但是发送数据时视为一个整体
-
ByteBuf中的概念和API
-
分配—new一个ByteBuf
-
ByteBufAllocate:ctx.alloc()就可以拿到一个分配器实例
1.直接内存、堆、复合式的都可以实现
2.ByteBufAllocate是一个接口,实现类分为池化的和非池化的,池化的就已经生成好了,放到一个内存池中,速度更快,内存碎片更少,非池化的,临时new一个新的出来
-
Unpooled
1.这是一个工具类,实质上是通过上述的UnpooledByteBufAllocate
2.池化算法—jemalloc算法
3.Unpooled.copiedBuffer,这个方法可以把字符串转成ByteBuffer
-
-
随机访问/顺序访问/读写操作
-
可丢弃字节/可读字节/可写字节
-
readerIndex
1.已经读过的字节是可丢弃字节,discardReadBytes,丢弃并回收
2.但是会导致内存复制,一般情况不会使用,除非内存极其紧张
-
writeIndex
-
-
索引管理
- 调整索引也是可以的,进行索引的移动markReaderIndex、resetReaderIndex,读写都是可以重置的
-
查找操作
-
indexOf,在指定范围的所有位置,起始和结束位置
-
forEachByte,例如里面有回车换行符
ByteProcessor里面有相关的符号类
-
-
派生缓冲区
- 原生buffer是100字节,业务只需要看中间的20个字节
- netty中提供了slice从上面弄一个视图下来,视为一个单独的buffer,进行相关操作,内存实例上是一个,并没有复制出一个新的,改了派生的,也会修改原来的
- 只有ByteBuf.copy()才是复制出一个新的
-
引用计数
-
工具类
-
资源释放
- ReferenceCountUtil
解决粘包/半包问题
回顾我们的的Hello,Netty
修改后的客户端handler
-
package cn.enjoyedu.nettybasic.splicing.demo; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private AtomicInteger counter = new AtomicInteger(0); /*** 客户端读取到网络数据后的处理*/ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8) +"] and the counter is:"+counter.incrementAndGet()); } /*** 客户端被通知channel活跃后,做事*/ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf msg = null; String request = "Mark,Lison,King,James,Deer" + System.getProperty("line.separator"); for(int i=0;i<100;i++){ msg = Unpooled.buffer(request.length()); msg.writeBytes(request.getBytes()); ctx.writeAndFlush(msg); } } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
修改后的服务器handler
-
package cn.enjoyedu.nettybasic.splicing.demo; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/25 * 类说明:自己的业务处理 */ @ChannelHandler.Sharable public class EchoServerHandler extends ChannelInboundHandlerAdapter { private AtomicInteger counter = new AtomicInteger(0); /*** 服务端读取到网络数据后的处理*/ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf)msg; String request = in.toString(CharsetUtil.UTF_8); System.out.println("Server Accept["+request +"] and the counter is:"+counter.incrementAndGet()); String resp = "Hello,"+request+". Welcome to Netty World!" + System.getProperty("line.separator"); ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes())); } // /*** 服务端读取完成网络数据后的处理*/ // @Override // public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { // ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); // } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
客户端对比之前的不是只发送hello netty,发送100次,每次还有回车换行符
-
服务器端希望区别出回车换行符,同时统计发送的次数,
- 实际只收到两次,希望收到是100次,这就是粘包、半包问题
什么是TCP粘包半包?
- 上面的例子就是典型的tcp粘包、半包
具体表现
-
假如客户端发送d1和d2两个报文
- 1.服务端收到就是d1和d2两个独立的报文
- 2.服务器收到的d1和d2粘到一起了,就是粘包问题
- 3.d2被拆分成两份,第一份和d1粘在一起,发生拆包和粘包
- 4.d1被拆分成两份,第二份和d2粘在一起,发生拆包和粘包
-
注意这不是tcp协议的问题,因为消息如何界定应该是由应用层来定义的,udp是一次性发送的,不会拆包,所以不会有粘包问题
TCP粘包/半包发生的原因
-
应用程序写入数据的字节大小大于套接字发送缓冲区的大小
- 发送1m数据,缓存区只有4k,就必须要分包
-
进行MSS大小的TCP分段
- MSS是最大报文段的意思,以太网是1460个字节,到了tcp这一层,如果超过了,操作系统就会进行分段
-
以太网的payload大于MTU进行IP分片
- 在数据链路层,MTU是最大传输单元,网卡规定一次就只能发送这么多字节,如果1500个字节,操作系统就会进行分片
解决粘包半包问题
-
1.增加一个分隔符
-
最主要的是回车换行符,对于文本编辑是最方便的
-
以回车换行符,netty直接有对应的handler
-
package cn.enjoyedu.nettybasic.splicing.linebase; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import java.net.InetSocketAddress; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class LineBaseEchoClient { private final String host; public LineBaseEchoClient(String host) { this.host = host; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { final Bootstrap b = new Bootstrap();;/*客户端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/ .remoteAddress(new InetSocketAddress(host,LineBaseEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/ .handler(new ChannelInitializerImp()); ChannelFuture f = b.connect().sync(); System.out.println("已连接到服务器....."); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { // 修改点LineBasedFrameDecoder ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new LineBaseClientHandler()); } } public static void main(String[] args) throws InterruptedException { new LineBaseEchoClient("127.0.0.1").start(); } }
-
LineBasedFrameDecoder,解码
-
package cn.enjoyedu.nettybasic.splicing.linebase; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import java.net.InetSocketAddress; /** * 作者:Mark * 创建日期:2018/08/25 * 类说明: */ public class LineBaseEchoServer { public static final int PORT = 9998; public static void main(String[] args) throws InterruptedException { LineBaseEchoServer lineBaseEchoServer = new LineBaseEchoServer(); System.out.println("服务器即将启动"); lineBaseEchoServer.start(); } public void start() throws InterruptedException { final LineBaseServerHandler serverHandler = new LineBaseServerHandler(); EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/ .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/ /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel, 所以下面这段代码的作用就是为这个子channel增加handle*/ .childHandler(new ChannelInitializerImp()); ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/ System.out.println("服务器启动完成,等待客户端的连接和数据....."); f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/ } finally { group.shutdownGracefully().sync();/*优雅关闭线程组*/ } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { // 修改点 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new LineBaseServerHandler()); } } }
-
package cn.enjoyedu.nettybasic.splicing.linebase; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class LineBaseClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private AtomicInteger counter = new AtomicInteger(0); /*** 客户端读取到网络数据后的处理*/ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8) +"] and the counter is:"+counter.incrementAndGet()); ctx.close(); } /*** 客户端被通知channel活跃后,做事*/ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf msg = null; String request = "Mark,Lison,King,James,Deer" + System.getProperty("line.separator"); for(int i=0;i<10;i++){ msg = Unpooled.buffer(request.length()); msg.writeBytes(request.getBytes()); ctx.writeAndFlush(msg); } } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
channelRead0中的ctx.close()需要注释掉,关早了
-
自定义分隔符----应用层是文本协议(FTP)
首先将定义的分割符转成byteBuffer,并增加一个新的解码器DelimiterBasedFrameDecoder
-
package cn.enjoyedu.nettybasic.splicing.delimiter; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import java.net.InetSocketAddress; /** * 作者:Mark * 创建日期:2018/08/25 * 类说明: */ public class DelimiterEchoServer { public static final String DELIMITER_SYMBOL = "@~"; public static final int PORT = 9997; public static void main(String[] args) throws InterruptedException { DelimiterEchoServer delimiterEchoServer = new DelimiterEchoServer(); System.out.println("服务器即将启动"); delimiterEchoServer.start(); } public void start() throws InterruptedException { final DelimiterServerHandler serverHandler = new DelimiterServerHandler(); EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/ .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/ /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel, 所以下面这段代码的作用就是为这个子channel增加handle*/ .childHandler(new ChannelInitializerImp()); ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/ System.out.println("服务器启动完成,等待客户端的连接和数据....."); f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/ } finally { group.shutdownGracefully().sync();/*优雅关闭线程组*/ } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER_SYMBOL.getBytes()); ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter)); ch.pipeline().addLast(new DelimiterServerHandler()); } } }
服务器读取数据
-
package cn.enjoyedu.nettybasic.splicing.delimiter; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/25 * 类说明:自己的业务处理 */ @ChannelHandler.Sharable public class DelimiterServerHandler extends ChannelInboundHandlerAdapter { private AtomicInteger counter = new AtomicInteger(0); /*** 服务端读取到网络数据后的处理*/ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf) msg; // 指定编码格式 String request = in.toString(CharsetUtil.UTF_8); System.out.println("Server Accept["+request +"] and the counter is:"+counter.incrementAndGet()); // System.getProperty("line.separator")改成了DelimiterEchoServer.DELIMITER_SYMBOL String resp = "Hello,"+request+". Welcome to Netty World!" + DelimiterEchoServer.DELIMITER_SYMBOL; ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes())); } /*** 服务端读取完成网络数据后的处理*/ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
System.getProperty(“line.separator”)改成了DelimiterEchoServer.DELIMITER_SYMBOL
客户端handler
-
package cn.enjoyedu.nettybasic.splicing.delimiter; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class DelimiterClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private AtomicInteger counter = new AtomicInteger(0); /*** 客户端读取到网络数据后的处理*/ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8) +"] and the counter is:"+counter.incrementAndGet()); } /*** 客户端被通知channel活跃后,做事*/ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf msg = null; String request = "Mark,Lison,King,James,Deer" + DelimiterEchoServer.DELIMITER_SYMBOL; for(int i=0;i<10;i++){ msg = Unpooled.buffer(request.length()); msg.writeBytes(request.getBytes()); ctx.writeAndFlush(msg); } } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
客户端client
-
package cn.enjoyedu.nettybasic.splicing.delimiter; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import java.net.InetSocketAddress; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class DelimiterEchoClient { private final String host; public DelimiterEchoClient(String host) { this.host = host; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { final Bootstrap b = new Bootstrap();;/*客户端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/ .remoteAddress(new InetSocketAddress(host,DelimiterEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/ .handler(new ChannelInitializerImp()); ChannelFuture f = b.connect().sync(); System.out.println("已连接到服务器....."); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ByteBuf delimiter = Unpooled.copiedBuffer( DelimiterEchoServer.DELIMITER_SYMBOL.getBytes()); ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter)); ch.pipeline().addLast(new DelimiterClientHandler()); } } public static void main(String[] args) throws InterruptedException { new DelimiterEchoClient("127.0.0.1").start(); } }
消息定长------协商好消息长度后可以用这种模式,简单方便,物联网企业对性能要求不高的情况下
- 跟之前类似,也是需要加一个handler来处理定长
服务器端handler
-
package cn.enjoyedu.nettybasic.splicing.fixed; import cn.enjoyedu.nettybasic.splicing.delimiter.DelimiterEchoServer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/25 * 类说明:自己的业务处理 */ @ChannelHandler.Sharable public class FixedLengthServerHandler extends ChannelInboundHandlerAdapter { private AtomicInteger counter = new AtomicInteger(0); /*** 服务端读取到网络数据后的处理*/ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf) msg; String request = in.toString(CharsetUtil.UTF_8); System.out.println("Server Accept["+request +"] and the counter is:"+counter.incrementAndGet()); // 修改了应答内容 // 固定发送FixedLengthEchoServer.RESPONSE ctx.writeAndFlush(Unpooled.copiedBuffer( FixedLengthEchoServer.RESPONSE.getBytes())); } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
修改了应答内容,固定发送FixedLengthEchoServer.RESPONSE
服务器client
-
package cn.enjoyedu.nettybasic.splicing.fixed; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import java.net.InetSocketAddress; /** * 作者:Mark * 创建日期:2018/08/25 * 类说明: */ public class FixedLengthEchoServer { public static final String RESPONSE = "Welcome to Netty!"; public static final int PORT = 9996; public static void main(String[] args) throws InterruptedException { FixedLengthEchoServer fixedLengthEchoServer = new FixedLengthEchoServer(); System.out.println("服务器即将启动"); fixedLengthEchoServer.start(); } public void start() throws InterruptedException { final FixedLengthServerHandler serverHandler = new FixedLengthServerHandler(); EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/ .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/ /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel, 所以下面这段代码的作用就是为这个子channel增加handle*/ .childHandler(new ChannelInitializerImp()); ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/ System.out.println("服务器启动完成,等待客户端的连接和数据....."); f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/ } finally { group.shutdownGracefully().sync();/*优雅关闭线程组*/ } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new FixedLengthFrameDecoder( FixedLengthEchoClient.REQUEST.length())); ch.pipeline().addLast(new FixedLengthServerHandler()); } } }
-
FixedLengthFrameDecoder
客户端client
-
package cn.enjoyedu.nettybasic.splicing.fixed; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import java.net.InetSocketAddress; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class FixedLengthEchoClient { public final static String REQUEST = "Mark,Lison,Peter,James,Deer"; private final String host; public FixedLengthEchoClient(String host) { this.host = host; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup();/*线程组*/ try { final Bootstrap b = new Bootstrap();;/*客户端启动必须*/ b.group(group)/*将线程组传入*/ .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/ .remoteAddress(new InetSocketAddress(host,FixedLengthEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/ .handler(new ChannelInitializerImp()); ChannelFuture f = b.connect().sync(); System.out.println("已连接到服务器....."); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } private static class ChannelInitializerImp extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new FixedLengthFrameDecoder( FixedLengthEchoServer.RESPONSE.length())); ch.pipeline().addLast(new FixedLengthClientHandler()); } } public static void main(String[] args) throws InterruptedException { new FixedLengthEchoClient("127.0.0.1").start(); } }
-
固定长度FixedLengthEchoServer.RESPONSE.length()
客户端handler
-
package cn.enjoyedu.nettybasic.splicing.fixed; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import java.util.concurrent.atomic.AtomicInteger; /** * 作者:Mark * 创建日期:2018/08/26 * 类说明: */ public class FixedLengthClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private AtomicInteger counter = new AtomicInteger(0); /*** 客户端读取到网络数据后的处理*/ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8) +"] and the counter is:"+counter.incrementAndGet()); } /*** 客户端被通知channel活跃后,做事*/ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf msg = null; for(int i=0;i<10;i++){ msg = Unpooled.buffer(FixedLengthEchoClient.REQUEST.length()); msg.writeBytes(FixedLengthEchoClient.REQUEST.getBytes()); ctx.writeAndFlush(msg); } } /*** 发生异常后的处理*/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
把消息里面做点动作,消息头+消息体,消息头告诉消息体有多长
-
最常用、最灵活的一种
-
LengthFieldBasedFrameDecoder