linux netty udp服务端,Linux下Netty实现高性能UDP服务(SO_REUSEPORT)

参考:

https://www.jianshu.com/p/61df929aa98b

SO_REUSEPORT学习笔记:http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

代码示例:https://www.programcreek.com/java-api-examples/index.php?api=io.netty.channel.epoll.EpollDatagramChannel

Linux下UDP丢包问题分析思路:https://www.jianshu.com/p/22b0f89937ef

当前Linux网络应用程序问题

运行在Linux系统上网络应用程序,为了利用多核的优势,一般使用以下比较典型的多进程/多线程服务器模型:

单线程listen/accept,多个工作线程接收任务分发,虽CPU的工作负载不再是问题,但会存在:

单线程listener,在处理高速率海量连接时,一样会成为瓶颈

CPU缓存行丢失套接字结构(socket structure)现象严重

所有工作线程都accept()在同一个服务器套接字上呢,一样存在问题:

多线程访问server socket锁竞争严重

高负载下,线程之间处理不均衡,有时高达3:1不均衡比例

导致CPU缓存行跳跃(cache line bouncing)

在繁忙CPU上存在较大延迟

上面模型虽然可以做到线程和CPU核绑定,但都会存在:

单一listener工作线程在高速的连接接入处理时会成为瓶颈

缓存行跳跃

很难做到CPU之间的负载均衡

随着核数的扩展,性能并没有随着提升

SO_REUSEPORT解决了什么问题

linux man文档中一段文字描述其作用:

The new socket option allows multiple sockets on the same host to bind to the same port, and is intended to improve the performance of multithreaded network server applications running on top of multicore systems.

SO_REUSEPORT支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,解决的问题:

允许多个套接字 bind()/listen() 同一个TCP/UDP端口

每一个线程拥有自己的服务器套接字

在服务器套接字上没有了锁的竞争

内核层面实现负载均衡

安全层面,监听同一个端口的套接字只能位于同一个用户下面

其核心的实现主要有三点:

扩展 socket option,增加 SO_REUSEPORT 选项,用来设置 reuseport。

修改 bind 系统调用实现,以便支持可以绑定到相同的 IP 和端口

修改处理新建连接的实现,查找 listener 的时候,能够支持在监听相同 IP 和端口的多个 sock 之间均衡选择。

Netty使用SO_REUSEPORT

要想在Netty中使用SO_REUSEPORT特性,需要满足以下两个前提条件

linux内核版本 >= 3.9

Netty版本 >= 4.0.16

替换Netty中的Nio组件为原生组件

直接在Netty启动类中替换为在Linux系统下的epoll组件

NioEventLoopGroup → EpollEventLoopGroup

NioEventLoop → EpollEventLoop

NioServerSocketChannel → EpollServerSocketChannel

NioSocketChannel → EpollSocketChannel

如下所示:

group = new EpollEventLoopGroup();//NioEventLoopGroup ->EpollEventLoopGroup

bootstrap = newBootstrap();

bootstrap.group(group)

.channel(EpollDatagramChannel.class) //NioServerSocketChannel -> EpollDatagramChannel

.option(ChannelOption.SO_BROADCAST, true)

.option(EpollChannelOption.SO_REUSEPORT,true) //配置EpollChannelOption.SO_REUSEPORT

.option(ChannelOption.SO_RCVBUF, 1024 * 1024 *bufferSize)

.handler(new ChannelInitializer() {

@Overrideprotected voidinitChannel(Channel channel)throwsException {

ChannelPipeline pipeline=channel.pipeline();//....

}

});

netty提供了方法Epoll.isAvailable()来判断是否可用epoll

多线程绑定同一个端口

使用原生epoll组件替换nio原来的组件后,需要多次绑定同一个端口。

if(Epoll.isAvailable()) {//linux系统下使用SO_REUSEPORT特性,使得多个线程绑定同一个端口

int cpuNum =Runtime.getRuntime().availableProcessors();

log.info("using epoll reuseport and cpu:" +cpuNum);for (int i = 0; i < cpuNum; i++) {

ChannelFuture future=bootstrap.bind(UDP_PORT).await();if (!future.isSuccess()) {throw new Exception("bootstrap bind fail port is " +UDP_PORT);

}

}

}

更多例子:https://www.programcreek.com/java-api-examples/index.php?api=io.netty.channel.epoll.EpollDatagramChannel

也可以参考:https://github.com/netty/netty/issues/1706

Bootstrap bootstrap = newBootstrap()

.group(new EpollEventLoopGroup(5))

.channel(EpollDatagramChannel.class)

.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

.option(EpollChannelOption.SO_REUSEPORT,true)

.handler(channelInitializer);

