netty(五)Client与Server双向通信
一、本章重点
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、客户端收到服务端回复的消息,并把消息内容打印在控制台上,整个双向通信流程结束。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)