怎么使用Netty搭建服务端和客户端
这篇文章给大家分享的是有关怎么使用Netty搭建服务端和客户端的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。
服务端
publicclassPrintServer{ publicvoidbind(intport)throwsException{ EventLoopGroupbossGroup=newNioEventLoopGroup();//1 EventLoopGroupworkerGroup=newNioEventLoopGroup();//2 try{ ServerBootstrapb=newServerBootstrap();//3 b.group(bossGroup,workerGroup)//4 .channel(NioServerSocketChannel.class)//5 .option(ChannelOption.SO_BACKLOG,1024)//6 .childHandler(newChannelInitializer<SocketChannel>(){//7 @Override protectedvoidinitChannel(SocketChannelch)throwsException{ ch.pipeline().addLast(newPrintServerHandler()); } }); ChannelFuturef=b.bind(port).sync();//8 f.channel().closeFuture().sync();//9 }finally{ //优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } /** *@paramargs *@throwsException */ publicstaticvoidmain(String[]args)throwsException{ intport=8080; newTimeServer().bind(port); } }
我们来分析一下上面的这段代码(下面的每一点对应上面的注释)
1~2:首先我们创建了两个NioEventLoopGroup实例,它是一个由Netty封装好的包含NIO的线程组。为什么创建两个?我想经过前面的学习大家应该都清楚了。对,因为Netty的底层是IO多路复用,bossGroup 是用于接收客户端的连接,原理就是一个实现的Selector的Reactor线程。而workerGroup用于进行SocketChannel的网络读写。
3:创建一个ServerBootstrap对象,可以把它想象成Netty的入口,通过这类来启动Netty,将所需要的参数传递到该类当中,大大降低了的开发难度。
4:将两个NioEventLoopGroup实例绑定到ServerBootstrap对象中。
5:创建Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),这里创建的是NIOserverSocketChannel,它的功能可以理解为当接受到客户端的连接请求的时候,完成TCP三次握手,TCP物理链路建立成功。并将该“通道”与workerGroup线程组的某个线程相关联。
6:设置参数,这里设置的SO_BACKLOG,意思是客户端连接等待队列的长度为1024.
7:建立连接后的具体Handler。就是我们接受数据后的具体操作,例如:记录日志,对信息解码编码等。
8:绑定端口,同步等待成功
9:等待服务端监听端口关闭
绑定该服务端的Handler
publicclassPrintServerHandlerextendsChannelHandlerAdapter{ @Override publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg) throwsException{ ByteBufbuf=(ByteBuf)msg;//1 byte[]req=newbyte[buf.readableBytes()]; buf.readBytes(req);//将缓存区的字节数组复制到新建的req数组中 Stringbody=newString(req,"UTF-8"); System.out.println(body); Stringresponse="打印成功"; ByteBufresp=Unpooled.copiedBuffer(response.getBytes()); ctx.write(resp);//2 } @Override publicvoidchannelReadComplete(ChannelHandlerContextctx)throwsException{ ctx.flush();//3 } @Override publicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){ ctx.close(); } }
PrintServerHandler 继承 ChannelHandlerAdapter ,在这里它的功能为 打印客户端发来的数据并且返回客户端打印成功。
我们只需要实现channelRead,exceptionCaught,前一个为接受消息具体逻辑的实现,后一个为发生异常后的具体逻辑实现。
1:我们可以看到,接受的消息被封装为了Object ,我们将其转换为ByteBuf ,前一章的讲解中也说明了该类的作用。我们需要读取的数据就在该缓存类中。
2~3:我们将写好的数据封装到ByteBuf中,然后通过write方法写回到客户端,这里的3调用flush方法的作用为,防止频繁的发送数据,write方法并不直接将数据写入SocketChannel中,而是把待发送的数据放到发送缓存数组中,再调用flush方法发送数据。
客户端
publicclassPrintClient{ publicvoidconnect(intport,Stringhost)throwsException{ EventLoopGroupgroup=newNioEventLoopGroup();//1 try{ Bootstrapb=newBootstrap();//2 b.group(group)//3 .channel(NioSocketChannel.class)//4 .option(ChannelOption.TCP_NODELAY,true)//5 .handler(newChannelInitializer<SocketChannel>(){//6 @Override publicvoidinitChannel(SocketChannelch) throwsException{ ch.pipeline().addLast(newPrintClientHandler()); } }); ChannelFuturef=b.connect(host,port).sync();//7 f.channel().closeFuture().sync();//8 }finally{ //优雅退出,释放NIO线程组 group.shutdownGracefully(); } } /** *@paramargs *@throwsException */ publicstaticvoidmain(String[]args)throwsException{ intport=8080; newTimeClient().connect(port,"127.0.0.1"); } }
我们继续来分析一下上面的这段代码(下面的每一点对应上面的注释)
1:区别于服务端,我们在客户端只创建了一个NioEventLoopGroup实例,因为客户端你并不需要使用I/O多路复用模型,需要有一个Reactor来接受请求。只需要单纯的读写数据即可
2:区别于服务端,我们在客户端只需要创建一个Bootstrap对象,它是客户端辅助启动类,功能类似于ServerBootstrap。
3:将NioEventLoopGroup实例绑定到Bootstrap对象中。
4:创建Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),区别与服务端,这里创建的是NIOSocketChannel.
5:设置参数,这里设置的TCP_NODELAY为true,意思是关闭延迟发送,一有消息就立即发送,默认为false。
6:建立连接后的具体Handler。注意这里区别与服务端,使用的是handler()而不是childHandler()。handler和childHandler的区别在于,handler是接受或发送之前的执行器;childHandler为建立连接之后的执行器。
7:发起异步连接操作
8:当代客户端链路关闭
绑定该客户端的Handler
publicclassPrintClientHandlerextendsChannelHandlerAdapter{ privatestaticfinalLoggerlogger=Logger .getLogger(TimeClientHandler.class.getName()); privatefinalByteBuffirstMessage; /** *Createsaclient-sidehandler. */ publicTimeClientHandler(){ byte[]req="你好服务端".getBytes(); firstMessage=Unpooled.buffer(req.length);//1 firstMessage.writeBytes(req); } @Override publicvoidchannelActive(ChannelHandlerContextctx){ ctx.writeAndFlush(firstMessage);//2 } @Override publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)//3 throwsException{ ByteBufbuf=(ByteBuf)msg; byte[]req=newbyte[buf.readableBytes()]; buf.readBytes(req); Stringbody=newString(req,"UTF-8"); System.out.println("服务端回应消息:"+body); } @Override publicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){//4 //释放资源 System.out.println("Unexpectedexceptionfromdownstream:" +cause.getMessage()); ctx.close(); } }
PrintClientHandler 继承 ChannelHandlerAdapter ,在这里它的功能为 发送数据并打印服务端发来的数据。
我们只需要实现channelActive,channelRead,exceptionCaught,第一个为建立连接后立即执行,后两个与一个为接受消息具体逻辑的实现,另一个为发生异常后的具体逻辑实现。
1:将发送的信息封装到ByteBuf中。
2:发送消息。
3:接受客户端的消息并打印
4:发生异常时,打印异常信息,释放客户端资源
感谢各位的阅读!关于“怎么使用Netty搭建服务端和客户端”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!