基于Netty实现NIO模型UDP服务端

一、NIO模型简述

NIO (Non-blocking IO),称之为非阻塞IO,传输过程如下:
NIO模型

  • 在NIO模式下,当用户进程执行系统调用后,如果当前数据还没有准备好,则会立即返回(NIO的非阻塞就提现在这里),然后再次进行系统调用,不断测试数据是否准备好。如果数据准备好了,当前进程会进入阻塞转态,直到数据从内核空间拷贝到用户空间,进程才会被唤醒,就可以处理数据了。

二、代码实现

1. 创建springBoot项目
目录结构:
在这里插入图片描述
2. 导入Maven依赖配置

		<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.53.Final</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.janino</groupId>
            <artifactId>janino</artifactId>
            <version>3.0.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

3. 创建UDP服务端并绑定端口

package com.demo.nettyDemo.udp;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;

/**
 * @title
 * @date 2023/7/19 13:44
 **/
public class NettyUdp implements Runnable {
    // UDP服务端口
    private static int PORT = 15001;
    // 缓冲区默认最小值
    private static int DEFAULT_MINIMUM = 512;
    // 缓冲区默认初始值
    private static int DEFAULT_INITIAL = 2048;
    // 缓冲区默认最大值
    private static int DEFAULT_MAXIMUM = 65536;
    
    public static void main(String[] args) {
        new NettyUdp().run();
    }

    @Override
    public void run() {
        // 创建Netty线程组,用于接收请求和处理IO请求
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        // 创建netty客户端启动器
        Bootstrap b = new Bootstrap();
        // 创建客户端与服务器的交互管道
        Channel channel;
        // 设置线程模型
        b.group(eventLoopGroup)
                // 指明网络通讯的是udp通讯
                .channel(NioDatagramChannel.class)
                // 设置netty接收缓冲区大小
                .option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(DEFAULT_MINIMUM,
                        DEFAULT_INITIAL, DEFAULT_MAXIMUM))
                // 自定义处理接收的数据
                .handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel nch) throws Exception {
                        // 在处理器链的最后添加一个NettyHandler
                        nch.pipeline().addLast(new NettyHandler());
                    }
                });
        try {
            // 绑定端口
            channel = b.bind(PORT).sync().channel();
            // 阻塞主线程,等待服务端关闭
            channel.closeFuture().sync().await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

4. 自定义数据包处理器

package com.demo.nettyDemo.udp;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;

/**
 * @title
 * @date 2023/7/19 14:09
 **/
@Slf4j(topic = "demoLog")
public class NettyHandler  extends SimpleChannelInboundHandler<DatagramPacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        // 接收数据包的内容写入byte数组缓冲区
        ByteBuf buf = msg.content();
        // 读取byte数组缓冲区的长度
        int len = buf.readableBytes();
        // 创建字节数组,用于接收数据
        byte[] data = new byte[len];
        // 获取服务端的IP地址和端口号
        InetSocketAddress inetSocketAddress = msg.sender();
        // 将byte数组缓冲区的内容写入byte数组
        buf.readBytes(data);
        // 将字节数组转为string
        String receive = new String(data, 0, len);
        log.info(inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort() + ":" + receive);
    }
}

5. 在启动类中添加UDP服务启动线程

@SpringBootApplication
public class NettyDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(NettyDemoApplication.class, args);
        // 开启线程启动UDP服务端
        Thread udpThread = new Thread(new NettyUdp());
        udpThread.start();
    }
}

三、丢包率测试

  1. 使用Jmeter请求UDP端口,模拟高并发请求
  2. 测试情况:
    • VMware:CentOS 7 2核4G
RPS测试时长(分钟)请求总量丢包数丢包率
400051197850829706.93%
30005895,53825,3752.83%
20005600,18816,4792.75%
  1. 监控方法
  1. 修改内核中Sokcet接收缓冲区后测试
    • VMware:CentOS 7 2核4G
    • net.core.rmem_default = 1048576
    • net.core.rmem_max = 1048576
QPS测试时长(分钟)请求总量丢包数丢包率
400051,197,39511,6090.97%
3000589,54383,6900.41%
20005600,20100.00%

四、结论

经过并发测试,简单优化调整下系统的socket接收缓冲区,效果依然没有达到预期,接下来将持续进行优化。

项目源码git地址

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值