基于Netty的TCP协议的Socket服务端

2025博客之星年度评选已开启 10w+人浏览 3.6k人参与

01 引言

上一节分享Websocket独立部署的一个设计思路,我们今天接着聊一下基于Netty的TCP协议的Socket服务端如何搭建。这个对于熟悉的人可能很简单,但是对于新手或者不常用的开发者来说,可能一头雾水。

小编在初次使用Socket的时候,都是度娘一大堆,然后抄抄抄,完成自己的任务。至于为什么这么做,完全不知道。这一节将自己的理解分享并记录下来,以备不时之需。

02 服务端案例

2.1 代码展示

public void start() {
    // 创建线程组
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workGroup = new NioEventLoopGroup(5);

    try {
        // 服务端类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 添加组
        serverBootstrap.group(bossGroup, workGroup);
        // 设置NioServerSocketChannel通道
        serverBootstrap.channel(NioServerSocketChannel.class);
        // 连接队列大小
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)// 保持连接
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); 
        serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>(){
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 设置处理器链,依次执行
                ChannelPipeline pipeline = socketChannel.pipeline();
                pipeline.addLast(new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer("_".getBytes())));
                pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
                pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
                // 自定义的handler,处理业务逻辑
                pipeline.addLast(new BusinessHandler<>());    
            }
        });

        // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = serverBootstrap.bind(9091).sync();
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    } catch (Exception e) {
        log.error("信息异常:", e);
    }finally {
        bossGroup.close();
        workGroup.close();
    }
}

2.2 创建线程组

 // 创建线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup(5);

EventLoopGroup就是一个线程池。在NettySocket中需要两个不同的线程池,分别处理不同的任务。其中bossGroup用来接收客户端连接,通常设置1个线程,而workGroup用来处理I/O操作和业务逻辑,可以根据CPU的核心数指定。

2.3 创建服务端

// 服务端类
ServerBootstrap bootstrap = new ServerBootstrap();
// 添加组
serverBootstrap.group(bossGroup, workGroup);
// 设置NioServerSocketChannel通道
serverBootstrap.channel(NioServerSocketChannel.class);
// 连接队列大小
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)// 保持连接
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);

服务端引导类创建完成之后,需要设置参数:

  • group():添加线程池组,第一个参数为bossGroup,第二个是workGroup
  • channel():设置NioServerSocketChannel通道
  • option():配置服务端监听的ServerSocketChannel
  • childOption():配置客户端连接的SocketChannel

所以这里的ChannelOption.SO_BACKLOG只能设置在option中,用来指定服务端接收的任务最大队列。ChannelOption.SO_KEEPALIVE用来TCP保活机制,检测死连接,是针对客户端的连接,所以需要设置在childOption上。

ChannelOption.SO_KEEPALIVE也可以不同设置,采用心跳机制来保活。其使用有一定的局限性,通常都会通过心跳机制来代替。

2.4 设置处理链

serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>(){
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 设置处理器链,依次执行
        ChannelPipeline pipeline = socketChannel.pipeline();
      
        pipeline.addLast("..."); 
        pipeline.addLast("...");  
    }
});

采用责任链模式,每个处理器处理特定任务,依次执行。里面具体的Handler单独说明。

2.5 绑定端口,同步阻塞

// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
ChannelFuture channelFuture = serverBootstrap.bind(9091).sync();
// 对关闭通道进行监听
channelFuture.channel().closeFuture().sync();

当前服务端绑定一个端口,客户端就可以通过当前端口连接,并同步阻塞,等待端口绑定成功。最后同步阻塞等待通过关闭。

03 消息处理

消息的处理是接受和推送消息重要部分。Netty框架提供了丰富的处理器,我们可以选择适合自己的处理器。处理器都是实现io.netty.channel.ChannelInboundHandler接口

3.1 框架自带编解码器

Netty框架下的Socket数据传输,默认都是ByteBuf(字节缓冲)。我们使用的时候自然想通过常用的字符串传输,而Netty自然帮我们提供了字符串相关的编解码处理器。

  • io.netty.handler.codec.string.StringDecoder
  • io.netty.handler.codec.string.StringEncoder

通过源码我们可以看到注释:

StringDecoder是将ByteBuf转成字符串的解码器,但是在处理之前必须使用ByteToMessageDecoder先解码,子类包括:

  • io.netty.handler.codec.DelimiterBasedFrameDecoder:分隔符分割
  • io.netty.handler.codec.FixedLengthFrameDecoder:固定长度分割
  • io.netty.handler.codec.LengthFieldBasedFrameDecoder:按照字段长度分割
  • io.netty.handler.codec.LineBasedFrameDecoder:按行分割

这几种方式都是有效防止拆包、粘包的方法。

按照注释的案例,我们就可以配置。而StringEncoder是用来发送消息的解码器,用来将字符串转成ByteBuf

我们这里采用分隔符的方式分割:

pipeline.addLast(new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer("_".getBytes())));
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));

而其中DelimiterBasedFrameDecodermaxFrameLength参数用来控制接收消息的最大字节大小,超过就会异常。

