✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:实战🍅
✈️本篇内容: Netty基础实战✈️
🍱本篇收录完整代码地址:gitee.com/diqirenge/a…🍱
楔子
学习一门技术,少不了看他的示例demo,本篇文章就以10个小例子,带你轻松掌握Netty开发的基础技能。
最简单的服务端例子
模块名称
demo-netty-001
仓库地址
模块描述
最简单的服务端例子
代码实现
添加依赖
<dependencies>
<!-- Netty4.1 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty_version}</version>
</dependency>
</dependencies>
<properties>
<netty_version>4.1.36.Final</netty_version>
</properties>
编写NettyServer
/**
* Netty服务端
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class NettyServer {
public static void main(String[] args) {
// 启动一个server,设置端口是7777
new NettyServer().bing(7777);
}
private void bing(int port) {
//配置服务端NIO线程组
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
// 非阻塞模式
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new MyChannelInitializer());
ChannelFuture f = b.bind(port).sync();
System.out.println("服务端启动成功,端口是:" + port);
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
childGroup.shutdownGracefully();
parentGroup.shutdownGracefully();
}
}
}
继承ChannelInitializer,实现自己的管道初始化器
/**
* 管道初始化器
* 用于初始化管道,在服务端链接建立时,会调用initChannel方法
* <p>
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
System.out.println("[初始化管道时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
+ ",服务端端口:" + channel.localAddress().getPort());
}
}
测试
1、下载NetAssist网络调试工具
2、启动服务端,输出以下结果
服务端启动成功,端口是:7777
3、启动NetAssist网络调试工具
(1)协议类型选择TCP Client
(2)服务器端口填写7777
(3)点击连接
控制台输出:
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
增加服务端处理客户端信息
模块名称
demo-netty-002
仓库地址
模块描述
增加服务端处理客户端信息
代码实现
继承ChannelInboundHandlerAdapter 实现channelRead方法
/**
* Netty 服务端处理类
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//接收msg消息
ByteBuf buf = (ByteBuf) msg;
byte[] msgByte = new byte[buf.readableBytes()];
buf.readBytes(msgByte);
System.out.print(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " [服务端]接收到[客户端]消息:");
System.out.println(new String(msgByte, Charset.forName("GBK")));
}
}
修改MyChannelInitializer,添加我们处理数据的方法
protected void initChannel(SocketChannel channel) {
System.out.println("[初始化管道时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
+ ",服务端端口:" + channel.localAddress().getPort());
// 添加我们处理数据的方法
channel.pipeline().addLast(new MyServerHandler());
}
测试
1、启动服务端,输出以下结果
服务端启动成功,端口是:7777
2、启动NetAssist网络调试工具
(1)协议类型选择TCP Client
(2)服务器端口填写7777
(3)点击连接
控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
(4)发送消息
关注公众号【奔跑的码畜】,一起进步不迷路
控制台输出
2023-11-08 14:52:16 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路
在管道中添加基于换行符的解码器
模块名称
demo-netty-003
仓库地址
模块描述
在管道中添加基于换行符的解码器
代码实现
在initChannel方法中添加以下代码
// 基于换行符号,也就是说要在客户端换行,服务端才能接收到数据
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
修改MyServerHandler
/**
* Netty 服务端处理类
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
SocketChannel channel = (SocketChannel) ctx.channel();
System.out.println("[管道激活时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
+ ",服务端端口:" + channel.localAddress().getPort());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " [服务端]接收到[客户端]消息:" + msg);
}
}
测试
1、启动服务端,输出以下结果
服务端启动成功,端口是:7777
2、启动NetAssist网络调试工具
(1)协议类型选择TCP Client
(2)服务器端口填写7777
(3)点击连接,控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
(4)发送消息
关注公众号【奔跑的码畜】,一起进步不迷路
①不换行
控制台没有输出
②换行
控制台输出如下
2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路
添加断开链接以及异常处理
模块名称
demo-netty-004
仓库地址
模块描述
添加断开链接以及异常处理
代码实现
在MyServerHandler中添加断开连接处理
@Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " [客户端]断开链接" + "服务端[" + ctx.channel().localAddress().toString() + "]");
}
在MyServerHandler中添加异常处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
System.out.println("异常信息:\r\n" + cause.getMessage());
}
测试
1、启动服务端,输出以下结果
服务端启动成功,端口是:7777
2、启动NetAssist网络调试工具
(1)协议类型选择TCP Client
(2)服务器端口填写7777
(3)点击连接,控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
(4)发送消息(末尾换行)
关注公众号【奔跑的码畜】,一起进步不迷路
控制台输出如下
2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路
(5)点击断开
控制台输出
2023-11-08 14:58:47 [客户端]断开链接服务端[/169.254.232.1:7777]
使用writeAndFlush,通知客户端消息
模块名称
demo-netty-005
仓库地址
模块描述
使用writeAndFlush,通知客户端消息
代码实现
在MyServerHandler使用writeAndFlush,通知客户端消息
@Override
public void channelActive(ChannelHandlerContext ctx) {
SocketChannel channel = (SocketChannel) ctx.channel();
System.out.println("[管道激活时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
+ ",服务端端口:" + channel.localAddress().getPort());
//通知客户端链接建立成功
String str = "[管道激活时]通知客户端链接建立成功" + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " "
+ channel.localAddress().getHostString() + "\r\n";
ctx.writeAndFlush(str);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " [服务端]接收到[客户端]消息:" + msg);
//通知客户端链消息发送成功
String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " 服务端已经接收到信息了,消息内容[" + msg + "]" + "\r\n";
ctx.writeAndFlush(str);
}
因为要通知客户端,所以我们在initChannel方法中添加编码器
// 编码
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
测试
1、启动服务端,输出以下结果
服务端启动成功,端口是:7777
2、启动NetAssist网络调试工具
(1)协议类型选择TCP Client
(2)服务器端口填写7777
(3)点击连接
①控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
②NetAssist输出
【Receive from 169.254.232.1 : 7777】:[管道激活时]通知客户端链接建立成功 2023-11-08 15:00:57 169.254.232.1
(4)发送消息(末尾换行)
关注公众号【奔跑的码畜】,一起进步不迷路
①控制台输出
2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路
②NetAssist输出
2023-11-08 15:02:00 服务端已经接收到信息了,消息内容[关注公众号【奔跑的码畜】,一起进步不迷路]
(5)点击断开
控制台输出如下
2023-11-08 14:58:47 [客户端]断开链接服务端[/169.254.232.1:7777]
添加管道组
模块名称
demo-netty-006
仓库地址
模块描述
添加管道组
代码实现
添加管道处理类,放一个默认组就行了
/**
* 管道处理类
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class ChannelHandler {
// 自定义管道组
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
修改MyServerHandler
(1)channelActive
//当有客户端链接后,添加到channelGroup通信组
ChannelHandler.channelGroup.add(ctx.channel());
(2)channelRead
//收到消息后,群发给客户端
ChannelHandler.channelGroup.writeAndFlush(str);
(3)channelInactive
// 当有客户端退出后,从channelGroup中移除。
ChannelHandler.channelGroup.remove(ctx.channel());
测试
1、启动服务端,输出以下结果
服务端启动成功,端口是:7777
2、启动NetAssist网络调试工具
(1)协议类型选择TCP Client
(2)服务器端口填写7777
(3)点击连接
(4)重复以上步骤,再开一个客户端
(5)选择任意客户端发送消息(末尾换行)
关注公众号【奔跑的码畜】,一起进步不迷路
①控制台输出
2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路
②NetAssist客户端①输出
2023-11-08 15:02:00 服务端已经接收到信息了,消息内容[关注公众号【奔跑的码畜】,一起进步不迷路]
③NetAssist客户端②输出
2023-11-08 15:02:00 服务端已经接收到信息了,消息内容[关注公众号【奔跑的码畜】,一起进步不迷路]
(5)断开任意NetAssist客户端
控制台输出如下
2023-11-08 14:58:47 [客户端]断开链接服务端[/169.254.232.1:7777]
最简单的客户端例子
模块名称
demo-netty-007
仓库地址
模块描述
最简单的客户端例子
代码实现
编写Netty客户端
/**
* Netty客户端
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class NettyClient {
public static void main(String[] args) {
new NettyClient().connect("127.0.0.1", 7777);
}
private void connect(String inetHost, int inetPort) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.AUTO_READ, true);
b.handler(new MyChannelInitializer());
ChannelFuture f = b.connect(inetHost, inetPort).sync();
System.out.println("客户端启动成功...");
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
编写对应的管道初始化器
/**
* 管道初始化器
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/07
*/
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
System.out.println("[初始化管道时]客户端链接到服务端。channelId:"+channel.id());
}
}
测试
1、启动服务端
服务端控制台输出
服务端启动成功,端口是:7777
2、启动客户端NettyClient
①客户端控制台输出
[初始化管道时]客户端链接到服务端。channelId:bbcdec88
客户端启动成功...
②服务端控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
3、关闭客户端NettyClient
服务端控制台输出
异常信息:
远程主机强迫关闭了一个现有的连接。
2023-11-08 15:16:34 [客户端]断开链接服务端[/127.0.0.1:7777]
参考服务端编写客户端数据处理代码
模块名称
demo-netty-008
仓库地址
模块描述
参考服务端编写客户端数据处理代码
代码实现
修改client下的MyChannelInitializer
/**
* 管道初始化器
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/08
*/
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
System.out.println("[初始化管道时]客户端链接到服务端。channelId:" + channel.id());
// 添加我们处理数据的方法
// 基于换行符号,也就是说要在客户端换行,服务端才能接收到数据
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
// 编码
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
//在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new MyClientHandler());
}
}
编写MyClientHandler
/**
* Netty 客户端处理类
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/08
*/
public class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
SocketChannel channel = (SocketChannel) ctx.channel();
System.out.println("[管道激活时]监听到有一客户端链接到服务端...channelId:" + channel.id());
// 通知服务端链接建立成功
String str = "[管道激活时]通知服务端链接建立成功" + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " "
+ channel.id() + "\r\n";
ctx.writeAndFlush(str);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " [客户端]接收到[服务端]消息:" + msg);
//通知客户端链消息发送成功
String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " 客户端已经接收到信息了,消息内容[" + msg + "]" + "\r\n";
// 收到消息后,通知服务端
ctx.writeAndFlush(str);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " [客户端]断开链接" + ",channelId[" + ctx.channel().id().toString() + "]";
System.out.println(str);
// 收到消息后,通知服务端
ctx.writeAndFlush(str);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
System.out.println("异常信息:\r\n" + cause.getMessage());
}
}
测试
1、启动服务端
服务端控制台输出
服务端启动成功,端口是:7777
2、启动客户端NettyClient
①客户端控制台输出
[初始化管道时]客户端链接到服务端。channelId:5c8eae43
客户端启动成功...
[管道激活时]监听到有一客户端链接到服务端...channelId:5c8eae43
②服务端控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:21:07 [服务端]接收到[客户端]消息:[管道激活时]通知服务端链接建立成功 2023-11-08 15:21:07 5c8eae43
3、关闭客户端NettyClient
服务端控制台输出
异常信息:
远程主机强迫关闭了一个现有的连接。
2023-11-08 15:16:34 [客户端]断开链接服务端[/127.0.0.1:7777]
ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter
模块名称
demo-netty-009
仓库地址
模块描述
ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter
代码实现
编写入站消息处理
/**
* 入站消息处理
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/08
*/
public class MyInMsgHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
SocketChannel channel = (SocketChannel) ctx.channel();
System.out.println("[管道激活时]监听到有一客户端链接到服务端...channelId:" + channel.id());
// 通知服务端链接建立成功
String str = "[管道激活时]通知服务端链接建立成功" + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " "
+ channel.id() + "\r\n";
ctx.writeAndFlush(str);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " [客户端]接收到[服务端]消息:" + msg);
//通知客户端链消息发送成功
String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " 客户端已经接收到信息了,消息内容[" + msg + "]" + "\r\n";
// 收到消息后,通知服务端
ctx.writeAndFlush(str);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " [客户端]断开链接" + ",channelId[" + ctx.channel().id().toString() + "]";
System.out.println(str);
// 收到消息后,通知服务端
ctx.writeAndFlush(str);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
System.out.println("异常信息:\r\n" + cause.getMessage());
}
}
编写出站消息处理
/**
* 出站消息处理
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/11/08
*/
public class MyOutMsgHandler extends ChannelOutboundHandlerAdapter {
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("触发[MyOutMsgHandler.read]方法\r\n");
super.read(ctx);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ctx.writeAndFlush("触发[MyOutMsgHandler.write]方法\r\n");
super.write(ctx, msg, promise);
}
}
测试
1、启动服务端
服务端控制台输出
服务端启动成功,端口是:7777
2、启动客户端NettyClient
①客户端控制台输出
[初始化管道时]客户端链接到服务端。channelId:62e77898
客户端启动成功...
[管道激活时]监听到有一客户端链接到服务端...channelId:62e77898
②服务端控制台输出
[初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:24:03 [服务端]接收到[客户端]消息:[管道激活时]通知服务端链接建立成功 2023-11-08 15:24:03 62e77898
2023-11-08 15:24:03 [服务端]接收到[客户端]消息:触发[MyOutMsgHandler.read]方法
3、关闭客户端NettyClient
服务端控制台输出
异常信息:
远程主机强迫关闭了一个现有的连接。
2023-11-08 15:16:34 [客户端]断开链接服务端[/127.0.0.1:7777]
netty与springboot集成
模块名称
demo-netty-010
仓库地址
模块描述
netty与springboot集成的例子
代码实现
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
</dependencies>
添加配置文件application.yml
server:
port:8080
netty:
host: 127.0.0.1
port: 7777
编写客户端代码
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
private final Logger logger = LoggerFactory.getLogger(MyChannelInitializer.class);
@Override
protected void initChannel(SocketChannel channel) {
logger.info("[初始化管道时]监听到有一客户端链接到服务端...服务端IP:{},服务端端口:{}", channel.localAddress().getHostString()
, channel.localAddress().getPort());
// 添加我们处理数据的方法
// 基于换行符号,也就是说要在客户端换行,服务端才能接收到数据
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
// 编码
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
//在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new MyServerHandler());
}
}
public class MyServerHandler extends ChannelInboundHandlerAdapter {
private final Logger logger = LoggerFactory.getLogger(MyServerHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
SocketChannel channel = (SocketChannel) ctx.channel();
logger.info("[管道激活时]监听到有一客户端链接到服务端...服务端IP:{},服务端端口:{}", channel.localAddress().getHostString()
, channel.localAddress().getPort());
ctx.writeAndFlush("您好,欢迎来到【Netty】世界!\r\n");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
logger.info("{} [服务端]接收到[客户端]消息:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
, msg);
ctx.writeAndFlush(msg + "\r\n");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("{} [客户端]断开链接 服务端[{}}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
, ctx.channel().localAddress().toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
System.out.println("异常信息:\r\n" + cause.getMessage());
}
}
public class NettyServer {
private final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private final EventLoopGroup parentGroup = new NioEventLoopGroup();
private final EventLoopGroup childGroup = new NioEventLoopGroup();
private Channel channel;
public ChannelFuture bing(InetSocketAddress address) {
ChannelFuture channelFuture = null;
try {
ServerBootstrap b = new ServerBootstrap();
b.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class) //非阻塞模式
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new MyChannelInitializer());
channelFuture = b.bind(address).syncUninterruptibly();
channel = channelFuture.channel();
} catch (Exception e) {
logger.error("服务端启动失败...{}", e.getMessage());
} finally {
if (null != channelFuture && channelFuture.isSuccess()) {
logger.info("服务端启动成功,端口是:{}", address.getPort());
} else {
logger.error("服务端启动失败...");
}
}
return channelFuture;
}
public void destroy() {
if (null == channel) {
return;
}
channel.close();
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
public Channel getChannel() {
return channel;
}
}
编写NettyController
@RestController
@RequestMapping(value = "/netty/server")
public class NettyController {
@Resource
private NettyServer nettyServer;
@GetMapping("/getAddress")
public String getAddress() {
return "[服务端]地址是:" + nettyServer.getChannel().localAddress();
}
}
编写启动类
@SpringBootApplication
public class NettyApplication implements CommandLineRunner {
@Value("${netty.host}")
private String host;
@Value("${netty.port}")
private int port;
@Resource
private NettyServer nettyServer;
public static void main(String[] args) {
SpringApplication.run(NettyApplication.class, args);
}
@Override
public void run(String... args) {
InetSocketAddress address = new InetSocketAddress(host, port);
ChannelFuture channelFuture = nettyServer.bing(address);
Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.destroy()));
channelFuture.channel().closeFuture().syncUninterruptibly();
}
}
测试
1、编写测试类
public class NettyClientTest {
public static void main(String[] args) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.AUTO_READ, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
// 基于换行符号
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
// 在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 客户端接收到消息:" + msg);
}
});
}
});
ChannelFuture f = b.connect("127.0.0.1", 7777).sync();
// 向服务端发送信息
f.channel().writeAndFlush("www.52javaee.com\r\n");
f.channel().writeAndFlush("公众号:奔跑的码畜\r\n");
f.channel().writeAndFlush("作者:第七人格\r\n");
f.channel().writeAndFlush("向服务端发送信息:netty与springboot的集成例子\r\n");
f.channel().closeFuture().syncUninterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
2、启动NettyApplication
①服务端控制台输出
2023-11-08 15:26:16.825 INFO 5956 --- [ main] org.example.server.NettyServer : 服务端启动成功,端口是:7777
3、启动client测试类
①client测试类控制台输出
2023-11-08 15:27:14 客户端接收到消息:您好,欢迎来到【Netty】世界!
2023-11-08 15:27:14 客户端接收到消息:www.52javaee.com
2023-11-08 15:27:14 客户端接收到消息:公众号:奔跑的码畜
2023-11-08 15:27:14 客户端接收到消息:作者:第七人格
2023-11-08 15:27:14 客户端接收到消息:向服务端发送信息:netty与springboot的集成例子
②服务端控制台输出
2023-11-08 15:27:14.816 INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyChannelInitializer : [初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:27:14.821 INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler : [管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:27:14.843 INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:www.52javaee.com
2023-11-08 15:27:14.843 INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:公众号:奔跑的码畜
2023-11-08 15:27:14.844 INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:作者:第七人格
2023-11-08 15:27:14.844 INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:向服务端发送信息:netty与springboot的集成例子
4、执行http请求
### 获取服务端地址
GET http://localhost:8080/netty/server/getAddress
返回结果
[服务端]地址是:/127.0.0.1:7777