springboot使用Netty服务端实现TCP协议通讯
导包
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
服务监听器
package com.zl.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
/**
* 服务监听器
*/
@Slf4j
@Component
public class NettyServer implements ApplicationRunner {
public void start() {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 4000);
//new 一个主线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//new 一个工作线程组
EventLoopGroup workGroup = new NioEventLoopGroup(200);
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer())
.localAddress(socketAddress)
//设置队列大小
.option(ChannelOption.SO_BACKLOG, 1024)
// 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定端口,开始接收进来的连接
try {
ChannelFuture future = bootstrap.bind(socketAddress).sync();
log.info("服务器启动开始监听端口: {}", socketAddress.getPort());
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("服务器开启失败", e);
} finally {
//关闭主线程组
bossGroup.shutdownGracefully();
//关闭工作线程组
workGroup.shutdownGracefully();
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
this.start();
}
}
服务初始化器
package com.zl.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import lombok.extern.slf4j.Slf4j;
/**
* 服务初始化器
*/
@Slf4j
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加编解码,此处代码为解析tcp传过来的参数,为UTF-8格式,可以自定义解码格式
// socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
// socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
// 解析 byte
socketChannel.pipeline().addLast("decoder", new ByteArrayDecoder());
socketChannel.pipeline().addLast("encoder", new ByteArrayEncoder());
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}
服务处理器
package com.zl.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* 服务处理器
*/
@Slf4j
@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static NettyServerHandler nettyServerHandler;
@PostConstruct
public void init() {
nettyServerHandler = this;
}
/**
* 客户端连接会触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("连接成功...");
}
/**
* 客户端发消息会触发
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
byte[] req = (byte[]) msg;
//此处为解析tcp参数所需要的方法!!!自定义
String code = getCode(req);
log.info("服务器收到消息: {}", code);
String[] split = code.split(" ");
StringBuffer sb = new StringBuffer();
for (String str : split) {
sb.append((char) Integer.parseInt(str, 16));
}
log.info("服务器解析消息: {}", sb.toString());
}
/**
* 发生异常触发
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("客户端连接: " + ctx.channel().id().asShortText());
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
clients.remove(ctx.channel());
}
/**
* 解析为十六进制数据
*/
private String getCode(byte[] req) {
String HEXES = "0123456789ABCDEF";
final StringBuilder hex = new StringBuilder(2 * req.length);
for (int i = 0; i < req.length; i++) {
byte b = req[i];
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F))).append(" ");
}
return hex.toString();
}
}
注意
在解析16进制数据的时候注意大小端问题,socket是对TCP/IP协议的封装,TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式;而windows和linux是使用的小端模式,所以在读取的时候需要转化为小端模式读取,一个字节没问题,但是多个字节时需要转换;后面的字节放前面,倒序排放;同个网络和平台可能没关系,例如java虚拟机内部已经进行了处理,但是跨平台的话需要注意;
如:String msg = "00 BC 45";要先变成String str = "45 BC 00";
如果是16进制转10进制,需要考虑对方正负号的影响,如果是字节以FF开头,一定要注意是否要取反。不需要的话就没关系,需要的话就记得转换
if (sixteen.startsWith("FF")){
Long max = Long.parseLong("FFFFFFFF", 16);
Long curLong = Long.parseLong(sixteen, 16);
pos[i] = new Long(curLong - max).intValue();
}
测试
测试使用工具为:Packet Sender
Packet Sender是一款开源的网络数据包传输软件,用于发送和接收TCP,UDP以及SSL(加密的TCP)数据包;该工具不仅易于使用,最重要的是它是一个绝对免费的应用,用户可以无限制的使用所有功能;它可以为从事网络运营工作的用户提供足够多的功能,轻松满足用户对TCP,UDP以及SSL数据包的发送和接收需求。
结果:
参考链接:
https://www.csdn.net/tags/NtDaggysMDI4MjAtYmxvZwO0O0OO0O0O.html
https://blog.csdn.net/weixin_51717204/article/details/120349799
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY