Netty初探

上一章记录了Java NIO简单使用,其中提到了常见的几种IO多路复用模型,本章我们初试Netty,使用Netty写一个Netty客户端与服务端连接。简单的比较一下Java NIO与Netty NIO的区别。

不同的IO模型对比

之前我们使用多线程优化过BIO模型,也使用过原生的NIO,这里我们使用表格对比下几种IO模型及区

对比项 同步阻塞IO 伪异步IO 非阻塞IO 异步IO
连接数:线程数 1:1 M:N M:1 M:0(无需IO线程,被动回调)
Row2 阻塞同步IO 阻塞同步 非阻塞同步 非阻塞异步
吞吐量

下面我们使用Netty进行最简单的NIO开发。

Netty服务端开发

  • 服务端启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class NettyServer {

public void run(int port) throws Exception {
// 主线程池
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 工作线程池
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// Netty启动器
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 关闭线程池
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
  • Handler消息处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

Logger logger = Logger.getLogger(NettyServerHandler.class.getName());

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 数据缓冲区,读取客户端发送过来的数据
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String readMsg = new String(bytes, StandardCharsets.UTF_8);
logger.info("服务端接收到消息: "+readMsg);
// 释放消息
ReferenceCountUtil.release(msg);
// 发送消息到客户端
ByteBuf writeBuf = Unpooled.copiedBuffer((readMsg+"你好").getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(writeBuf);
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
logger.info("服务端发生异常关闭连接");
}
}

Netty客户端开发

  • 客户端启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class NettyClient {
/**
* Netty客户端使用方式与服务端基本相同,不同之处如下
* 1. 客户端只需要指定workGroup即可
* 2. channel类型为NioSocketChannel
*/
public void run(String host,int port){
EventLoopGroup ioGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(ioGroup)
.channel(NioSocketChannel.class)
.option(NioChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());
}
});
try {
ChannelFuture future = b.connect(host,port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
ioGroup.shutdownGracefully();
}
}
}
  • 客户端消息处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
Logger logger = Logger.getLogger(NettyClientHandler.class.getName());
private ByteBuf buf;

public NettyClientHandler() {
}

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连接成功后发送消息
sendMsg(ctx);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf readBuf = (ByteBuf) msg;
byte[] data = new byte[readBuf.readableBytes()];
readBuf.readBytes(data);
String readMsg = new String(data,StandardCharsets.UTF_8);
logger.info("客户端接收到消息: "+readMsg);
ReferenceCountUtil.release(msg);
sendMsg(ctx);
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
logger.info("客户端发生异常关闭连接...");
}

public void sendMsg(ChannelHandlerContext ctx){
String msg = "张三";
byte[] sendData = msg.getBytes(StandardCharsets.UTF_8);
buf = Unpooled.buffer(sendData.length);
buf.writeBytes(sendData);
if (ctx!=null) {
ctx.writeAndFlush(buf);
}
}
}

测试

  • 服务端日志

  • 客户端日志

从上面可以看出,使用Netty开发高并发的服务器是比较快捷高效的。但是仍然没有处理TCP的粘包与拆包。下一节我们模拟下TCP的半包读写以及如何处理这些问题。

作者

Labradors

发布于

2022-06-01

更新于

2022-06-05

许可协议

评论