要了解Netty服务端与客户端的双向通信,我们首先要了解服务端与客户端的启动过程,然后再了解服务端发送/接收数据的过程以及客户端发送/接收数据的过程。本文将通过代码示例的方式让大家对Netty有一个初步的认识。
服务端启动过程
流程:
服务端引导类 ServerBootstrap
1.创建两个NioEventLoopGroup
bossGroup: 监听端口
workerGroup: 处理每一条连接的数据读写的线程组
2.配置两大线程组,通过.group(bossGroup, workerGroup)给引导类配置两大线程组,即指定线程模型
3.指定服务端的IO模型,通过.channel(NioServerSocketChannel.class)来指定 IO 模型
4.调用childHandler()方法,给引导类创建一个ChannelInitializer,定义连接数据读写处理逻辑
代码:
public static void main(String[] args) {
// 创建一个引导类
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 1.创建两个NioEventLoopGroup
// bossGroup 监听端口
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// workerGroup 处理每一条连接的数据读写的线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
serverBootstrap
// 2.配置两大线程组
.group(bossGroup, workerGroup)
// 3.指定服务端的IO模型
.channel(NioServerSocketChannel.class)
// 4.调用childHandler()方法,给引导类创建一个ChannelInitializer,定义连接数据读写处理逻辑
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
}
}).bind(8000);
}
总结:要启动一个Netty服务端,必须要指定三类属性,分别是线程模型、IO 模型、连接读写处理逻辑,然后再调用bind(8000),我们就可以在本地绑定一个 8000 端口启动起来。
客户端启动流程
流程:
客户端启动的引导类是 Bootstrap
- 与服务端的启动一样,我们需要给它指定线程模型,驱动着连接的数据读写
- 指定 IO 模型为 NioSocketChannel,表示 IO 模型为 NIO
- 给引导类指定一个 handler,这里主要就是定义连接的业务处理逻辑
- 配置完线程模型、IO 模型、业务处理逻辑之后,调用 connect 方法进行连接,由于 connect 方法返回的是一个 Future,是一个异步方法,可以通过 addListener 方法可以监听到连接是否成功,从而打印出连接信息
代码:
public static void main(String[] args) throws InterruptedException {
// 客户端引导类
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap
// 1.指定线程模型
.group(group)
// 2.指定IO类型为NIO
.channel(NioSocketChannel.class)
// IO 处理逻辑
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
});
bootstrap.connect("127.0.0.1", 8000).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败!");
}
});
}
客户端与服务端双向通信
服务端读/写数据
通过服务端引导类 ServerBootstrap 的childHandler()方法指定服务端相关数据的读写逻辑,即 在 initChannel() 方法里面给服务端添加一个逻辑处理器,负责向客户端写数据和读客户端的数据
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new FirstServerHandler());
}
}).bind(8000);
FirstServerHandler
writeAndFlush():写数据至客户端 channelRead():从客户端读数据
public class FirstServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(new Date() + ": 服务端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8")));
// 回复数据到客户端
System.out.println(new Date() + ": 服务端写出数据");
ByteBuf out = getByteBuf(ctx);
ctx.channel().writeAndFlush(out);
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
byte[] bytes = "你好,欢迎关注我的微信公众号,《技术探究猿》!".getBytes(Charset.forName("utf-8"));
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(bytes);
return buffer;
}
}
客户端读/写数据
通过客户端引导类 Bootstrap 的 handler() 方法指定客户端相关的数据读写逻辑,即在 initChannel() 方法里面给客户端添加一个逻辑处理器,用来负责向服务端写数据和读服务端的数据
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new FirstClientHandler());
}
});
FirstClientHandler
writeAndFlush():写数据至服务端
channelRead():从服务端读数据
public class FirstClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(new Date() + ": 客户端写出数据");
// 1. 获取数据
ByteBuf buffer = getByteBuf(ctx);
// 2. 写数据
ctx.channel().writeAndFlush(buffer);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(new Date() + ": 客户端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8")));
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
// 1. 获取二进制抽象 ByteBuf
ByteBuf buffer = ctx.alloc().buffer();
// 2. 准备数据,指定字符串的字符集为 utf-8
byte[] bytes = "你好,技术猿!".getBytes(Charset.forName("utf-8"));
// 3. 填充数据到 ByteBuf
buffer.writeBytes(bytes);
return buffer;
}
}
写在最后
一张图总结一下知识点:
总的来说,Netty的客户端和服务端的启动过程还是比较简单的,感兴趣的小伙伴可以跑跑代码,对Netty有一个初步的印象。接下来将会带大家了解一下Netty的核心组件Channel、EventLoop、ChannelHandler、EventLoopGroup等