这篇博主要讲的是如何搭建第一个Netty的demo,所以对一些Netty的类库和用法不会过多的描述。
1.Netty服务器端开发
为了方便理解,这个demo没有采用匿名类的方式去写,实际上如果采用匿名类的编写,代码文件会少一点。虽然看起来可能麻烦些,初学的时候还是觉得分开写比较好。
1.1MyServer.java
在代码的11~12行创建了两个NioEventLoopGroup实例,NioEventLoopGroup实个线程组,包含了一组NIO线程,专门用于网络事件的处理,实际上就是Reactor线程组。这里需要创建两个的原因是一个用于服务器端接受客户端的连接,另外一个用于进行SocketChannel的网络读写。
第15行调用了ServerBootstrap的group方法 ,将两个NIO线程组当作入参传递到ServerBootstrap中,接着设置创建的channel 为NioServerSocketChannel,它的功能对应于JDK NIO类库中的ServerSocketChannel类。在这里我们new了一个MyServerInitializer(),稍后会介绍下这个是做什么的。
最后绑定IO事件的处理类ChildChannelHandler(这里可以注意一下,当编写客户端是用的处理类和服务器端是不同的),它的作用类似于Reactor模式中的Handler类,主要用于处理网络IO事件(我们的编解码过程就是靠它完成)。
服务器端其中辅助类配置完成后,调用bind方法绑定监听端口,然后调用sync等待绑定操作完成。解着Netty返回一个ChannelFuture ,主要用于异步操作的通知回调。
第19行channelFuture.channel().closeFuture().sync();
进行阻塞,等待服务器关闭之后main才会退出。
第22~23行调用shutdownGracefully()方法退出,释放和shutdownGracefully有关的资源。
//Netty服务器端MyServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class MyServer {
public static void main(String[] args) throws Exception{
//配置Server端的NIO线程组
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workergroup=new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(bossGroup,workergroup).channel(NioServerSocketChannel.class).childHandler(new MyServerInitializer());
//绑定端口,同步等待成功
ChannelFuture channelFuture=serverBootstrap.bind(8080).sync();
//等待服务端监听端口关闭
channelFuture.channel().closeFuture().sync();
}finally {
//退出,释放线程池资源
bossGroup.shutdownGracefully();
workergroup.shutdownGracefully();
}
}
}
1.2 MyServerInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline=ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyServerHandler());
}
}
第16行使用了LengthFieldPrepender编码,LengthFieldBasedFrameDecoder解码的netty传输可以解决半包粘包问题,这个会在后面写一篇博客记录下,在此就不多描述了。
1.3 MyServerHandler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.UUID;
public class MyServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress()+","+msg);
ctx.channel().writeAndFlush("from server"+ UUID.randomUUID());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
MyServerHandler继承于SimpleChannelInboundHandler。public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter
SimpleChannelInboundHandler又继承于ChannelInboundHandlerAdapter,ChannelInboundHandlerAdapter用于对网络事件进行读写操作,通常只关注channelRead0和exceptionCaught方法。
2.Netty客户端开发
2.1MyClient.java
//Netty客户端MyClient
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class MyClient {
public static void main(String[] args) throws Exception{
EventLoopGroup eventLoopGroup=new NioEventLoopGroup();
try{
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new MyClientInitializer());
ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",8080).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}
于服务器端不同的是第14行,它的Channel需要设置为NioSocketChannel,然后为其添加Handler。
2.2 MyClientHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.time.LocalDateTime;
public class MyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress());
System.out.println("client output:"+msg);
ctx.writeAndFlush("from client:"+ LocalDateTime.now());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("来自于客户端的问候");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
当客户端和服务器端TCP通路建立成功后,Netty的NIO线程会调用channelActive方法,发送问候给服务器端,调用ChannelHandlerContext的writeAndFlush的方法将请求消息发送给服务器端。
当服务器端返回应答消息时,channelRead方法被调用,打印应答消息。
第22~26行,当发生异常时,打印异常日志,释放客户端资源。
2.3 MyClientInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline=ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyClientHandler());
}
}
当创建NioSocketChannel成功后,再进行初始化时,将ChannelHandler设置到ChannelPipeline中,用于处理网络IO事件。
3.运行
首先启动MyServer,然后启动MyClient。打印信息如下:
3.1 MyServer
/127.0.0.1:62326,来自于客户端的问候
/127.0.0.1:62326,from client:2019-09-25T11:33:39.612
/127.0.0.1:62326,from client:2019-09-25T11:33:39.615
/127.0.0.1:62326,from client:2019-09-25T11:33:39.616
/127.0.0.1:62326,from client:2019-09-25T11:33:39.616
/127.0.0.1:62326,from client:2019-09-25T11:33:39.617
/127.0.0.1:62326,from client:2019-09-25T11:33:39.618
/127.0.0.1:62326,from client:2019-09-25T11:33:39.619
......
3.2 MyClient
ef5-8484-91c31c7cc23c
/127.0.0.1:8080
client output:from server7fbb6e58-b176-4a37-a5ef-cc36a77cfb00
/127.0.0.1:8080
client output:from server80777926-723b-4973-880d-98b86b9c9f93
/127.0.0.1:8080
client output:from server2b911e76-5b89-4f71-94bb-fbb36ae4a310
/127.0.0.1:8080
client output:from server6d2fab67-2776-45b1-99b1-cedfaa60069b
/127.0.0.1:8080
client output:from server330d4c9a-1dd8-448f-ac4e-5effef9b7fb9
/127.0.0.1:8080
......