1、Echo服务端代码
1.1 EchoServerHandler 处理器
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* @ClassName EchoServerHandler
* @Description TODO
* @Date 2019/5/3 11:14
* @Version 1.0
**/
@ChannelHandler.Sharable
//标示一个Channel-Handler可以被多个Channel安全的共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));
//ctx.write(Object) 方法不会使消息写入到通道上,他被缓冲在了内部,
//你需要调用 ctx.flush() 方法来把缓冲区中数据强行输出。
//或者你可以用更简洁的 cxt.writeAndFlush(msg) 以达到同样的目的。
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("读完了");
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
代码解读
EchoServerHandler继承自ChannelInboundHandlerAdapte,用于对网络事件进行读写操作
下面这张结构图体现了一个总体的继承关系
我们发现所有的Handler都来自ChannelHandler接口
下面我们关注一些比较重要的方法
- channelRead 读写网络数据执行的方法
- channelReadComplete 读写网络数据结束执行的方法
- exceptionCaught 发送异常时执行的方法
1.2 EchoServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @ClassName EchoServer
* @Description echo服务端
* @Date 2019/5/2 11:43
* @Version 1.0
**/
public class EchoServer {
private int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new EchoServer(port).start();
}
private void start() throws InterruptedException {
//1 创建线两个程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
//2 创建辅助工具类,用于服务器通道的一系列配置
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup) //绑定线程组
.channel(NioServerSocketChannel.class) //指定NIO模型
.option(ChannelOption.SO_BACKLOG, 1024) //设置tcp缓冲区
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
//3 配置具体数据接收方法的处理
channel.pipeline().addLast(new EchoServerHandler());
}
});
//绑定端口,等待同步成功
ChannelFuture cf = b.bind(port).sync();
//等待服务器监听端口关闭
cf.channel().closeFuture().sync();
} finally {
//优雅的关闭EventLoopGroup,释放线程池资源
bossGroup.shutdownGracefully().sync();
workGroup.shutdownGracefully().sync();
}
}
}
代码解读
1、NioEventLoopGroup
NioEventLoopGroup是个线程组,它包含了一组NIO线程,专门用于网络事件的处理,实际上它们就是Reactor线程组。这里创建两个的原因是一个用于服务端接受客户端的连接,另一个用于进行SocketChannel的网络读写。
- shutdownGracefully:用于优雅的退出,是否服务端资源
2、 ServerBootstrap
ServerBootstrap是netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。
下面我们来看下里面的几个方法:
-
group 设置线程组模型,NIO线程组作为参数传入
-
channel:设置channel通道类型NioServerSocketChannel
-
option: 作用于每个新建立的channel,设置TCP连接中的一些参数,如下
- ChannelOption.SO_BACKLOG: 存放已完成三次握手的请求的等待队列的最大长度;
对于ChannelOption.SO_BACKLOG的解释:(❤ ω ❤) 服务器端TCP内核维护有两个队列,我们称之为A、B队列。 客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手), 服务器端接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手), 此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到客户端发送的ACK时(第三次握手), TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept从B队列中取出完成了三次握手的连接。A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的连接数并无影响,backlog影响的只是还没有被accept取出的连接
- ChannelOption.TCP_NODELAY: 为了解决Nagle的算法问题,默认是false, 要求高实时性,有数据时马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数,就设置为false,会累积一定大小后再发送
-
childHandler: 用于对每个通道里面的数据处理
-
bind :绑定监听端口
3、 ChannelFuture
ChannelFutur的功能类似于JDK的java.util.concurrent.Future,主要用于异步操作的回调通知。
使用 ChannelFuture的channel().closeFuture().sync()方法进行阻塞,等待服务端链路关闭之后才继续往下执行
2、Echo客户端
2.1 EchoClientHandler
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 当从服务器接收到一条消息时被调用
* @param channelHandlerContext
* @param in
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) throws Exception {
System.out.println("Client received:"+in.toString(CharsetUtil.UTF_8));
}
/**
* 服务器的连接已经建立好之后将会被调用
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当被知道Channel活跃时,发送一条消息
ctx.writeAndFlush(Unpooled.copiedBuffer("这是一条netty搭建echo服务发送的消息",CharsetUtil.UTF_8));
}
/**
* 发生异常时,记录异常信息同时关闭Channel
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2.2 EchoClient
public class EchoClient {
private int port;
private String host;
public EchoClient(String host, int port) {
this.port = port;
this.host = host;
}
public static void main(String[] args) throws InterruptedException {
if (args.length != 2) {
System.out.println("参数不对:" + EchoClient.class.getSimpleName());
}
new EchoClient("127.0.0.1",8080).start();
}
private void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new EchoClientHandler());
}
});
//异步绑定服务器,调用sync()方法阻塞等待直到绑定完成
ChannelFuture channelFuture = bootstrap.connect().sync();
//等待服务器监听,端口关闭,sync会直到绑定操作结束为止。
channelFuture.channel().closeFuture().sync();
} finally {
// 关闭EventLoopGroup,释放所有的资源
group.shutdownGracefully().sync();
}
}
}