第一个Netty程序
本章将运用netty建立一个Echo Server和Client来熟悉Netty的特性
Echo Server
一个Netty服务器包含以下两个主要部分:
- Bootstrapping:用来配置服务器熟悉,包括线程及端口。
- Server Handler:Server组件,包括各种如何处理新的链接等各种业务逻辑的实现
启动Server
我们通过创建一个ServerBootStrap实例来启动一个Server。如下面代码所示,通过配置实例的端口号,线程(事件)模型以及处理各种业务逻辑的handler来实现一个Netty Server。
|
|
- 创建Server实例
- 指定NIO(异步IO)协议及网络地址
- 向channel pipline中添加handler
- 绑定服务器,等待服务器关闭并释放资源
为了运行一个服务器,首先要创建一个ServerBootstrao实例(1),因为我们用的是NIO传输协议,所以需要指定NioEventLoopGroup来接受并处理新的连接,指定NioServerSocketChannel作为channel的类型,同时需要设置Server绑定的InetSocketAddress才能够接受新的连接(2)
下一步,通过创建一个子channel(child channel, 3)来指定当一个新的连接到来时执行的动作,在这里运用了ChannelInitializer类型。由于ChannelPipeline中包含多个handler,所以我们将新建的EchoServerHandler添加到最后(4)。
在(5)处,通过调用sync()方法来阻塞绑定我们的Server直到成功,同样在(8),我们阻塞调用Server的close接口,直到Server关闭,在(10),我们可以关闭EventLoopGroup并释放包括创建的线程在内的所有资源。
简化一下以上的步骤:
- 创建一个用于启动Server的ServerBootstrap示例,并在后边绑定它
- 创建一个NIOEventLoopGroup实例来处理各种事件,例如接受新连接,接受新数据,写数据等等。
- 指定Server需要绑定的本地的InetSocketAddress
- 创建一个childHandler来指定每一个新连接到来时需要处理的业务逻辑
- 当上述所有步骤都完成后,调用ServerBootstrap.bind()来绑定Server。
实现业务逻辑
我们通过继承ChannelInboundHandlerAdapter并复写messageReceived方法来实现我们的接收数据并回写数据的业务逻辑。
|
|
- Shareable注解说明该handler可以在多个channel之间共享
- 接受新的数据并将其回写,注意在这里还未将数据“flush”到客户端
- 将之前所有的数据flush到客户端,并关闭channel
- 打印异常日志
- 出现异常时关闭channel
Netty的handler提供各种各样的接口“钩子”,我们可以通过复写不同的“钩子”来实现不同的业务逻辑,但这些钩子中只有channelRead是必须的。
拦截异常
处理复写channelRead方法来实现业务逻辑外,我们可以通过复写exceptionCaught来处理Exception或者Throwable等异常。
实现echo client
一个echo client需要包括以下几个功能:
- 连接到服务器
- 写数据
- 接受服务器传回的数据
- 关闭连接
启动client
启动一个client与启动一个Server较为相似,如下代码所示:
|
|
- 创建一个bootstrap
- 指定EventLoopGroup来处理客户端事件,运用NioEventLoopGroup
- 指定socket channle
- 设置需要连接的网络地址
- 利用ChannelInitializer指定channelHandler
- 将EchoClientHandler添加到ChannelPipeline中
- 调用sync()方法连接到远程服务器
- 阻塞等到连接关闭
- 关闭bootstrap和线程池,并释放所有资源
几个重要的步骤:
- 创建Bootstrap实例
- 创建NioEventLoopGroup并用来处理各种事件:创建新连接,读写数据等
- 指定需要连接的服务器地址
- 指定连接创建后调用的handler
- 以上步骤完成后,调用Bootstrap的connect方法连接到远程服务器。
实现客户端业务逻辑
我们通过继承SimpleChannelInboundHandlerAdapter并复写其方法来实现客户端的业务逻辑。目前,我们只需要以下三个方法即可:
- channelActive():当客户端与服务端连接建立时调用
- channelRead0():从服务端接收到数据时调用
- exceptionCaught():发生异常时调用
|
|
- 能够在多个channel共享handler
- 当连接建立时,向服务端发送数据
- 调用hexdump方法打印接收到的数据
- 打印异常
当连接建立时,channelActive方法将会被调用,并向服务端发送一条数据。当接到新的数据时channelRead0方法将会被调用,但需要注意的时客户端接收到的数据可能是不完整的,一个五字节的数据可能会分两次被传输。第一次传输连个字节,第二次传输三个字节。但在TCP协议或者其他面向流的协议来说,这种传输是可以保障顺序的。exceptionCaught被用来捕捉异常,并关闭连接。
或许你会疑问我们在EchoClientHandler为什么继承SimpleChannelInboundHandlerAdapter而不是像EchoServerHandler中继承ChannelInboundHandlerAdapter。最主要的原因是当你使用ChannelInboundHandlerAdapter时你需要自己释放资源,例如当使用ByteBuf时你需要调用ByteBuf.release()方法释放资源,而使用SimpleChannelInboundHandlerAdapter你不需要关心资源的释放,因为当channelRead0执行完毕时系统会自动释放资源。在Netty中,所有实现了ReferenceCounted接口的messages都会自动释放。