时间,犹如白驹过隙,一眨眼,国庆假期已经过去了四天了,日子总是像指间流过的细沙,在不经意间悄然滑落。所以,你该开始学Netty了
,今天开始学习如何实现服务端和客户端的双向通信,毕竟和聊天一样,要有来有回才有意思
1
客户端发送数据到服务端
上篇文章提到,读写处理逻辑是在 Bootstrap
的handler()
方法指定的,上节课写的如下代码:
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
// 指定数据读写处理逻辑
channel.pipeline().addLast(new StringEncoder());
}
});
现在,我们自定义一段处理逻辑给它,如下:
代码语言:javascript复制// 设置线程组
bootstrap.group(group)
// 设置线程模型
.channel(NioSocketChannel.class)
// 设置连接读写处理逻辑
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
// channel.pipeline() 责任链模式 返回和这条连接相关的逻辑处理链
// addLast 添加一个逻辑处理器 也就是我们自定义的读写处理逻辑了
channel.pipeline().addLast(new CustomizeHandler());
}
});
上述代码中的
channel.pipeline()
责任链模式 返回和这条连接相关的逻辑处理链addLast()
添加一个逻辑处理器 也就是我们自定义的读写处理逻辑了
其中,CustomizeHandler.java
代码如下:
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;
import java.util.Date;
/**
* Created by zhoudl on 2018/10/4.
*/
public class CustomizeHandler 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);
}
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;
}
}
继承自ChannelInboundHandlerAdapter
的channelActive()
方法会在连接成功之后自动回调;
写数据的过程分为两部分:
- 获取一个
ByteBuf
格式的二进制数据,这个结构是Netty对二进制数据做的抽象; ctx.alloc()
学过C/C 的人肯定知道 alloc 和内存相关,所以这行代码的意思是获取ByteBuff
的内存管理器,而这个内存管理器的作用就是分配一个ByteBuff
出来;- 填充数据到
ByteBuff
中,这样就达到了Netty传输数据的要求; - 使用
ctx.channel().writeAndFlush(buffer);
将数据写出到服务端。
上述代码和传统的Java Socket编程不同的一点就是写出的数据格式不同,Netty是自己对二进制数据做了一层抽象,定义了一个ByteBuff的结构出来,无论数据读还是写,Netty都只需要着这样的格式才行,下面开始学习服务端如何读取到这端数据。
2
服务端读取客户端数据
同理,服务端的读写处理逻辑处理还是在ServerBootstrap
的childHandler()
方法中,这里除了单词不同之外,其他和客户端同理,这就是Netty API 友好的体现方式之一,学了客户端,服务端猜也能猜个大概,所谓大胆猜测,小心验证。
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(new StringDecoder());
channel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});
}
})
一样的,接下来我们自定义一个处理器出来,代码如下:
代码语言:javascript复制import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;
import java.util.Date;
/**
* Created by zhoudl on 2018/10/4.
*/
public class CustomizeServerHandler 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")));
}
}
同样的,你会发现,它继承了ChannelInboundHandlerAdapter
,不同的在于这里是读数据,所以覆盖的方法变了,换成了read()
方法,当客户端连接成功并发送数据之后这个方法被自动回调。
接下来开始学习服务端向客户端回应数据的过程,学完上边这俩之后,现在应该已经没什么难度了。
3
服务端向客户端回应数据
此处写数据和客户端写数据过程类似,我就不再赘述了,直接上代码,简单直接又明了,代码如下:
代码语言:javascript复制import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;
import java.util.Date;
/**
* Created by zhoudl on 2018/10/4.
*/
public class CustomizeServerHandler 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;
}
}
4
标题内容
紧接着,客户端需要读取服务端发过来的数据,而读取数据的过程和上述服务端读取客户端数据的代码无异,将以下代码添加到CustomizeHandler
中,便能实现客户端读数据的逻辑,代码如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(new Date() ": 客户端读到数据 -> " byteBuf.toString(Charset.forName("utf-8")));
}
4
总结
- Netty数据交换传输的载体是
ByteBuff
,只有把数据填充到ByteBuff
之后才能使得对端接收成功数据; ByteBuff
是通过连接的内存管理器创建的;- 写数据的方法是
writeAndFlush
; - 逻辑处理链
`pipeline
`,使用该方法添加逻辑处理器,具体的深层次原理以后再讨论,今天先学到这里。
关于ByteBuff
的学习会单独写一篇文章,因为内容比较多!
以上代码会同步更新在本人的Github和CSDN上
Github地址:https://github.com/Bylant/LeetCode