Netty系列之一:回显服务端和客户端 转自http://czj4451.iteye.com/blog/2106590

Netty是一款基于Java NIO的框架,能够建立通道、 
处理事件、编解码和异常处理等,为上层应用提供了清晰、简洁的开发接口:减少用户的编码和错误,使应用开发者能够把注意力集中在业务逻辑上。 

下面以回显功能为例: 

一、服务端: 

1. 实例化引导类 

抽象类为AbstractBootstrap,服务端使用ServerBootstrap: 
Java代码   收藏代码
  1. ServerBootstrap b = new ServerBootstrap();  


2. 设置参数 

Netty将EventLoopGroup, Channel, Address, Handler以及其它配置都放到了AbstractBootstrap中,统一设置: 


a. 设置事件组 

Netty是基于事件处理的,EventLoopGroup是接口,实现类有NioEventLoopGroup和OioEventLoopGroup (Old IO即Java IO) 等: 

Java代码   收藏代码
  1. EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接受连接事件组  
  2. EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理每个连接业务事件组  
  3. b.group(bossGroup, workerGroup); // 可以只使用一个group,分成两个group的好处是:业务耗时较长导致阻塞时,不会对接受连接造成影响。  


b. 设置通道类型 

接口为ServerSocketChannel,实现类有NioServerSocketChannel和OioServerSocketChannel。这里使用NioServerSocketChannel 
Java代码   收藏代码
  1. b.channel(NioServerSocketChannel.class);  


c. 设置服务启动绑定的地址 

传入一个SocketAddress实例,指定端口即可(如8080): 
Java代码   收藏代码
  1. b.localAddress(new InetSocketAddress(8080))  

当然,也可以在真正绑定的时候设置: 
Java代码   收藏代码
  1. ChannelFuture f = b.bind(new InetSocketAddress(8080)).sync();  

调用sync()会等待前面的方法执行完毕,后面会有很多这样的写法。 
ChannelFuture就是Channel的Future类,可以拿到执行结果。 

d. 设置childHandler 

实现ChannelInitializer接口的initChannel方法,将处理器(业务逻辑)加到通道管道的末尾: 
Java代码   收藏代码
  1. b.childHandler(new ChannelInitializer<SocketChannel>() {  
  2.     @Override  
  3.     protected void initChannel(SocketChannel ch) throws Exception {  
  4.         ch.pipeline().addLast(new EchoServerHandler());  
  5.     }  
  6. });  


3. 绑定操作 

和Socket的绑定类似,如果地址已经通过localAddress设定,这里就可以调用无参方法: 
Java代码   收藏代码
  1. ChannelFuture f = b.bind().sync();  


4. 等待结束 
Java代码   收藏代码
  1. f.channel().closeFuture().sync();  


5. 最终 

在finally块中关闭事件组以及线程池等资源: 
Java代码   收藏代码
  1. group.shutdownGracefully().sync();  



二、回显客户端: 

和服务端大体类似。不同的地方: 

1'. 设置客户端引导类: 
Java代码   收藏代码
  1. Bootstrap b = new Bootstrap();  


2'. 设置参数: 

a'. 设置事件组 

一个客户端只有一个通道,一个group就够了: 

Java代码   收藏代码
  1. EventLoopGroup group = new NioEventLoopGroup();  
  2. b.group(group);  


b'. 设置通道类型 

根据使用要求,也可以使用其它类型的客户端通道,如OioSocketChannel: 
Java代码   收藏代码
  1. b.channel(NioSocketChannel.class);  


c'. 设置连接地址: 

因为是客户端,需要指定连接的服务端主机和端口: 
Java代码   收藏代码
  1. b.remoteAddress(new InetSocketAddress(host, port));  


d'. 设置事件处理类: 

注意这里的方法是handler 
Java代码   收藏代码
  1. b.handler(new ChannelInitializer<SocketChannel>() {  
  2.     @Override  
  3.     protected void initChannel(SocketChannel ch) throws Exception {  
  4.         ch.pipeline().addLast(new EchoClientHandler());  
  5.     }  
  6. });  


3'. 连接: 

这里是连接,而不是绑定。 

Java代码   收藏代码
  1. ChannelFuture f = b.connect().sync();  


如果远程地址没有在remoteAddress中设定,需要在连接时设置: 
Java代码   收藏代码
  1. ChannelFuture f = b.connect("localhost"8080);  


三、事件处理类 

EchoServerHandler,EchoClientHandler都是先往引导程序注册,事件发生时触发相应处理方法: 

i. 服务器事件处理类 

EchoServerHandler扩展io.netty.channel. ChannelInboundHandlerAdapter类,重写下面三个方法,当然,可以根据需要重写更多的方法: 

channelRead: 服务端收到客户端发来的数据 

channelReadComplete: 服务端读取客户端数据完毕 

exceptionCaught: 发生异常,比如客户端关闭连接时 

ii. 客户端事件处理类 

EchoClientHandler扩展io.netty.channel. SimpleChannelInboundHandler类,重写下面三个方法: 

channelActive: 连接已建立,这时可以向服务端发送数据 

channelRead0: 服务端向客户端发送数据,可以读取 

exceptionCaught: 发生异常,比如服务端关闭连接时 

四、代码 

回显服务端 

