通过上篇文章已经分别对netty服务端和客户端有了简单的认识.
详情见:IO编程(三)Netty编程入门(服务端,客户端流程)
下面我们要实现客户端写数据到服务端,服务端读取数据同时写数据返回客户端
回到之前的客户端NettyClient
在bootstrap.handler()中添加写数据的方法
//处理IO逻辑
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//获取管道(和这条连接相关的逻辑处理链)
ChannelPipeline pipeline = nioSocketChannel.pipeline();
//添加我们自己的逻辑处理器
pipeline.addLast(new MyClientHandler());
}
});
创建我们自己的逻辑处理器
MyClientHandler
/**
* @Author: xuxu
* @Date: 2019/12/26 14:19
*/
public class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//ctx.alloc()获取到一个 ByteBuf 的内存管理器,这个 内存管理器的作用就是分配一个 ByteBuf
ByteBuf buffer = ctx.alloc().buffer();
String msg = new Date()+ "--->客户端写数据:你好!Netty!";
System.out.println(msg);
//填充数据
buffer.writeBytes(msg.getBytes("UTF-8"));
//写数据
ctx.writeAndFlush(buffer);
}
}
- 逻辑处理器继承自
ChannelInboundHandlerAdapter
,然后覆盖了channelActive()
方法,这个方法会在客户端连接建立成功之后被调用 - 客户端连接建立成功之后,调用到
channelActive()
方法,在这个方法里面,我们编写向服务端写数据的逻辑 - 写数据的逻辑分为两步:首先我们需要获取一个 netty 对二进制数据的抽象
ByteBuf
,上面代码中,ctx.alloc()
获取到一个ByteBuf
的内存管理器,这个 内存管理器的作用就是分配一个ByteBuf
,然后我们把字符串的二进制数据填充到ByteBuf
,这样我们就获取到了 Netty 需要的一个数据格式,最后我们调用ctx.channel().writeAndFlush()
把数据写到服务端
和传统的 socket 编程不同的是,Netty 里面数据是以 ByteBuf 为单位的, 所有需要写出的数据都必须塞到一个 ByteBuf,数据的写出是如此,数据的读取亦是如此
下面我们来编写服务端读取客户端数据的代码
NettyServer中我们知道服务端处理客户端连接数据是在serverBootstrap.childHandler()中
//定义每条连接的数据读写,业务逻辑
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
System.out.println("处理当前连接数据");
}
});
serverBootstrap.childHandler中添加
//定义每条连接的数据读写,业务逻辑
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new MyServerHandler());
}
});
我们创建MyServerHandler 同样继承ChannelInboundHandlerAdapter 但是这次重写channelRead方法,这个方法在接收到客户端发来的数据之后被回调。
/**
* @Author: xuxu
* @Date: 2019/12/26 14:42
*/
public class MyServerHandler 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")));
}
}
思考:这里的 msg
参数指的就是 Netty 里面数据读写的载体,为什么这里不直接是 ByteBuf
,而需要我们强转一下?
我们先启动服务端,再启动客户端.
服务端控制台
客户端控制台
这样就成功实现了客户端写消息服务端读取消息.
接下来我们需要实现服务端回数据给客户端,同时客户端需要读消息
服务端MyServerHandler 读取客户端信息后 写数据给客户端
/**
* @Author: xuxu
* @Date: 2019/12/26 14:42
*/
public class MyServerHandler 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")));
String returnMsg = new Date()+ "--->服务端写数据:你好!客户端!";
System.out.println(returnMsg);
//要清空上一次填充的数据
byteBuf.clear();
byteBuf.writeBytes(returnMsg.getBytes("utf-8"));
ctx.writeAndFlush(byteBuf);
}
}
客户端在MyClientHandler中添加读数据的部分
/**
* @Author: xuxu
* @Date: 2019/12/26 14:19
*/
public class MyClientHandler extends ChannelInboundHandlerAdapter {
//客户端写数据
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//ctx.alloc()获取到一个 ByteBuf 的内存管理器,这个 内存管理器的作用就是分配一个 ByteBuf
ByteBuf buffer = ctx.alloc().buffer();
String msg = new Date() + "--->客户端写数据:你好!Netty!";
System.out.println(msg);
//填充数据
buffer.writeBytes(msg.getBytes("UTF-8"));
//写数据
ctx.writeAndFlush(buffer);
}
//客户端du读数据
@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")));
}
}
控制台结果
服务端
客户端
发现不管是服务端还是客户端,读写的流程基本上是一致的
1.客户端和服务端的逻辑处理是均是在启动的时候,通过给逻辑处理链 pipeline
添加逻辑处理器,来编写数据的读写逻辑
客户端连接成功之后会回调到逻辑处理器的 channelActive()
方法,而不管是服务端还是客户端,收到数据之后都会调用到 channelRead
方法。
写数据调用writeAndFlush
方法,客户端与服务端交互的二进制数据载体为 ByteBuf
,ByteBuf
通过连接的内存管理器创建,字节数据填充到 ByteBuf
之后才能写到对端