承接上一章,接下来开始进入Netty入门,本文所采用的Netty版本号是5.0,这个请大家注意下。
还是上一个场景,一个简单的客户端和服务端直接发送字符串的程序,如果使用Netty框架如何展示呢?废话不多说,直接上代码。
服务端:
package com.dlb.note.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * 功能:服务器 * 版本:1.0 * 日期:2016年12月8日14:58:06 * 作者:馟苏 */ public class TimeServer { /** * 主函数 */ public static void main(String []args) { // 配置服务端的NIO线程池 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度 .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChildChannelHandler()); // 绑定端口,同步等待成功 ChannelFuture future = serverBootstrap.bind(8888).sync(); System.out.println("服务器在8888端口监听hello"); // 等待服务端监听端口关闭 future.channel().closeFuture().sync(); System.out.println("服务器关闭bye"); } catch (Exception e) { e.printStackTrace(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class ChildChannelHandler extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new TimerServerHandler()); } } class TimerServerHandler extends ChannelHandlerAdapter { // 可读 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 读数据 ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("receive:" + body); // 写数据 ByteBuf res = Unpooled.copiedBuffer("hello,client!".getBytes()); ctx.write(res); ctx.flush(); } // 连接 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("client come,ip:" + ctx.channel().remoteAddress()); } // 关闭 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("client close,ip:" + ctx.channel().remoteAddress()); ctx.close(); } // 异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println(cause.toString()); ctx.close(); } }
现在来分析一下:首先服务端做了两个线程池,一个是boss线程池,一个worker线程,这是Netty框架给我们提供的。并且在使用完毕后,可以优雅的关闭。然后做一个服务器启动引导serverBootStrap,我们可以设置TCP连接的属性,Netty给我们提供了很多属性。接下来,注册管道,设置处理器,绑定端口监听。
其中比较重要的是处理器,我们可以这么考虑,Netty为我们提供了一个管道,管道中存在许多个处理器。一个客户端链接对应着一个管道。这样我们就可以把处理逻辑写在处理器中。在处理器中我们主要关心这么几个方法,可读:channelRead、链接到来:channelActive、链接断开:channelInactive、异常:exceptionCaught。并且在链接关闭后,直接通过ctx.close();可以将关联的通道等都关闭,并且释放资源。
客户端:
package com.dlb.note.client; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; /** * 功能:支持tcp粘包/拆包的时间客户端 * 版本:1.0 * 日期:2016/12/9 15:40 * 作者:馟苏 */ public class TimeClient { /** * main函数 * @param args */ public static void main(String []args) { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { protected void initChannel(Channel channel) throws Exception { channel.pipeline().addLast(new MyHandler()); } }); // 等待客户端链接成功 ChannelFuture future = bootstrap.connect("localhost", 8888).sync(); System.out.println("客户端链接成功!"); // 等待客户端链接关闭 future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } } class MyHandler extends ChannelHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 1000; i++) { // 1. 以回车/换行符区分 // ByteBuf msg = Unpooled.copiedBuffer(("你好啊,服务器,我是董鲁北啊!" // + System.getProperty("line.separator")).getBytes()); // 2. 采用特殊分隔符区分 ByteBuf msg = Unpooled.copiedBuffer(("你好啊,服务器,我是revoid!$_".getBytes())); ctx.writeAndFlush(msg); } ctx.close(); super.channelActive(ctx); } }
客户端也是非常简单的,首先创建一个线程池,然后注册管道,处理器,在链接服务器成功后,给服务器发送消息。
通过上述介绍,我们可以发现,通过Netty框架编写一个简单的客户端和服务端通信程序,只需要寥寥几行就可以实现。但是仍然还有一个问题没有解决,那就是没有解决,TCP的粘包和分包问题,这个问题,将会在下一章介绍。