一、本章重点
ChannelInboundHandlerAdapter.java【管道连接处理适配器】
作用:定义了channelActive()、channelRead()等方法,用于定义netty管道操作过程中的一些处理方法。
ChannelInboundHandlerAdapter.channelActive()【管道连接成功后回调,操作方法】
作用:连接上服务端后会被回调。
ChannelInboundHandlerAdapter.java【管道连接成功后回调,读取方法】
作用:接收到客户端发来的数据之后会被回调。
二、客户端代码
NettyNIOClient【Netty实现的NIO客户端】
package client;
import handler.ClinetHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Netty实现的NIO客户端【向服务端输出数据、读取数据】
*
* @author 有梦想的肥宅
* @date 2022/5/1
*/
public class NettyNIOClient {
public static void main(String[] args) throws InterruptedException {
//1、创建客户端启动引导类,用于引导客户端的启动工作和连接服务端
Bootstrap bootstrap = new Bootstrap();
//2、创建NioEventLoopGroup对象,作用类似线程组
NioEventLoopGroup group = new NioEventLoopGroup();
//3、配置客户端启动引导类
bootstrap
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
//PS:这里配置建立连接后的处理逻辑【自定义handler】
ch.pipeline().addLast(new ClinetHandler());
}
});
//4、与服务端建立连接
bootstrap.connect("127.0.0.1", 8000).addListener(future -> {
if (future.isSuccess()) {
System.out.println("1、【有梦想的肥宅】Client连接Server成功!");
} else {
System.out.println("1、【有梦想的肥宅】Client连接Server失败!");
}
});
}
}
ClinetHandler【客户端连接处理类】
package handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 客户端连接处理类
*
* @author 有梦想的肥宅
* @date 2022/5/19
*/
public class ClinetHandler extends ChannelInboundHandlerAdapter {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/*PS:channelActive()方法在建立连接后会被回调*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws InterruptedException {
Thread.sleep(1000);
System.out.println("2、【有梦想的肥宅】Client开始写出数据:" + sdf.format(new Date()));
//1、获取数据
//1.1 获取二进制抽象ByteBuf对象
ByteBuf byteBuf = ctx.alloc().buffer();
//1.2 准备数据,并指定字符串的字符集为UTF-8
byte[] bytes = "【有梦想的肥宅】Client发消息啦~".getBytes(StandardCharsets.UTF_8);
//1.3 填充数据到ByteBuf对象
byteBuf.writeBytes(bytes);
//2、写数据
Thread.sleep(1000);
ctx.channel().writeAndFlush(byteBuf);
System.out.println("3、【有梦想的肥宅】Client写出数据完毕:" + sdf.format(new Date()));
}
/*PS:channelRead()方法在接收到数据之后会被回调*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws InterruptedException {
Thread.sleep(1000);
System.out.println("8、【有梦想的肥宅】Client开始接收数据:" + sdf.format(new Date()));
//1、强转接收到的消息内容
ByteBuf byteBuf = (ByteBuf) msg;
//2、在服务端输出接收到的消息内容
Thread.sleep(1000);
System.out.println("9、【有梦想的肥宅】Client接收到Server回复的消息:" + byteBuf.toString(StandardCharsets.UTF_8) + sdf.format(new Date()));
}
}
三、服务端代码
NettyNIOServer【Netty实现的NIO服务端】
package server;
import handler.ServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Netty实现的NIO服务端【向客户端读取数据、输出数据】
*
* @author 有梦想的肥宅
* @date 2022/6/3
*/
public class NettyNIOServer {
public static void main(String[] args) {
//1、创建服务端启动引导类,用于引导服务端的启动工作
ServerBootstrap serverBootstrap = new ServerBootstrap();
//2、创建NioEventLoopGroup对象,作用类似线程组
NioEventLoopGroup boss = new NioEventLoopGroup();//用于获取链接的线程组
NioEventLoopGroup worker = new NioEventLoopGroup();//用于获取数据的线程组
//3、配置服务端启动引导类
serverBootstrap
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
//PS:这里配置建立连接后的处理逻辑【自定义handler】
ch.pipeline().addLast(new ServerHandler());
}
}).bind(8000);
}
}
ServerHandler【服务端连接处理类】
package handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 服务端连接处理类
*
* @author 有梦想的肥宅
* @date 2022/5/19
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/*PS:channelRead()方法在接收到数据之后会被回调*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws InterruptedException {
Thread.sleep(1000);
System.out.println("4、【有梦想的肥宅】Server开始接收数据:" + sdf.format(new Date()));
//1、强转接收到的消息内容
ByteBuf byteBuf = (ByteBuf) msg;
//2、在服务端输出接收到的消息内容
Thread.sleep(1000);
System.out.println("5、【有梦想的肥宅】Server接收到消息:" + byteBuf.toString(StandardCharsets.UTF_8) + sdf.format(new Date()));
//3、向服务端输出内容【类似客户的channelActive()方法的操作】
System.out.println("6、【有梦想的肥宅】server开始回复数据:" + sdf.format(new Date()));
//3.1 获取二进制抽象ByteBuf对象
ByteBuf byteBufToClient = ctx.alloc().buffer();
//3.2 准备数据,并指定字符串的字符集为UTF-8
byte[] bytes = "【有梦想的肥宅】server回复消息啦~".getBytes(StandardCharsets.UTF_8);
//3.3 填充数据到ByteBuf对象
byteBufToClient.writeBytes(bytes);
//3.4 写数据
Thread.sleep(1000);
ctx.channel().writeAndFlush(byteBufToClient);
System.out.println("7、【有梦想的肥宅】Server回复数据完毕:" + sdf.format(new Date()));
}
}
四、执行效果和小结
为了方便小伙伴们查看流程执行状况,我特意做了Thread.sleep(1000),并给输出日志加上了序号和时间,执行效果如下:
小结如下:
- 1、Client先连接上了Server,由于我们自定义的ClinetHandler类重写了ChannelInboundHandlerAdapter的channelActive()方法,在连接上服务端时,会去调用此方法
- 2、执行channelActive()方法,向服务端输出内容
- 3、又由于我们自定义的ServerHandler类重写了ChannelInboundHandlerAdapter的channelRead()方法,当接收到客户端发来的数据之后会被调用
- 4、服务端输出接收到的消息内容,并向客户端回复消息
- 5、我们自定义的ClinetHandler类还重写了ChannelInboundHandlerAdapter的channelRead()方法,能够读取到服务端回复的消息并处理
- 6、客户端收到服务端回复的消息,并把消息内容打印在控制台上,整个双向通信流程结束。