Title

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

posted @   快乐小洋人  阅读(1917)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示