netty基本功能使用
1 Netty简介
Netty是最流行的NIO框架,它的健壮性、功能、性能、可定制性和可扩展性在同类框架都是首屈一指的。它已经得到成百上千的商业/商用项目验证,如Hadoop的RPC框架Avro、RocketMQ以及主流的分布式通信框架Dubbox等等。
Netty是基于Java NIO client-server的网络应用框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来开发网络应用程序,这种新的方式使它很容易使用和具有很强的扩展性。Netty的内部实现是很复杂的,但是Netty提供了简单易用的API从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。
网络应用程序通常需要有较高的可扩展性,无论是Netty还是其他的基于Java Nio的框架,都会提供可扩展性的解决方案。Netty中一个关键组成部分是它的异步特性,本片文章将讨论同步(阻塞)和异步(非阻塞)的IO来说明为什么使用异步代码解
决扩展性问题以及如何使用异步。
2 NIO的通信步骤
①创建ServerSocketChannel,为其配置非阻塞模式。
②绑定监听,配置TCP参数,录入backlog大小等。
③创建一个独立的IO线程,用于轮询多路复用器Selector。
④创建Selector,将之前创建的ServerSocketChannel注册到Selector上,并设置监听标识位SelectionKey.OP_ACCEPT。
⑤启动IO线程,在循环体中执行Selector.select()方法,轮询就绪的通道。
⑥当轮询到处于就绪状态的通道时,需要进行操作位判断,如果是ACCEPT状态,说明是新的客户端接入,则调用accept方法接收新的客户端。
⑦设置新接入客户端的一些参数,如非阻塞,并将其继续注册到Selector上,设置监听标识位等。
⑧如果轮询的通道标识位是READ,则进行读取,构造Buffer对象等。
⑨更细节的问题还有数据没发送完成继续发送的问题......
3 Netty通信的步骤
①创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信的读写。
②创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。
③创建一个用于实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式以及实际处理数据的接口。
④绑定端口,执行同步阻塞方法等待服务器端启动即可。
//服务端代码 package netty.test; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler; public class NettyServer { private int port; public NettyServer(int port) { this.port = port; } public void run() { EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理服务器端接收客户端连接 EventLoopGroup workerGroup = new NioEventLoopGroup(); //进行网络通信(读写) try { ServerBootstrap bootstrap = new ServerBootstrap(); //辅助工具类,用于服务器通道的一系列配置 bootstrap.group(bossGroup, workerGroup) //绑定两个线程组 .channel(NioServerSocketChannel.class) //指定NIO的模式 .childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline() .addLast(MarshallingCodeCFactory.buildMarshallingDecoder())//jboss解码 .addLast(MarshallingCodeCFactory.buildMarshallingEncoder())//jboss编码 .addLast(new ReadTimeoutHandler(5))//客户端5s没上送消息自动断开通道,短连接配合 .addLast(new ServerHandler(1));//业务处理者添加到pipeline最后 } }) /** * 对于ChannelOption.SO_BACKLOG的解释: * 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端 * 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到 * 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept * 从B队列中取出完成了三次握手的连接。 * A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。 * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的 * 连接数并无影响,backlog影响的只是还没有被accept取出的连接 */ .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区 .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小 .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小 .childOption(ChannelOption.SO_KEEPALIVE, false); //短连接 ChannelFuture future = bootstrap.bind(port).sync(); //连接本机服务器 future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) { new NettyServer(8081).run(); } } class ServerHandler extends ChannelInboundHandlerAdapter { private Integer id; public ServerHandler(){} public ServerHandler(Integer id){ this.id = id; } @Override public void channelActive(final ChannelHandlerContext ctx) { //链接刚刚建立好时会触发一次该方法 final ByteBuf time = ctx.alloc().buffer(4); //初始化缓冲区,制定最初大小4个字节 time.writeBytes("testttttttttt".getBytes()); final ChannelFuture f = ctx.writeAndFlush(time); //发送个通知到客户端,该任务是一个futrue f.addListener(new ChannelFutureListener() {//为该futrue添加一个监听器,执行完时关闭客户端链接 @Override public void operationComplete(ChannelFuture future) { if(false) ctx.close(); //关闭客户端链接 } }); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//接受客户端信息 //do something msg ByteBuf buf = (ByteBuf)msg; byte[] data = new byte[buf.readableBytes()]; buf.readBytes(data); String request = new String(data, "utf-8"); System.out.println("server"+id+" receive request: " + request); //写给客户端 String response = "response:"+request; ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes())); //.addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//异常处理方法 System.out.println("server found exception"); cause.printStackTrace(); ctx.close(); } }
//客户端 package netty.test; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.ReferenceCountUtil; public class NettyClient { public static void main(String[] args) throws InterruptedException { EventLoopGroup workerGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new ClientHandler()); } }); ChannelFuture future = bootstrap.connect("10.1.0.127", 10086).sync(); String data = "{\"items\":{\"longitude\":114.27900999999999,\"terminalNumber\":\"352016900000001\",\"speed\":90,\"time\":1529907551,\"command\":\"\",\"sequence\":10263,\"status\":15,\"processCode\":\"OBD_PARASET_1\",\"terminalType\":\"OBD\",\"session\":1,\"latitude\":24.4188915,\"versionNo\":\"\",\"bearing\":242},\"indexes\":[\"sequence\",\"processCode\",\"terminalType\",\"terminalNumber\",\"time\",\"session\",\"latitude\",\"longitude\",\"speed\",\"bearing\",\"command\",\"versionNo\",\"status\"],\"sessionId\":null}"; future.channel().writeAndFlush(Unpooled.copiedBuffer(data.getBytes())); future.channel().closeFuture().sync(); workerGroup.shutdownGracefully(); } } class ClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//接收服务器返回 try { ByteBuf buf = (ByteBuf) msg; byte[] data = new byte[buf.readableBytes()]; buf.readBytes(data); System.out.println("Server response:" + new String(data).trim()); } finally { ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//异常处理 System.out.println("client found Exception"); cause.printStackTrace(); ctx.close(); } }
4 TCP粘包、拆包问题
TCP是一个“流”协议,所谓流就是没有界限的遗传数据。大家可以想象一下,如果河水就好比数据,他们是连成一片的,没有分界线,
TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的具体情况进行包的划分,也就是说,在业务上一个完整的包可能会被
TCP分成多个包进行发送,也可能把多个小包封装成一个大的数据包发送出去,这就是所谓的粘包/拆包问题。
Netty中解决TCP粘包/拆包的方法:
①分隔符类:DelimiterBasedFrameDecoder(自定义分隔符)
②定长:FixedLengthFrameDecoder


更多的基本功能:如编解码、消息压缩等:https://blog.csdn.net/haoyuyang/article/details/53243785
posted on 2018-10-11 14:16 javaGreenHand。。。 阅读(55) 评论(0) 收藏 举报
浙公网安备 33010602011771号