ChannelFuture future;for(int i = 0; i < 5; ++i) {

future=bootstrap.bind(host, port).await();if(!future.isSuccess())throw new Exception(String.format("Fail to bind on [host = %s , port = %d].", host, port), future.cause());

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Netty 是一个 Java 开发的网络应用程序框架,提供了许多易于使用的 API 用于开发各种网络应用程序,包括 UDP 服务。下面是一个使用 Netty 开发 UDP 服务的简单示例代码: ``` import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioDatagramChannel; public class NettyUDPServer { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioDatagramChannel.class) .handler(new SimpleChannelInboundHandler<DatagramPacket>() { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception { // 处理接收到的数据 } }); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } } ``` 在上面的代码中,我们创建了一个 Bootstrap 实例并配置了它,将其绑定到 8080 口。然后,我们在 channelRead0() 方法中处理接收到的数据包。 这仅仅是一个简单的示例,您可以根据需要扩展它并实现更多功能。 ### 回答2: Netty是一个基于Java的网络编程框架,它提供了丰富的API和组件,方便开发者进行TCP和UDP的网络通信。下面是一个简单的Netty UDP服务实现。 首先,需要引入Netty的相关依赖。可以通过Maven或者手动下载jar包的方式导入。 接着,创建一个UDP服务类,命名为UdpServer。在这个类中,需要实现对应的ChannelInboundHandlerAdapter来处理传入的UDP数据报。 首先,创建一个Bootstrap实例来设置服务的配置。然后,设置NioEventLoopGroup来处理I/O操作。对于UDP协议,只需要一个处理I/O的EventLoopGroup就足够了。 然后,配置服务的Channel。在这里,使用NioDatagramChannel类来创建UDP服务通道。设置通道的Option参数,如SO_BROADCAST和SO_REUSEADDR,以及绑定服务的IP地址和口号。 接下来,创建ChannelInitializer来初始化服务的ChannelPipeline。在这个方法中,将自定义的ChannelInboundHandlerAdapter添加到ChannelPipeline中。这个Handler负责处理传入的UDP数据报。 最后,绑定并启动服务,等待客户的连接。 以下是一个简单的示例代码: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.nio.NioDatagramChannel; public class UdpServer { private static final int PORT = 9999; public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioDatagramChannel.class) .option(ChannelOption.SO_BROADCAST, true) .option(ChannelOption.SO_REUSEADDR, true) .handler(new ChannelInitializer<NioDatagramChannel>() { @Override protected void initChannel(NioDatagramChannel ch) throws Exception { ch.pipeline().addLast(new UdpServerHandler()); } }); ChannelFuture future = bootstrap.bind(PORT).sync(); future.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } private static class UdpServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { DatagramPacket packet = (DatagramPacket) msg; // 处理UDP数据报的逻辑 // ... } } } ``` 在上面的代码中,创建了一个UdpServerHandler类来处理传入的UDP数据报。你可以根据自己的需求实现具体的业务逻辑。 以上是一个简单的Netty UDP服务实现示例,希望对你有所帮助。 ### 回答3: Netty是一个高性能的网络编程框架,支持TCP和UDP协议。下面我将简要介绍如何使用Netty编写一个UDP服务。 首先,我们需要创建一个新的Java项目,并引入Netty库的相关依赖。可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.65.Final</version> </dependency> ``` 接下来,我们创建一个UDP服务的类,命名为"UdpServer"。在该类中,我们需要完成以下几个步骤: 1. 创建一个EventLoopGroup对象。EventLoopGroup用于处理IO操作,默认情况下会创建2 * CPU核心数个EventLoop用于处理任务。 2. 设置服务的启动类Bootstrap,并配置相关参数,如channel类型、口号等。 3. 设置ChannelInitializer,用于初始化每个新连接的Channel,将需要处理的Handler添加到Channel的ChannelPipeline中。 4. 启动服务,绑定并侦听指定的口。 下面是一个简单的UDP服务示例代码: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel; public class UdpServer { public static void main(String[] args) throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(); // 创建EventLoopGroup对象 try { Bootstrap bootstrap = new Bootstrap(); // 创建启动类Bootstrap对象 bootstrap.group(group) // 设置EventLoopGroup .channel(NioDatagramChannel.class) // 设置channel类型为NioDatagramChannel .option(ChannelOption.SO_BROADCAST, true) // 设置选项,允许广播 .handler(new ChannelInitializer<DatagramChannel>() { @Override protected void initChannel(DatagramChannel ch) throws Exception { ch.pipeline().addLast(new UdpServerHandler()); // 添加自定义的ChannelHandler } }); ChannelFuture future = bootstrap.bind(9999).sync(); // 启动服务并绑定口 future.channel().closeFuture().await(); // 关闭通道的事件监听 } finally { group.shutdownGracefully().sync(); // 关闭EventLoopGroup } } } ``` 在上述代码中,UdpServerHandler为自定义的ChannelHandler,用于处理接收到的UDP消息。你可以在该Handler中编写自己的业务逻辑。 以上是一个简单的Netty UDP服务实现。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值