在几乎所有Netty教程中都会出现的Netty程序, Echo程序
简单来说就是客户端发送任何内容, 服务端都会返回一样的内容
学习最简单的Echo程序, 主要是为了了解Netty使用的模板代码怎么写
1. 客户端实现
客户端的任务就是, 把消息发送给服务端, 并打印服务端返回的信息
客户端需要做下面几件事
- 建立连接
- 发送数据
- 接收数据
- 打印数据
https://gitee.com/spitman/learnnetty/blob/master/src/main/java/org/zyj/io/example/echo/EchoClient.java
1.1 实现一个发送消息的Handler
当连接建立之后, 立刻发送一条数据
class EchoClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
//建立连接之后, 立刻发送一条消息
String sendMsg = "这是一条消息!";
System.out.println(sendMsg);
ByteBuf byteBuf = Unpooled.copiedBuffer(sendMsg, StandardCharsets.UTF_8);
ctx.writeAndFlush(byteBuf);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//读取服务端返回的信息
ByteBuf buf = (ByteBuf) msg;
if (buf.isReadable()){
System.out.println(buf.toString(StandardCharsets.UTF_8));
}
}
}
1.2 客户端启动Netty
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture f = b.connect("127.0.0.1", 11223).sync();
f.channel().closeFuture().sync();//阻塞主线程, 程序不会退出
} finally {
group.shutdownGracefully();
}
}
channel(NioSocketChannel.class)
: 这里指定使用NioChannel的方式建立连接(依赖JDK实现), 不同的SocketChannel实现类是不同的操作系统接口, 会提供不同的特性, 可使用的option参数也有所区别option(ChannelOption._TCP_NODELAY_, true)
: 不需要等待组装成大TCP包, 这样延迟会比较低ch.pipeline().addLast(new EchoClientHandler())
: 添加ChannelHandler服务端实现
回音服务, 就是将客户端发送的信息, 原样返回
要实现这个功能, 服务端要做下面几件事建立连接
- 读取数据
- 发送数据
https://gitee.com/spitman/learnnetty/blob/master/src/main/java/org/zyj/io/example/echo/EchoServer.java
2.1 服务端回音Handler
class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf receivedMsg = (ByteBuf) msg;
System.out.println(receivedMsg.toString(StandardCharsets.UTF_8));//打印一下第一个传进来的msg是什么
//ChannelHandlerContext对象提供了多种操作, 是你可以出发各种IO事件和IO操作
//ctx.write(msg); //直接将msg写入channel中, 不需要显示的释放ByteBuf, Netty会帮我们释放
//一个TCP消息包, 只发送一点点数据, 就很浪费网络, 包头是固定长度
//所以ctx.write(msg) 这一步仅仅只是写入缓存中, 并没有真正发送出去
//想要立刻发送出去, 需要显示的调用ctx.flush()
//ctx.flush();//将缓存中的数据发送出去
//或者可以简单的使用一行代码来简洁的表示
ctx.writeAndFlush(msg);
}
}
2.2 服务端启动Netty
public static void main(String[] args) throws Throwable {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
public void initChannel(NioSocketChannel ch) {//每次建立连接的时候都会被调用
ch.pipeline().addLast(new EchoServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(11223).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
option(ChannelOption._SO_BACKLOG_, 128)
: 套接字listen函数中的backlog参数, 等待处理连接的队列大小; 如果backlog过小,就可能出现Accept的速度跟不上,A,B队列满了,就会导致客户端无法建立连接childOption(ChannelOption._SO_KEEPALIVE_, true)
: 当设置为true的时候,TCP会实现监控连接是否有效,当连接处于空闲状态的时候,超过了2个小时,本地的TCP实现会发送一个数据包给远程的 socket,如果远程没有发回响应,TCP会持续尝试11分钟,直到响应为止,如果在12分钟的时候还没响应,TCP尝试关闭socket连接。ch.pipeline().addLast(new EchoServerHandler())
: 添加回音Handler3. 运行效果