Netty介绍:
Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
换句话说,Netty是一个NIO框架,使用它可以简单快速地开发网络应用程序,比如客户端和服务端的协议。Netty大大简化了网络程序的开发过程比如TCP和UDP的 Socket的开发。
“快速和简单”并不意味着应用程序会有难维护和性能低的问题,Netty是一个精心设计的框架,它从许多协议的实现中吸收了很多的经验比如FTP、SMTP、HTTP、许多二进制和基于文本的传统协议,Netty在不降低开发效率、性能、稳定性、灵活性情况下,成功地找到了解决方案。
有一些用户可能已经发现其他的一些网络框架也声称自己有同样的优势,所以你可能会问是Netty和它们的不同之处。答案就是Netty的哲学设计理念。Netty从第一天开始就为用户提供了用户体验最好的API以及实现设计。正是因为Netty的设计理念,才让我们得以轻松地阅读本指南并使用Netty。
接下来,我们看下Client端的代码实现:
1 package ruizhan.hjf.netty; 2 3 import io.netty.bootstrap.Bootstrap; 4 import io.netty.buffer.Unpooled; 5 import io.netty.channel.ChannelFuture; 6 import io.netty.channel.ChannelInitializer; 7 import io.netty.channel.ChannelOption; 8 import io.netty.channel.EventLoopGroup; 9 import io.netty.channel.nio.NioEventLoopGroup; 10 import io.netty.channel.socket.SocketChannel; 11 import io.netty.channel.socket.nio.NioSocketChannel; 12 /** 13 * Netty客户端的程序 14 * @author huangjianfei 15 */ 16 public class Client { 17 /*IP地址*/ 18 static final String HOST = System.getProperty("host", "127.0.0.1"); 19 /*端口号*/ 20 static final int PORT1 = Integer.parseInt(System.getProperty("port", "8765")); 21 22 static final int PORT2 = Integer.parseInt(System.getProperty("port", "8764")); 23 24 public static void main(String[] args) throws Exception { 25 EventLoopGroup workgroup = new NioEventLoopGroup(); 26 Bootstrap b = new Bootstrap();//客户端 27 b.group(workgroup) 28 .channel(NioSocketChannel.class)//客户端 -->NioSocketChannel 29 .option(ChannelOption.SO_KEEPALIVE, true) 30 .handler(new ChannelInitializer<SocketChannel>() {//handler 31 @Override 32 protected void initChannel(SocketChannel sc) throws Exception { 33 sc.pipeline().addLast(new ClientHandler()); 34 } 35 }); 36 //创建异步连接 可添加多个端口 37 ChannelFuture cf1 = b.connect(HOST, PORT1).sync(); 38 ChannelFuture cf2 = b.connect(HOST, PORT2).sync(); 39 40 //buf 41 //client向server端发送数据 Buffer形式 42 cf1.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty".getBytes())); 43 cf2.channel().writeAndFlush(Unpooled.copiedBuffer("hello world".getBytes())); 44 45 46 cf1.channel().closeFuture().sync(); 47 cf2.channel().closeFuture().sync(); 48 49 workgroup.shutdownGracefully(); 50 } 51 }
Servler端代码实现:
1 package ruizhan.hjf.netty; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.buffer.Unpooled; 5 import io.netty.channel.ChannelFuture; 6 import io.netty.channel.ChannelInitializer; 7 import io.netty.channel.ChannelOption; 8 import io.netty.channel.EventLoopGroup; 9 import io.netty.channel.nio.NioEventLoopGroup; 10 import io.netty.channel.socket.SocketChannel; 11 import io.netty.channel.socket.nio.NioServerSocketChannel; 12 /** 13 * Netty实现的服务端程序 14 * @author huangjianfei 15 */ 16 public class Server 17 { 18 /*端口号*/ 19 static final int PORT1 = Integer.parseInt(System.getProperty("port", "8765")); 20 21 static final int PORT2 = Integer.parseInt(System.getProperty("port", "8764")); 22 23 public static void main(String[] args) 24 { 25 EventLoopGroup bossGroup = null; 26 EventLoopGroup workerGroup = null; 27 ServerBootstrap b = null; 28 try{ 29 //1:第一个线程组是用于接收Client连接的 30 bossGroup = new NioEventLoopGroup(); //(1) 31 //2:第二个线程组是用于实际的业务处理操作的 32 workerGroup = new NioEventLoopGroup(); 33 //3:创建一个启动NIO服务的辅助启动类ServerBootstrap 就是对我们的Server进行一系列的配置 34 b = new ServerBootstrap();//(2) 35 //4:绑定两个线程组 36 b.group(bossGroup, workerGroup) 37 //5:需要指定使用NioServerSocketChannel这种类型的通道 38 .channel(NioServerSocketChannel.class)//(3) 服务端 -->NioServerSocketChannel 39 //6:一定要使用childHandler 去绑定具体的事件处理器 40 .childHandler(new ChannelInitializer<SocketChannel>() //(4) childHandler 41 { 42 @Override 43 protected void initChannel(SocketChannel sc) throws Exception 44 { 45 //7:将自定义的serverHandler加入到管道中去(多个) 46 sc.pipeline().addLast(new ServerHandler());//handler中实现真正的业务逻辑 47 // sc.pipeline().addLast(new ServerHandler()); 48 // sc.pipeline().addLast(new ServerHandler()); 49 } 50 }) 51 /** 52 * 服务器端TCP内核模块维护两个队列,我们称之为A,B吧 53 * 客户端向服务端connect的时候,会发送带有SYN标志的包(第一次握手) 54 * 服务端收到客户端发来的SYN时,向客户端发送SYN ACK确认(第二次握手) 55 * 此时TCP内核模块把客户端连接加入到A队列中,最后服务端收到客户端发来的ACK时(第三次握手) 56 * TCP内核模块把客户端连接从A队列移到B队列,连接成功,应用程序的accept会返回 57 * 也就是说accept从B队列中取出完成三次握手的连接 58 * A队列和B队列的长度之和是backLog,当A,B队列的长度之和大于backLog时,新连接将会被TCP内核拒绝 59 * 所以,如果backLog过小,可能会出现accept速度跟不上,A,B队列满了,导致新的客户端无法连接, 60 * 要注意的是,backLog对程序支持的连接数并无影响,backLog影响的只是还没有被accept取出的连接 61 */ 62 //8:设置TCP连接的缓冲区 63 .option(ChannelOption.SO_BACKLOG, 128)//(5) 64 // .option(ChannelOption.SO_SNDBUF, 32*1024) //设置发送缓冲大小 65 // .option(ChannelOption.SO_RCVBUF, 32*1024) //设置接收缓冲大小 66 //9:保持连接 67 .childOption(ChannelOption.SO_KEEPALIVE, true);//(6) 68 //10:绑定指定的端口 进行监听 69 //此处端口号先写死 也可以绑定多个端口 70 ChannelFuture cf2= b.bind(PORT1).sync(); // (7) 71 72 ChannelFuture cf3= b.bind(PORT2).sync(); // (7) 绑定多个端口 73 74 //Thread.sleep(10000); 75 cf2.channel().closeFuture().sync(); //异步等待关闭 76 cf3.channel().closeFuture().sync(); //异步等待关闭 77 78 }catch(Exception e){ 79 e.printStackTrace(); 80 }finally{ 81 workerGroup.shutdownGracefully(); 82 bossGroup.shutdownGracefully(); 83 } 84 } 85 }
接下来,就是真正去实现数据传输的业务逻辑层代码的实现,在这里也就是ClientHanlder和ServlerHandler:
1 package ruizhan.hjf.netty; 2 3 import io.netty.buffer.ByteBuf; 4 import io.netty.buffer.Unpooled; 5 import io.netty.channel.ChannelHandlerAdapter; 6 import io.netty.channel.ChannelHandlerContext; 7 import io.netty.util.ReferenceCountUtil; 8 9 /** 10 * 客户端业务处理类 11 * (编写主要的业务逻辑) 12 * @author huangjianfei 13 */ 14 public class ClientHandler extends ChannelHandlerAdapter 15 { 16 // ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。 17 // 请记住处理器的职责是释放所有传递到处理器的引用计数对象。 18 @Override 19 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 20 { 21 try{ 22 //do something 23 //接收服务端发来的数据 ByteBuf 24 ByteBuf buf = (ByteBuf)msg; 25 //创建一个和buf一样长度的字节空数组 26 byte[] data = new byte[buf.readableBytes()]; 27 //将buf中的数据读取到data数组中 28 buf.readBytes(data); 29 //将data数组惊醒包装 以String格式输出 30 String response = new String(data,"utf-8"); 31 System.out.println("client :"+response); 32 33 //以上代码是接收服务端发来的反馈数据// 34 35 ctx.close(); 36 }finally{ 37 // Discard the received data silently. 38 ReferenceCountUtil.release(msg); 39 } 40 } 41 42 @Override 43 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception 44 { 45 // Close the connection when an exception is raised. 46 cause.printStackTrace(); 47 ctx.close(); 48 } 49 }
1 package ruizhan.hjf.netty; 2 3 import io.netty.buffer.ByteBuf; 4 import io.netty.buffer.Unpooled; 5 import io.netty.channel.ChannelFutureListener; 6 import io.netty.channel.ChannelHandlerAdapter; 7 import io.netty.channel.ChannelHandlerContext; 8 import io.netty.util.ReferenceCountUtil; 9 10 /** 11 * 服务端业务处理类 12 * (编写主要的业务逻辑) 13 * @author huangjianfei 14 */ 15 public class ServerHandler extends ChannelHandlerAdapter 16 { 17 18 /** 19 * 每当从客户端收到新的数据时,这个方法会在收到消息时被调用 20 * ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。 21 * 请记住处理器的职责是释放所有传递到处理器的引用计数对象。 22 */ 23 @Override 24 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 25 { 26 try{ 27 //do something 28 //接收客户端发送的数据 ByteBuf 29 ByteBuf buf = (ByteBuf)msg; 30 //创建一个和buf长度一样的空字节数组 31 byte[] data = new byte[buf.readableBytes()]; 32 //将buf中的数据读取到data数组中 33 buf.readBytes(data); 34 //将data数据包装成string输出 35 String request = new String(data,"utf-8"); 36 System.out.println("server :"+request); 37 38 //以上代码是接收客户端信息// 39 40 //server端向client发送反馈数据 41 //如果是绑定了多个端口 那么都会进行发送 42 ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes())) 43 .addListener(ChannelFutureListener.CLOSE);//添加监听 当服务端向客户端发送完数据后,关闭connect连接 44 /** 45 * ChannelFutureListener,当一个写请求完成时通知并且关闭Channel 46 * 加上监听 意味着服务端回送数据到客户端时 连接关闭(短连接) 47 * 不加监听 意味着客户端与服务端一直保持连接状态(长连接) 48 */ 49 50 51 ctx.close(); 52 }finally{ 53 // Discard the received data silently. 54 ReferenceCountUtil.release(msg); 55 } 56 } 57 58 /** 59 * exceptionCaught()事件处理方法是当出现Throwable对象才会被调用 60 * 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时 61 */ 62 @Override 63 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception 64 { 65 // Close the connection when an exception is raised. 66 cause.printStackTrace(); 67 ctx.close(); 68 } 69 70 }
以上是Netty的基础入门实现,详见并发编程网,http://ifeve.com/netty5-user-guide/