沉淀再出发:关于netty的一些理解和使用
一、前言
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。netty在底层的数据通信和封装之中有着重要的作用,下面我们就来看看netty的简单使用过程,以及背后的原理。
二、netty的简单使用
2.1、netty的环境部署和使用
在这里我们使用myeclipse平台,maven管理工具进行开发,其实使用eclipse或者其他软件也可以。首先我们新建一个maven项目,项目名和包名自定:
之后我们修改pom.xml文件,增加netty依赖:
保存之后,系统就会自动为我们下载和安装了,非常的方便,这样,我们的环境就部署完毕了。
2.2、一个简单的案例
下面我们看一个简单地案例:
我们新建一个包,然后写入两个文件:
首先我们编写一个处理连接的类 HelloServerHandler :
1 package com.coder.server; 2 3 import io.netty.buffer.ByteBuf; 4 import io.netty.channel.ChannelHandlerContext; 5 import io.netty.channel.ChannelInboundHandlerAdapter; 6 import io.netty.util.CharsetUtil; 7 import io.netty.util.ReferenceCountUtil; 8 9 10 public class HelloServerHandler extends ChannelInboundHandlerAdapter { 11 /** 12 * 收到数据时调用 13 */ 14 @Override 15 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 16 try { 17 ByteBuf in = (ByteBuf)msg; 18 System.out.print(in.toString(CharsetUtil.UTF_8)); 19 } finally { 20 // 抛弃收到的数据 21 ReferenceCountUtil.release(msg); 22 } 23 } 24 25 /** 26 * 当Netty由于IO错误或者处理器在处理事件时抛出异常时调用 27 */ 28 @Override 29 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 30 // 当出现异常就关闭连接 31 cause.printStackTrace(); 32 ctx.close(); 33 } 34 }
其次,我们编写接收连接,并且派发和处理的类 HelloServer :
1 package com.coder.server; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioServerSocketChannel; 11 12 public class HelloServer { 13 private int port; 14 15 public HelloServer(int port) { 16 this.port = port; 17 } 18 19 public void run() throws Exception { 20 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用来接收进来的连接 21 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用来处理已经被接收的连接 22 System.out.println("准备运行端口:" + port); 23 24 try { 25 ServerBootstrap b = new ServerBootstrap(); 26 b.group(bossGroup, workerGroup) 27 .channel(NioServerSocketChannel.class) // 这里告诉Channel如何接收新的连接 28 .childHandler( new ChannelInitializer<SocketChannel>() { 29 @Override 30 protected void initChannel(SocketChannel ch) throws Exception { 31 // 自定义处理类 32 ch.pipeline().addLast(new HelloServerHandler()); 33 } 34 }) 35 .option(ChannelOption.SO_BACKLOG, 128) 36 .childOption(ChannelOption.SO_KEEPALIVE, true); 37 38 // 绑定端口,开始接收进来的连接 39 ChannelFuture f = b.bind(port).sync(); 40 41 // 等待服务器socket关闭 42 f.channel().closeFuture().sync(); 43 } catch (Exception e) { 44 workerGroup.shutdownGracefully(); 45 bossGroup.shutdownGracefully(); 46 } 47 } 48 49 public static void main(String[] args) throws Exception { 50 int port = 12345; 51 new HelloServer(port).run(); 52 } 53 }
然后运行,等待连接就好了,那么问题来了,使用什么进行连接呢?在windows中,我们可以使用Telnet,这个比较方便和简单,但是我们需要打开控制面板的程序和功能模块,并且启动服务,之后最好重启一下电脑:
下面我们运行程序,并使用Telnet客户端测试一下:
在telnet中‘ctrl+]’可以显示输入的文字,否则将看不到输入。
三、使用netty自定义时间服务器
本例中我们试图在服务器和客户端连接被创立时发送一个消息,然后在客户端解析收到的消息并输出。并且,在这个项目中使用 POJO 代替 ByteBuf 来作为传输对象。
3.1、pojo对象创建
Time 类:
1 package com.coder.pojo; 2 3 import java.util.Date; 4 5 /** 6 * 自定义时间数据类 7 * 8 */ 9 public class Time { 10 private final long value; 11 12 public Time() { 13 // 除以1000是为了使时间精确到秒
//注意这里的this,其实就是调用了 public Time(long value) ,并且更加的方便和快捷。 14 this(System.currentTimeMillis() / 1000L); 15 } 16 17 public Time(long value) { 18 this.value = value; 19 } 20 21 public long value() { 22 return value; 23 } 24 25 @Override 26 public String toString() { 27 return new Date((value()) * 1000L).toString(); 28 } 29 }
3.2、服务器程序
TimeEncoderPOJO类:
1 package com.coder.server; 2 3 import com.coder.pojo.Time; 4 5 import io.netty.buffer.ByteBuf; 6 import io.netty.channel.ChannelHandlerContext; 7 import io.netty.handler.codec.MessageToByteEncoder; 8 9 /** 10 * 服务器数据编码类 11 * 12 */ 13 public class TimeEncoderPOJO extends MessageToByteEncoder<Time> { 14 15 // 发送数据时调用 16 @Override 17 protected void encode(ChannelHandlerContext ctx, Time msg, ByteBuf out) throws Exception { 18 // 只传输当前时间,精确到秒 19 out.writeInt((int)msg.value()); 20 } 21 22 }
TimeServerHandlerPOJO类:连接建立并且准备通信的时候进行处理,发送当前时间,并增加监听。
1 package com.coder.server; 2 3 import com.coder.pojo.Time; 4 5 import io.netty.channel.ChannelFuture; 6 import io.netty.channel.ChannelFutureListener; 7 import io.netty.channel.ChannelHandlerContext; 8 import io.netty.channel.ChannelInboundHandlerAdapter; 9 10 /** 11 * 服务器解码器 12 * 连接建立时发送当前时间 13 * 14 */ 15 public class TimeServerHandlerPOJO extends ChannelInboundHandlerAdapter { 16 /** 17 * 连接建立的时候并且准备进行通信时被调用 18 */ 19 @Override 20 public void channelActive(final ChannelHandlerContext ctx) throws Exception { 21 // 发送当前时间信息 22 ChannelFuture f = ctx.writeAndFlush(new Time()); 23 // 发送完毕之后关闭 Channel 24 f.addListener(ChannelFutureListener.CLOSE); 25 } 26 27 @Override 28 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 29 cause.printStackTrace(); 30 ctx.close(); 31 } 32 }
TimeServerPOJO类:服务器的主程序
1 package com.coder.server; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioServerSocketChannel; 11 12 public class TimeServerPOJO { 13 private int port; 14 15 public TimeServerPOJO(int port) { 16 this.port = port; 17 } 18 19 public void run() throws Exception { 20 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用来接收进来的连接 21 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用来处理已经被接收的连接 22 System.out.println("准备运行端口:" + port); 23 24 try { 25 ServerBootstrap b = new ServerBootstrap(); // 启动NIO服务的辅助启动类 26 b.group(bossGroup, workerGroup) 27 .channel(NioServerSocketChannel.class) // 这里告诉Channel如何接收新的连接 28 .childHandler( new ChannelInitializer<SocketChannel>() { 29 @Override 30 protected void initChannel(SocketChannel ch) throws Exception { 31 // 自定义处理类 32 // 注意添加顺序 33 ch.pipeline().addLast(new TimeEncoderPOJO(),new TimeServerHandlerPOJO()); 34 } 35 }) 36 .option(ChannelOption.SO_BACKLOG, 128) 37 .childOption(ChannelOption.SO_KEEPALIVE, true); 38 39 // 绑定端口,开始接收进来的连接 40 ChannelFuture f = b.bind(port).sync(); 41 42 // 等待服务器socket关闭 43 f.channel().closeFuture().sync(); 44 } catch (Exception e) { 45 workerGroup.shutdownGracefully(); 46 bossGroup.shutdownGracefully(); 47 } 48 } 49 50 public static void main(String[] args) throws Exception { 51 int port = 12345; 52 new TimeServerPOJO(port).run(); 53 } 54 }
其中ch.pipeline().addLast(new TimeEncoderPOJO(),new TimeServerHandlerPOJO());方法的含义为:Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its ChannelPipeline
.也就是说当我们添加一些处理的时候会按照管道的方式,一步步的处理,因此先后顺序非常重要。
3.3、客户端程序
先来看看解码器(服务器端发送了编码后的时间信息,因此,这里客户端收到之后需要解码):
TimeDecoderPOJO 类:
1 package com.coder.client; 2 3 import java.util.List; 4 5 import com.coder.pojo.Time; 6 7 import io.netty.buffer.ByteBuf; 8 import io.netty.channel.ChannelHandlerContext; 9 import io.netty.handler.codec.ByteToMessageDecoder; 10 11 public class TimeDecoderPOJO extends ByteToMessageDecoder { 12 /** 13 * 有新数据接收时调用 14 * 为防止分包现象,先将数据存入内部缓存,到达满足条件之后再进行解码 15 */ 16 @Override 17 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 18 if(in.readableBytes() < 4) { 19 return; 20 } 21 22 // out添加对象则表示解码成功 23 out.add(new Time(in.readUnsignedInt())); 24 } 25 }
再看看客户端数据处理类:
TimeClientHandlerPOJO类:
1 package com.coder.client; 2 3 import com.coder.pojo.Time; 4 5 import io.netty.channel.ChannelHandlerContext; 6 import io.netty.channel.ChannelInboundHandlerAdapter; 7 8 /** 9 * 客户端数据处理类 10 * 11 */ 12 public class TimeClientHandlerPOJO extends ChannelInboundHandlerAdapter { 13 @Override 14 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 15 // 直接将信息转换成Time类型输出即可 16 Time time = (Time)msg; 17 System.out.println(time); 18 ctx.close(); 19 } 20 21 @Override 22 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 23 cause.printStackTrace(); 24 ctx.close(); 25 } 26 }
最后是客户端的主程序:
TimeClientPOJO类:
1 package com.coder.client; 2 3 import io.netty.bootstrap.Bootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 12 public class TimeClientPOJO { 13 public static void main(String[] args) throws Exception{ 14 String host = "127.0.0.1"; // ip 15 int port = 12345; // 端口 16 EventLoopGroup workerGroup = new NioEventLoopGroup(); 17 18 try { 19 Bootstrap b = new Bootstrap(); // 与ServerBootstrap类似 20 b.group(workerGroup); // 客户端不需要boss worker 21 b.channel(NioSocketChannel.class); 22 b.option(ChannelOption.SO_KEEPALIVE, true); // 客户端的socketChannel没有父亲 23 b.handler(new ChannelInitializer<SocketChannel>() { 24 @Override 25 protected void initChannel(SocketChannel ch) throws Exception { 26 // POJO 27 ch.pipeline().addLast(new TimeDecoderPOJO() ,new TimeClientHandlerPOJO()); 28 } 29 }); 30 31 // 启动客户端,客户端用connect连接 32 ChannelFuture f = b.connect(host, port).sync(); 33 34 // 等待连接关闭 35 f.channel().closeFuture().sync(); 36 } finally { 37 workerGroup.shutdownGracefully(); 38 } 39 } 40 }
至此程序编写完毕,先运行服务器,再运行客户端程序,然后测试即可,我们会发现服务器一直等待着请求,当客户端连接上之后,服务器就会发出带着格式的时间,客户端接收到之后进行解码,然后显示出来并且退出。在同一个myeclipse之中可以运行多个程序,使用下图中的按钮可以进行切换。
四、netty的基本组成部分
4.1、Channel
Channel 是 Java NIO 的一个基本构造。它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作。目前,可以把 Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。
4.2、Callback(回调)
Netty 在内部使用了回调来处理事件;当一个回调被触发时,相关的事件可以被一个 interfaceChannelHandler 的实现处理。
4.3、Future
Future 提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以 Netty 提供了它自己的实现ChannelFuture,用于在执行异步操作的时候使用。
4.4、Event 和 Handler
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。这些动作可能是:记录日志、数据转换、流控制、应用程序逻辑。Netty 是一个网络编程框架,所以事件是按照它们与入站或出站数据流的相关性进行分类的。可能由入站数据或者相关的状态更改而触发的事件包括:连接已被激活或者连接失活、数据读取、用户事件、错误事件。出站事件是未来将会触发的某个动作的操作结果,这些动作包括:打开或者关闭到远程节点的连接、将数据写到或者冲刷到套接字。
Netty 的 ChannelHandler 为处理器提供了基本的抽象,目前可以认为每个 ChannelHandler 的实例都类似于一种为了响应特定事件而被执行的回调。Netty 提供了大量预定义的可以开箱即用的 ChannelHandler 实现,包括用于各种协议(如 HTTP 和 SSL/TLS)的 ChannelHandler。在内部 ChannelHandler 自己也使用了事件和 Future。
五、netty聊天程序
5.1、服务器端
SimpleChatServerInitializer类:
1 package com.coder.server; 2 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.ChannelPipeline; 5 import io.netty.channel.socket.SocketChannel; 6 import io.netty.handler.codec.DelimiterBasedFrameDecoder; 7 import io.netty.handler.codec.Delimiters; 8 import io.netty.handler.codec.string.StringDecoder; 9 import io.netty.handler.codec.string.StringEncoder; 10 11 /** 12 * 服务器配置初始化 13 * 添加多个处理器 14 */ 15 public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> { 16 17 @Override 18 protected void initChannel(SocketChannel ch) throws Exception { 19 ChannelPipeline pipeline = ch.pipeline(); 20 // 添加处理类 21 // 使用' '' '分割帧 22 pipeline.addLast("framer", 23 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); 24 // 解码、编码器 25 pipeline.addLast("decoder", new StringDecoder()); 26 pipeline.addLast("encoder", new StringEncoder()); 27 // 处理器 28 pipeline.addLast("handler", new SimpleChatServerHandler()); 29 30 System.out.println("SimpleChatClient: " + ch.remoteAddress() + "连接上"); 31 } 32 33 }
SimpleChatServerHandler类:
1 package com.coder.server; 2 3 4 import io.netty.channel.*; 5 import io.netty.channel.group.ChannelGroup; 6 import io.netty.channel.group.DefaultChannelGroup; 7 import io.netty.util.concurrent.GlobalEventExecutor; 8 9 /** 10 * 服务端处理器 11 */ 12 public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> { 13 14 public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 15 16 /** 17 * 收到新的客户端连接时调用 18 * 将客户端channel存入列表,并广播消息 19 */ 20 @Override 21 public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 22 Channel incoming = ctx.channel(); 23 // 广播加入消息 24 channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入 "); 25 channels.add(incoming); // 存入列表 26 } 27 28 /** 29 * 客户端连接断开时调用 30 * 广播消息 31 */ 32 @Override 33 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 34 Channel incoming = ctx.channel(); 35 // 广播离开消息 36 channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 离开 "); 37 // channel会自动从ChannelGroup中删除 38 } 39 40 /** 41 * 收到消息时调用 42 * 将消息转发给其他客户端 43 */ 44 @Override 45 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 46 Channel incoming = ctx.channel(); 47 for(Channel channel : channels) { // 遍历所有连接的客户端 48 if(channel != incoming) { // 其他客户端 49 channel.writeAndFlush("[" + incoming.remoteAddress() + "] " + msg + " " ); 50 } else { // 自己 51 channel.writeAndFlush("[you] " + msg + " " ); 52 } 53 } 54 } 55 56 /** 57 * 监听到客户端活动时调用 58 */ 59 @Override 60 public void channelActive(ChannelHandlerContext ctx) throws Exception { 61 Channel incoming = ctx.channel(); 62 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 在线"); 63 } 64 65 /** 66 * 监听到客户端不活动时调用 67 */ 68 @Override 69 public void channelInactive(ChannelHandlerContext ctx) throws Exception { 70 Channel incoming = ctx.channel(); 71 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 掉线"); 72 } 73 74 /** 75 * 当Netty由于IO错误或者处理器在处理事件抛出异常时调用 76 * 关闭连接 77 */ 78 @Override 79 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 80 Channel incoming = ctx.channel(); 81 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 异常"); 82 } 83 }
SimpleChatServer类:
1 package com.coder.server; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelOption; 6 import io.netty.channel.EventLoopGroup; 7 import io.netty.channel.nio.NioEventLoopGroup; 8 import io.netty.channel.socket.nio.NioServerSocketChannel; 9 10 /** 11 * 服务端 main 启动 12 */ 13 public class SimpleChatServer { 14 private int port; // 端口 15 16 public SimpleChatServer(int port) { 17 this.port = port; 18 } 19 20 // 配置并开启服务器 21 public void run() throws Exception { 22 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用来接收进来的连接 23 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用来处理已接收的连接 24 25 try { 26 ServerBootstrap sb = new ServerBootstrap(); // 启动NIO服务的辅助启动类 27 sb.group(bossGroup, workerGroup) 28 .channel(NioServerSocketChannel.class) // 设置如何接受连接 29 .childHandler(new SimpleChatServerInitializer()) // 配置Channel 30 .option(ChannelOption.SO_BACKLOG, 128) // 设置缓冲区 31 .childOption(ChannelOption.SO_KEEPALIVE, true); // 启用心跳机制 32 33 System.out.println("SimpleChatServer 启动了"); 34 ChannelFuture future = sb.bind(port).sync(); // 绑定端口,开始接收连接 35 future.channel().closeFuture().sync(); // 等待关闭服务器(不会发生) 36 } finally { 37 workerGroup.shutdownGracefully(); 38 bossGroup.shutdownGracefully(); 39 System.out.println("SimpleChatServer 关闭了"); 40 } 41 } 42 43 public static void main(String[] args) throws Exception { 44 int port = 8080; 45 new SimpleChatServer(port).run(); // 开启服务器 46 } 47 }
5.2、客户端程序
SimpleChatClientInitializer类:
1 package com.coder.client; 2 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.ChannelPipeline; 5 import io.netty.channel.socket.SocketChannel; 6 import io.netty.handler.codec.DelimiterBasedFrameDecoder; 7 import io.netty.handler.codec.Delimiters; 8 import io.netty.handler.codec.string.StringDecoder; 9 import io.netty.handler.codec.string.StringEncoder; 10 11 /** 12 * 客户端配置初始化 13 * 与服务端类似 14 */ 15 public class SimpleChatClientInitializer extends ChannelInitializer<SocketChannel> { 16 17 @Override 18 protected void initChannel(SocketChannel ch) throws Exception { 19 ChannelPipeline pipeline = ch.pipeline(); 20 // 添加处理类 21 // 使用' '' '分割帧 22 pipeline.addLast("framer", 23 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); 24 // 解码、编码器 25 pipeline.addLast("decoder", new StringDecoder()); 26 pipeline.addLast("encoder", new StringEncoder()); 27 // 处理器 28 pipeline.addLast("handler", new SimpleChatClientHandler()); 29 } 30 31 }
SimpleChatClientHandler类:
1 package com.coder.client; 2 3 import io.netty.channel.ChannelHandlerContext; 4 import io.netty.channel.SimpleChannelInboundHandler; 5 6 /** 7 * 客户端处理类 8 * 直接输出收到的消息 9 */ 10 public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> { 11 12 @Override 13 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 14 System.out.println(msg); // 直接输出消息 15 } 16 17 }
SimpleChatClient类:
1 package com.coder.client; 2 3 import java.io.BufferedReader; 4 import java.io.InputStreamReader; 5 6 import io.netty.bootstrap.Bootstrap; 7 import io.netty.channel.Channel; 8 import io.netty.channel.EventLoopGroup; 9 import io.netty.channel.nio.NioEventLoopGroup; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 /** 12 * 客户端 13 * 开启客户端,接收控制台输入并发送给服务端 14 */ 15 public class SimpleChatClient { 16 private final String host; // IP 17 private final int port; // 端口 18 19 public SimpleChatClient(String host, int port) { 20 this.host = host; 21 this.port = port; 22 } 23 24 // 配置并运行客户端 25 public void run() throws Exception { 26 EventLoopGroup group = new NioEventLoopGroup(); 27 try { 28 Bootstrap b = new Bootstrap(); // 客户端辅助启动类 29 b.group(group) // 客户端只需要一个用来接收并处理连接 30 .channel(NioSocketChannel.class) // 设置如何接受连接 31 .handler(new SimpleChatClientInitializer());// 配置 channel 32 // 连接服务器 33 Channel channel = b.connect(host, port).sync().channel(); 34 // 读取控制台输入字符 35 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 36 while(true) { 37 // 每行成一帧输出,以" "结尾 38 channel.writeAndFlush(in.readLine() + " "); 39 } 40 } catch (Exception e) { 41 e.printStackTrace(); // 输出异常 42 } finally { 43 group.shutdownGracefully(); // 关闭 44 } 45 } 46 47 public static void main(String[] args) throws Exception { 48 new SimpleChatClient("localhost", 8080).run(); // 启动客户端 49 } 50 51 }
运行结果:
六、总结
通过代码的形式,我们对netty有了直观的了解和实际上的掌握。