3.2 自定义业务处理器

自定义业务处理器是用来处理客户端连接以及消息的。

@Slf4j
public class BusinessHandler extends SimpleChannelInboundHandler {
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 建立客户端
        Channel channel = ctx.channel();
        log.info("Socket客户端建立连接:channelId={}", channel.id());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 断开链接
        Channel channel = ctx.channel();
        log.info("Socket客户端断开连接:channelId={}", channel.id());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 接受消息
        Channel channel = ctx.channel();
        log.info("Socket收到来自通道channelId[{}]发送的消息:{}", channel.id(), msg);
        // 通过WebSocket将方法发送给客户端
        channel.writeAndFlush(msg + "789_000");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("异常:", cause);
    }
}

handlerAdded()

客户端建立连接之后会触发该方法。可以通过ctx.channel()获取来连接的通道(客户端)。连接的通常可以通过io.netty.channel.group.ChannelGroup收集。

ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE)
channelGroup.add(channel);

向客户端发送消息时,可以通过channelGroup.writeAndFlush()统一给客户端发送消息。

handlerRemoved()

客户端断开连接的时触发,可以通过channelGroup.remove(channel)移除已经关闭的客户端通道

channelRead0()

接收客户端消息的重要方法,通过channel.writeAndFlush()可以直接向客户端发送消息

exceptionCaught()

处理异常的方法

3.3 客户端测试

从图上可以看出,介绍的方法都被触发了。

从图可以看出客户端的也接收到服务端的消息了。

注意

客户端发送的消息:foo test…_

服务端发送的消息:foo test…789_000

客户端接受的消息:foo test…789

客户端和服务端接收到的消息都通过_截断的

04 小结

简单的服务端搭建就已经好了,但是实际应用的时候,还需要考虑心跳机制、以及无效客户端的清理等。TCP协议服务端的介绍就到这里,客户端我们下一期介绍。

计及源荷不确定性的综合能源生产单元运行调度与容量配置优化研究(Matlab代码实现)内容概要:本文围绕“计及源荷不确定性的综合能源生产单元运行调度与容量配置优化”展开研究,利用Matlab代码实现相关模型的构建与仿真。研究重点在于综合能源系统中多能耦合特性以及风、光等可再生能源出力和负荷需求的不确定性,通过鲁棒优化、场景生成(如Copula方法)、两阶段优化等手段,实现对能源生产单元的运行调度与容量配置的协同优化,旨在提高系统经济性、可靠性和可再生能源消纳能力。文中提及多种优化算法(如BFO、CPO、PSO等)在调度与预测中的应用,并强调了模型在实际能源系统规划与运行中的参考价值。; 适合人群:具备一定电力系统、能源系统或优化理论基础的研究生、科研人员及工程技术人员,熟悉Matlab编程和基本优化工具(如Yalmip)。; 使用场景及目标:①用于学习和复现综合能源系统中考虑不确定性的优化调度与容量配置方法;②为含高比例可再生能源的微电网、区域能源系统规划设计提供模型参考和技术支持;③开展学术研究,如撰写论文、课题申报时的技术方案借鉴。; 阅读建议:建议结合文中提到的Matlab代码和网盘资料,先理解基础模型(如功率平衡、设备模型),再逐步深入不确定性建模与优化求解过程,注意区分鲁棒优化、随机优化与分布鲁棒优化的适用场景,并尝试复现关键案例以加深理解。
内容概要:本文系统分析了DesignData(设计数据)的存储结构,围绕其形态多元化、版本关联性强、读写特性差异化等核心特性,提出了灵活性、版本化、高效性、一致性和可扩展性五大设计原则。文章深入剖析了三类主流存储方案:关系型数据库适用于结构化元信息存储,具备强一致性与高效查询能力;文档型数据库适配半结构化数据,支持动态字段扩展与嵌套结构;对象存储结合元数据索引则有效应对非结构化大文件的存储需求,具备高扩展性与低成本优势。同时,文章从版本管理、性能优化和数据安全三个关键维度提出设计要点,建议采用全量与增量结合的版本策略、索引与缓存优化性能、并通过权限控制、MD5校验和备份机制保障数据安全。最后提出按数据形态分层存储的核心结论,并针对不同规模团队给出实践建议。; 适合人群:从事工业设计、UI/UX设计、工程设计等领域数字化系统开发的技术人员,以及负责设计数据管理系统架构设计的中高级工程师和系统架构师。; 使用场景及目标:①为设计数据管理系统选型提供依据,合理选择或组合使用关系型数据库、文档型数据库与对象存储;②构建支持版本追溯、高性能访问、安全可控的DesignData存储体系;③解决多用户协作、大文件存储、历史版本管理等实际业务挑战。; 阅读建议:此资源以实际应用场景为导向,结合具体数据库类型和表结构设计进行讲解,建议读者结合自身业务数据特征,对比分析不同存储方案的适用边界,并在系统设计中综合考虑成本、性能与可维护性之间的平衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智_永无止境

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值