基于Netty实现UDP双向通信

1、Channel继承关系

关于ChannelPipeline原理可参考:https://blog.csdn.net/qq_21033663/article/details/105674261
在这里插入图片描述

2、NIO Channel分类

1)NioDatagramChannel:发送和接收数据包,支持TCP和UDP,对DatagramSocket和selector进行封装
2)NioServerSocketChannel:服务端使用,对JDK的ServerSocketChannel进行了封装
3)NioSocketChannel:客户端使用

3、UDP Demo

1)Server

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;

public class UdpServerTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootStrap = new Bootstrap();
        bootStrap.group(group)
                .channel(NioDatagramChannel.class) // 指定传输数据包,可支持UDP
                .option(ChannelOption.SO_BROADCAST, true) // 广播模式
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) // 线程池复用缓冲区
                .handler(new UDPServerHandler());
        Channel channel = bootStrap.bind(8888).sync().channel();
        // 这里阻塞
        channel.closeFuture().await();
    }

    static class UDPServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
    
    

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
    
    
            // 读取数据
            ByteBuf byteBuf = packet.content();
            byte[] bytes = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(bytes);
            System.out.println("receive client msg:" + new String(bytes));
            String test = "我是server";
            byte[] resBytes = test.getBytes();
            DatagramPacket sendPacket = new DatagramPacket(Unpooled.copiedBuffer(resBytes), packet.sender());
            ctx.writeAndFlush(sendPacket);
        }
    }
}

2)Client

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.net.InetSocketAddress;

public class UdpClientTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
                .channel(NioDatagramChannel.class)
                .option(ChannelOption.SO_BROADCAST, true)
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .handler(new UdpClientHandler());
        Channel ch = b.bind(7777).sync().channel();
        String test = "我是client";
        byte[] bytes = test.getBytes();
        ByteBuf byteBuf = Unpooled.copiedBuffer(bytes);
        ch.writeAndFlush(new DatagramPacket(byteBuf,
                    new InetSocketAddress("127.0.0.1", 8888))).sync();
        // 客户端等待10s用于接收服务端的应答消息,然后退出并释放资源
        ch.closeFuture().await(10000);

    }

    private static class UdpClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {
    
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) {
    
    
            ByteBuf byteBuf = msg.content();
            byte[] bytes = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(bytes);
            System.out.println("receive server msg:" + new String(bytes));
        }
    }
}

4、ChannelOption配置说明

1)ALLOCATOR
  ByteBuf的分配器(重用缓冲区),默认值为ByteBufAllocator.DEFAULT,4.0版本为UnpooledByteBufAllocator,4.1版本为PooledByteBufAllocator。该值也可以使用系统参数io.netty.allocator.type配置
注:Netty4.1使用对象池,重用缓冲区,可以直接只用如下配置
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
2)RCVBUF_ALLOCATOR
  用于Channel分配接收Buffer的分配器,默认值为AdaptiveRecvByteBufAllocator.DEFAULT,是一个自适应的接受缓冲区分配器,能根据接受到的数据自动调节大小。可选值为FixedRecvByteBufAllocator,固定大小的接受缓冲区分配器
3)MESSAGE_SIZE_ESTIMATOR
  消息大小估算器,默认为DefaultMessageSizeEstimator.DEFAULT。估算ByteBuf、ByteBufHolder和FileRegion的大小,其中ByteBuf和ByteBufHolder为实际大小,FileRegion估算值为0。该值估算的字节数在计算水位时使用,FileRegion为0可知FileRegion不影响高低水位
4)CONNECT_TIMEOUT_MILLIS
  连接超时毫秒数,默认值30000毫秒即30秒
5)MAX_MESSAGES_PER_READ
  一次Loop读取的最大消息数,对于ServerChannel或者NioByteChannel,默认值为16,其他Channel默认值为1。默认值这样设置,是因为:ServerChannel需要接受足够多的连接,保证大吞吐量,NioByteChannel可以减少不必要的系统调用select
6)WRITE_SPIN_COUNT
  一个Loop写操作执行的最大次数,默认值为16。也就是说,对于大数据量的写操作至多进行16次,如果16次仍没有全部写完数据,此时会提交一个新的写任务给EventLoop,任务将在下次调度继续执行。这样,其他的写请求才能被响应不会因为单个大数据量写请求而耽误
7)WRITE_BUFFER_HIGH_WATER_MARK
  写高水位标记,默认值64KB。如果Netty的写缓冲区中的字节超过该值,Channel的isWritable()返回False
