Netty-TCP服务端开发解析

一、简介

1、Java领域诸多大名鼎鼎的项目在网络通讯框架方面几乎都使用了netty,包括SpringCloudGateway、Dubbo、Zookeeper、Kafka、Spark、Hadoop、ElasticSearch、RocketMQ、gRpc等等。因为netty可靠性高、性能好、开发简单、功能丰富、bug修复及时。本篇对使用Netty开发TCP服务端的代码和工作原理做简要介绍。

2、tcp服务端的io操作主要有:Listen:监听端口、accept:接收连接、read:读数据、write:写数据。不论BIO还是NIO的开发,通常将接收连接和读写操作的线程分开,接收连接使用一个线程,读写操作使用另一组线程。接收连接的线程在接收到连接后,把连接送给读写线程去执行读写操作。

二、Netty服务端启动整体代码

public void startServer() {

        //用于服务器端接受客户端的连接

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);

        //用于网络事件的处理

        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workGroup)

                    .channel(NioServerSocketChannel.class)

                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override

                        protected void initChannel(SocketChannel socketChannel) {

                            socketChannel.pipeline().addLast(

                                    new DataEncoder(),

                                    new DataDecoder(),

                                    serverHandler);

                        }

                    })

                    .option(ChannelOption.SO_BACKLOG, 1024)

                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(tcpServerPort).sync();

            logger.info("TcpServer启动成功!");

            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {

            logger.error("创建tcp服务端异常", e);

        } finally {

            bossGroup.shutdownGracefully();

            workGroup.shutdownGracefully();

        }

}

1、准备好两个线程组,分别用于接收连接、读写操作:

2、创建Netty的服务端对象,并进行配置:

        a. new出服务端对象ServerBootstrap:ServerBootstrap serverBootstrap = new ServerBootstrap();

        b. 设置accept接收连接的线程组bossGroup和读写线程组workerGroup:serverBootstrap.group(bossGroup, workerGroup)

        c. 设置IO模型为NIO:.channel(NioServerSocketChannel.class)。

        d. 设置处理器.childHandler(),我们的业务代码就在此指定。

3、启动服务器,绑定端口并启动:ChannelFuture channelFuture = serverBootstrap.bind(tcpServerPort).sync();

4、同步等待服务器关闭,代码执行到这里会阻塞,直到所有的channel都关闭,作为服务端通常不会自动关闭,所有一般不继续执行:channelFuture.channel().closeFuture().sync();

关闭两个线程组:bossGroup.shutdownGracefully();workGroup.shutdownGracefully();

三、服务端开发要点

        许多数据处理的项目或框架都会采用责任链模式,如典型的:Tomcat中的拦截器,请求进来后会顺序经过一系列拦截器,返回响应时则逆序经过同样的拦截器。

netty中,将接收连接的accept线程看做boss或者说是父,将读写线程看做是worker或者说是child子,对childHandler的配置,其实就是配置读写数据的Handler链。

1、服务端启动的大部分代码是套路和参数设置,主要变化的地方是对于childHandler的设置:

        每个tcp连接建立时都会执行这里的initChannel方法,从而生成其对应的数据处理pipeline,pipeline中就是有序的一个个Handler。

netty中的Handler分为入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)两种,当客户端发来数据时,就顺序执行pipeline中的入站Handler们对应方法。反之,如果服务端要给客户端发数据,则会逆序执行pipeline中的出站Handler。一个Handler可以既是入站Handler也是出站Handler,当然对应的处理方法是不同的。

2、业务处理入站Handler,在channelRead方法中,msg参数就是我们的解码器处理之后的数据,通常是一个完整报文,业务逻辑处理完成后,通常使用writeAndFlush方法返回响应报文:

四、报文编解码和tcp粘包拆包问题

1、对于tcp报文处理,网上经常会看到一个问题叫拆包粘包,其本质其实是如何确定报文边界的问题,tcp是面向字节流的传输层协议,在连接建立后,我们可以认为收到的第一个字节是报文的开头,可是该如何确定这个报文的结尾呢?对于报文接收方来说,一个tcp包并不代表一个tcp报文,应用层代码收到的只是一个个的字节或者说字节数组,比如如果经过了1ms仍然没继续收到字节,可不能代表对方已经发送完毕,同理,如果对方连续发送了两个报文,接收方也必须知道中间应该从哪个字节分开。所以双方必须有一个可靠的能确定报文发送完毕的约定,这个约定事实上也是应用层协议功能的一部分。

2、假设约定某个特殊字符为报文的结束标志,如竖线“|”,对于netty,可以在站入口处加一个解码器Handler,把收到的字节全都缓存起来并逐个检查,直到遇到了“|”字符,则表示一个报文结束,然后再把这个报文发给后续的入站Handler处理,其后的Handler则只需要开发单个报文的处理逻辑

3、如下图的编码器和解码器。编码的逻辑是把报文体的长度以8位字符串的形式放在报文开头,不足8位补0处理。这个编码器是个出站Handler,位于pipeline的开头位置,对所有的“响应”报文做统一加头处理。相应的解码器则是个入站Handler,同样位于pipeline的开头位置,对所有入站“请求”报文做统一的解头处理来确认报文边界。因为编码器和解码器的类型不一样,一个是出站一个是入站,所以在pipeline中它俩谁前谁后并无所谓。

注:关于长连接和短连接。服务端和客户端都可以关闭连接,如果处理完一次请求响应就关闭,则为短连接,反之一次连接进行多次交互则为长连接。

netty在每次收到网络数据时会触发一次入站动作,所以在解码器中通常有这样的逻辑:如果收到的字节不足一个完整的报文,则暂存,如果有了完整的报文则再发送给后续入站Handler。

因为有的Handler有暂存数据的逻辑,所以最初配置的initChannel方法中为new创建对象,而对于不需要暂存数据、线程安全的Handler则可以考虑使用@Sharable注解标记并做成单例对象,各连接复用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

坏猫警长

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

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

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

打赏作者

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

抵扣说明:

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

余额充值