Java代码   收藏代码
  1. public class EchoServer {  
  2.   
  3.     private final int port; // 服务端绑定端口  
  4.   
  5.     public EchoServer(int port) {  
  6.         this.port = port;  
  7.     }  
  8.   
  9.     public void start() throws Exception {  
  10.         EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接受连接事件组  
  11.         EventLoopGroup workerGroup = new NioEventLoopGroup(); // 每个连接业务处理事件组  
  12.   
  13.         try {  
  14.             ServerBootstrap b = new ServerBootstrap(); // 引导器  
  15.             // 指定事件组,通道、绑定地址、业务处理器  
  16.             b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)  
  17.                     .localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() { // 虽然EchoServerHandler和ChannelInitializer都是ChannelHandler的实现类,但这里不能直接传入EchoServerHandler,否则会导致业务处理器无法使用  
  18.   
  19.                         @Override  
  20.                         protected void initChannel(SocketChannel ch) throws Exception {  
  21.                             // 将业务处理器加到通道管理线(处理器队列)的末尾  
  22.                             ch.pipeline().addLast(new EchoServerHandler());  
  23.                         }  
  24.                     });  
  25.   
  26.             ChannelFuture f = b.bind().sync(); // 绑定指定端口  
  27.             System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());  
  28.             f.channel().closeFuture().sync();  
  29.         } finally {  
  30.             bossGroup.shutdownGracefully().sync(); // 释放资源和线程池  
  31.         }  
  32.     }  
  33.   
  34.     public static void main(String[] args) throws Exception {  
  35.         args = new String[1];  
  36.         args[0] = "8180";  
  37.   
  38.         if (args.length != 1) {  
  39.             System.err.println("?Usage: ?" + EchoServer.class.getSimpleName() + "<port>?");  
  40.         }  
  41.   
  42.         int port = Integer.parseInt(args[0]);  
  43.         new EchoServer(port).start();  
  44.     }  
  45. }  


客户端 
Java代码   收藏代码
  1. public class EchoClient {  
  2.     private final String host; // 服务器地址  
  3.     private final int port; // 服务器端口  
  4.   
  5.     public EchoClient(String host, int port) {  
  6.         this.host = host;  
  7.         this.port = port;  
  8.     }  
  9.   
  10.     public void start() throws Exception {  
  11.         EventLoopGroup group = new NioEventLoopGroup();  
  12.         try {  
  13.             Bootstrap b = new Bootstrap(); // 客户端引导器  
  14.   
  15.             // 指定事件组、客户端通道、远程服务端地址、业务处理器  
  16.             b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))  
  17.                     .handler(new ChannelInitializer<SocketChannel>() {  
  18.   
  19.                         @Override  
  20.                         protected void initChannel(SocketChannel ch) throws Exception {  
  21.                             ch.pipeline().addLast(new EchoClientHandler());  
  22.                         }  
  23.                     });  
  24.   
  25.             // 连接到服务端,sync()阻塞直到连接过程结束  
  26.             ChannelFuture f = b.connect().sync();  
  27.   
  28.             // 等待通道关闭  
  29.             f.channel().closeFuture().sync();  
  30.         } finally {  
  31.             // 关闭引导器并释放资源,包括线程池  
  32.             group.shutdownGracefully().sync();  
  33.         }  
  34.     }  
  35.   
  36.     public static void main(String[] args) throws Exception {  
  37.         args = new String[2];  
  38.         args[0] = "mysit.cnsuning.com";  
  39.         args[1] = "8180";  
  40.           
  41.         if (args.length != 2) {  
  42.             System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");  
  43.             return;  
  44.         }  
  45.   
  46.         final String host = args[0];  
  47.         final int port = Integer.parseInt(args[1]);  
  48.         new EchoClient(host, port).start();  
  49.     }  
  50. }  


服务端处理 

Java代码   收藏代码
  1. public class EchoServerHandler extends ChannelInboundHandlerAdapter {  
  2.   
  3.     @Override  
  4.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
  5.         ByteBuf buf = (ByteBuf) msg;  
  6.         System.out.println("Server received: " + ByteBufUtil.hexDump(buf.readBytes(buf.readableBytes()))); 缓冲内部存储读写位置,readBytes将指针后移  
  7.         // System.out.println("?Server received: ?" + msg);  
  8.         buf.resetReaderIndex(); // 重置读写位置,如果省略这一句,ctx.write(msg)往客户端发送的数据为空  
  9.         ctx.write(msg);  
  10.     }  
  11.   
  12.     @Override  
  13.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {  
  14.         // 读写完毕,调用flush将数据真正发送到客户端  
  15.         ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);  
  16.     }  
  17.   
  18.     @Override  
  19.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
  20.         cause.printStackTrace(); // 打印异常  
  21.         ctx.close(); // 关闭通道  
  22.     }  
  23. }  


客户端处理 
Java代码   收藏代码
  1. @Sharable  
  2. public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {  
  3.   
  4.     @Override  
  5.     public void channelActive(ChannelHandlerContext ctx) throws Exception {  
  6.         // 连接建立,向服务端发送数据  
  7.         ctx.write(Unpooled.copiedBuffer("Hello Netty!", CharsetUtil.UTF_8));  
  8.   
  9.         // 注意:需要调用flush将数据发送到服务端  
  10.         ctx.flush();  
  11.     }  
  12.   
  13.     @Override  
  14.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
  15.         // 打印异常并关闭通道  
  16.         cause.printStackTrace();  
  17.         ctx.close();  
  18.     }  
  19.   
  20.     @Override  
  21.     protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {  
  22.         // 读取服务端返回的数据并打印  
  23.         System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));  
  24.     }  
  25. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值