8)WRITE_BUFFER_LOW_WATER_MARK
  写低水位标记,默认值32KB。当Netty的写缓冲区中的字节超过高水位之后若下降到低水位,则Channel的isWritable()返回True。写高低水位标记使用户可以控制写入数据速度,从而实现流量控制。推荐做法是:每次调用channl.write(msg)方法首先调用channel.isWritable()判断是否可写
9)WRITE_BUFFER_WATER_MARK
10)ALLOW_HALF_CLOSURE
  关闭连接时,允许半关,默认不允许
11)AUTO_READ
  自动读取,默认值为True。Netty只在必要的时候才设置关心相应的I/O事件。对于读操作,需要调用channel.read()设置关心的I/O事件为OP_READ,这样若有数据到达才能读取以供用户处理。该值为True时,每次读操作完毕后会自动调用channel.read(),从而有数据到达便能读取;否则,需要用户手动调用channel.read()。需要注意的是:当调用config.setAutoRead(boolean)方法时,如果状态由false变为true,将会调用channel.read()方法读取数据;由true变为false,将调用config.autoReadCleared()方法终止数据读取
12)AUTO_CLOSE
13)SO_BROADCAST
  广播
14)SO_KEEPALIVE
  连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是7200s即2小时。Netty默认关闭该功能
15)SO_SNDBUF
  设置发送缓冲区的大小,发送缓冲区用于保存发送数据,直到发送成功
  TCP数据发送缓冲区大小。该缓冲区即TCP发送滑动窗口,linux操作系统可使用命令:cat /proc/sys/net/ipv4/tcp_smem查询其大小
16)SO_RCVBUF
  设置接收缓冲区的大小,接收缓冲区用于保存网络协议站内收到的数据,直到程序读取成功
  TCP数据接收缓冲区大小。该缓冲区即TCP接收滑动窗口,linux操作系统可使用命令:cat /proc/sys/net/ipv4/tcp_rmem查询其大小。一般情况下,该值可由用户在任意时刻设置,但当设置值超过64KB时,需要在连接到远端之前设置。
17)SO_REUSEADDR
  取决于网络环境和自己的信心,如果网络环境好,有信息不会有“迷途知返”的包,并且认为 ACK 可以发到 Server,那么可以考虑开启 SO_REUSEADDR(默认关闭)
18)SO_LINGER
  关闭Socket的延迟时间,默认值为-1,表示禁用该功能。-1表示socket.close()方法立即返回,但OS底层会将发送缓冲区全部发送到对端。0表示socket.close()方法立即返回,OS放弃发送缓冲区的数据直接向对端发送RST包,对端收到复位错误。非0整数值表示调用socket.close()方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。
19)SO_BACKLOG
  BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程都处于工作是(用完了),用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50
20)SO_TIMEOUT
  用于设置接受数据的等待的超时时间,单位为毫秒,它的默认值是0,表示无限等待。
21)IP_TOS
  IP参数,设置IP头部的Type-of-Service字段,用于描述IP包的优先级和QoS选项
22)IP_MULTICAST_ADDR
  对应IP参数IP_MULTICAST_IF,设置对应地址的网卡为多播模式
23)IP_MULTICAST_IF
  对应IP参数IP_MULTICAST_IF2,同上但支持IPV6
24)IP_MULTICAST_TTL
  IP参数,多播数据报的time-to-live即存活跳数
25)IP_MULTICAST_LOOP_DISABLED
  对应IP参数IP_MULTICAST_LOOP,设置本地回环接口的多播功能。由于IP_MULTICAST_LOOP返回True表示关闭,所以Netty加上后缀_DISABLED防止歧义。
26)TCP_NODELAY
  对应于socket选项中的TCP_NODELAY,该参数的使用和Nagle算法有关,Nagle算法是将小的数据包组装为更大的帧进行发送,而不会来一个数据包发送一次,目的是为了提高每次发送的效率,因此在数据包没有组成足够大的帧时,就会延迟该数据包的发送,虽然提高了网络负载却造成了延时,TCP_NODELAY参数设置为true,就可以禁用Nagle算法,即使用小数据包即时传输
  TCP_NODELAY就是用于启用或关闭Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数减少网络交互,就设置为false等累积一定大小后再发送。默认为false。
27)DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION
  DatagramChannel注册的EventLoop即表示已激活
28)SINGLE_EVENTEXECUTOR_PER_GROUP
  单线程执行ChannelPipeline中的事件,默认值为True。该值控制执行ChannelPipeline中执行ChannelHandler的线程。如果为True,整个pipeline由一个线程执行,这样不需要进行线程切换以及线程同步,是Netty4的推荐做法;如果为False,ChannelHandler中的处理过程会由Group中的不同线程执行

猜你喜欢

转载自blog.csdn.net/qq_21033663/article/details/113773141