Netty是一款基于Java NIO的框架,能够建立通道、
处理事件、编解码和异常处理等,为上层应用提供了清晰、简洁的开发接口:减少用户的编码和错误,使应用开发者能够把注意力集中在业务逻辑上。
下面以回显功能为例:
一、服务端:
1. 实例化引导类
抽象类为AbstractBootstrap,服务端使用ServerBootstrap:
2. 设置参数
Netty将EventLoopGroup, Channel, Address, Handler以及其它配置都放到了AbstractBootstrap中,统一设置:
a. 设置事件组
Netty是基于事件处理的,EventLoopGroup是接口,实现类有NioEventLoopGroup和OioEventLoopGroup (Old IO即Java IO) 等:
b. 设置通道类型
接口为ServerSocketChannel,实现类有NioServerSocketChannel和OioServerSocketChannel。这里使用NioServerSocketChannel
c. 设置服务启动绑定的地址
传入一个SocketAddress实例,指定端口即可(如8080):
当然,也可以在真正绑定的时候设置:
调用sync()会等待前面的方法执行完毕,后面会有很多这样的写法。
ChannelFuture就是Channel的Future类,可以拿到执行结果。
d. 设置childHandler
实现ChannelInitializer接口的initChannel方法,将处理器(业务逻辑)加到通道管道的末尾:
3. 绑定操作
和Socket的绑定类似,如果地址已经通过localAddress设定,这里就可以调用无参方法:
4. 等待结束
5. 最终
在finally块中关闭事件组以及线程池等资源:
二、回显客户端:
和服务端大体类似。不同的地方:
1'. 设置客户端引导类:
2'. 设置参数:
a'. 设置事件组
一个客户端只有一个通道,一个group就够了:
b'. 设置通道类型
根据使用要求,也可以使用其它类型的客户端通道,如OioSocketChannel:
c'. 设置连接地址:
因为是客户端,需要指定连接的服务端主机和端口:
d'. 设置事件处理类:
注意这里的方法是handler
3'. 连接:
这里是连接,而不是绑定。
如果远程地址没有在remoteAddress中设定,需要在连接时设置:
三、事件处理类
EchoServerHandler,EchoClientHandler都是先往引导程序注册,事件发生时触发相应处理方法:
i. 服务器事件处理类
EchoServerHandler扩展io.netty.channel. ChannelInboundHandlerAdapter类,重写下面三个方法,当然,可以根据需要重写更多的方法:
channelRead: 服务端收到客户端发来的数据
channelReadComplete: 服务端读取客户端数据完毕
exceptionCaught: 发生异常,比如客户端关闭连接时
ii. 客户端事件处理类
EchoClientHandler扩展io.netty.channel. SimpleChannelInboundHandler类,重写下面三个方法:
channelActive: 连接已建立,这时可以向服务端发送数据
channelRead0: 服务端向客户端发送数据,可以读取
exceptionCaught: 发生异常,比如服务端关闭连接时
四、代码
回显服务端
客户端
服务端处理
客户端处理
处理事件、编解码和异常处理等,为上层应用提供了清晰、简洁的开发接口:减少用户的编码和错误,使应用开发者能够把注意力集中在业务逻辑上。
下面以回显功能为例:
一、服务端:
1. 实例化引导类
抽象类为AbstractBootstrap,服务端使用ServerBootstrap:
- ServerBootstrap b = new ServerBootstrap();
2. 设置参数
Netty将EventLoopGroup, Channel, Address, Handler以及其它配置都放到了AbstractBootstrap中,统一设置:
a. 设置事件组
Netty是基于事件处理的,EventLoopGroup是接口,实现类有NioEventLoopGroup和OioEventLoopGroup (Old IO即Java IO) 等:
- EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接受连接事件组
- EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理每个连接业务事件组
- b.group(bossGroup, workerGroup); // 可以只使用一个group,分成两个group的好处是:业务耗时较长导致阻塞时,不会对接受连接造成影响。
b. 设置通道类型
接口为ServerSocketChannel,实现类有NioServerSocketChannel和OioServerSocketChannel。这里使用NioServerSocketChannel
- b.channel(NioServerSocketChannel.class);
c. 设置服务启动绑定的地址
传入一个SocketAddress实例,指定端口即可(如8080):
- b.localAddress(new InetSocketAddress(8080))
当然,也可以在真正绑定的时候设置:
- ChannelFuture f = b.bind(new InetSocketAddress(8080)).sync();
调用sync()会等待前面的方法执行完毕,后面会有很多这样的写法。
ChannelFuture就是Channel的Future类,可以拿到执行结果。
d. 设置childHandler
实现ChannelInitializer接口的initChannel方法,将处理器(业务逻辑)加到通道管道的末尾:
- b.childHandler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline().addLast(new EchoServerHandler());
- }
- });
3. 绑定操作
和Socket的绑定类似,如果地址已经通过localAddress设定,这里就可以调用无参方法:
- ChannelFuture f = b.bind().sync();
4. 等待结束
- f.channel().closeFuture().sync();
5. 最终
在finally块中关闭事件组以及线程池等资源:
- group.shutdownGracefully().sync();
二、回显客户端:
和服务端大体类似。不同的地方:
1'. 设置客户端引导类:
- Bootstrap b = new Bootstrap();
2'. 设置参数:
a'. 设置事件组
一个客户端只有一个通道,一个group就够了:
- EventLoopGroup group = new NioEventLoopGroup();
- b.group(group);
b'. 设置通道类型
根据使用要求,也可以使用其它类型的客户端通道,如OioSocketChannel:
- b.channel(NioSocketChannel.class);
c'. 设置连接地址:
因为是客户端,需要指定连接的服务端主机和端口:
- b.remoteAddress(new InetSocketAddress(host, port));
d'. 设置事件处理类:
注意这里的方法是handler
- b.handler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline().addLast(new EchoClientHandler());
- }
- });
3'. 连接:
这里是连接,而不是绑定。
- ChannelFuture f = b.connect().sync();
如果远程地址没有在remoteAddress中设定,需要在连接时设置:
- 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: 发生异常,比如服务端关闭连接时
四、代码
回显服务端
- public class EchoServer {
- private final int port; // 服务端绑定端口
- public EchoServer(int port) {
- this.port = port;
- }
- public void start() throws Exception {
- EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接受连接事件组
- EventLoopGroup workerGroup = new NioEventLoopGroup(); // 每个连接业务处理事件组
- try {
- ServerBootstrap b = new ServerBootstrap(); // 引导器
- // 指定事件组,通道、绑定地址、业务处理器
- b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
- .localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() { // 虽然EchoServerHandler和ChannelInitializer都是ChannelHandler的实现类,但这里不能直接传入EchoServerHandler,否则会导致业务处理器无法使用
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- // 将业务处理器加到通道管理线(处理器队列)的末尾
- ch.pipeline().addLast(new EchoServerHandler());
- }
- });
- ChannelFuture f = b.bind().sync(); // 绑定指定端口
- System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
- f.channel().closeFuture().sync();
- } finally {
- bossGroup.shutdownGracefully().sync(); // 释放资源和线程池
- }
- }
- public static void main(String[] args) throws Exception {
- args = new String[1];
- args[0] = "8180";
- if (args.length != 1) {
- System.err.println("?Usage: ?" + EchoServer.class.getSimpleName() + "<port>?");
- }
- int port = Integer.parseInt(args[0]);
- new EchoServer(port).start();
- }
- }
客户端
- public class EchoClient {
- private final String host; // 服务器地址
- private final int port; // 服务器端口
- public EchoClient(String host, int port) {
- this.host = host;
- this.port = port;
- }
- public void start() throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- try {
- Bootstrap b = new Bootstrap(); // 客户端引导器
- // 指定事件组、客户端通道、远程服务端地址、业务处理器
- b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
- .handler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline().addLast(new EchoClientHandler());
- }
- });
- // 连接到服务端,sync()阻塞直到连接过程结束
- ChannelFuture f = b.connect().sync();
- // 等待通道关闭
- f.channel().closeFuture().sync();
- } finally {
- // 关闭引导器并释放资源,包括线程池
- group.shutdownGracefully().sync();
- }
- }
- public static void main(String[] args) throws Exception {
- args = new String[2];
- args[0] = "mysit.cnsuning.com";
- args[1] = "8180";
- if (args.length != 2) {
- System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
- return;
- }
- final String host = args[0];
- final int port = Integer.parseInt(args[1]);
- new EchoClient(host, port).start();
- }
- }
服务端处理
- public class EchoServerHandler extends ChannelInboundHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- ByteBuf buf = (ByteBuf) msg;
- System.out.println("Server received: " + ByteBufUtil.hexDump(buf.readBytes(buf.readableBytes()))); 缓冲内部存储读写位置,readBytes将指针后移
- // System.out.println("?Server received: ?" + msg);
- buf.resetReaderIndex(); // 重置读写位置,如果省略这一句,ctx.write(msg)往客户端发送的数据为空
- ctx.write(msg);
- }
- @Override
- public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
- // 读写完毕,调用flush将数据真正发送到客户端
- ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace(); // 打印异常
- ctx.close(); // 关闭通道
- }
- }
客户端处理
- @Sharable
- public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- // 连接建立,向服务端发送数据
- ctx.write(Unpooled.copiedBuffer("Hello Netty!", CharsetUtil.UTF_8));
- // 注意:需要调用flush将数据发送到服务端
- ctx.flush();
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- // 打印异常并关闭通道
- cause.printStackTrace();
- ctx.close();
- }
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
- // 读取服务端返回的数据并打印
- System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));
- }
- }