c++ udp通信_UDP的epoll并发框架—解决OpenUOM的并发问题

本文探讨了如何解决UDP并发问题,通过改造OpenUOM利用epoll实现UDP通信的异步并发模型。介绍了从TCP到UDP的OpenUOM处理逻辑变化,提出让UDP socket像TCP一样Listen的思路,并展示了在内核层面扩展UDP监听功能的尝试,最终优化了OpenUOM的多线程版本,提高了并发性能。
摘要由CSDN通过智能技术生成
6d2354ab6831075f6386b9687b94a09d.png

UDP具有是一种很好的封装协议,比如OpenUOM使用UDP封装会比TCP好很多,现在越来越多的业务采用UDP传输,然后自己定义按序到达以及流控逻辑,然而就我个人的使用经验来看,UDP太难做并发,大多数情况下,使用UDP会让epoll等高性能event机制优势全无。本文以OpenUOM为例,说明一下我是怎么解决UDP并发问题的。

异步并发模型与epoll

和apache相比,nginx采用异步的处理方式,也就是说,一个线程可以处理多个连接,基于event模型,来了个数据包就读,可能依次到达的数据不属于同一个连接,但是没关系,只要能将可读的socket描述符和具体的连接对应上即可。这样会使得在大并发场景下,让CPU逼近其极限运转,因为它几乎没有时间闲着,它会一直处理到达的数据包。apache的模型就不是这样,它会让一个连接单独占有一个线程,如果有大量的连接就会有大量的线程,然而对于每一个线程而言,其数据读写的压力并不是很大,这就会导致大量线程之间频繁切换,而切换会导致cache的刷新等副作用...因此在同样的硬件配置情形下,nginx的异步模型要比apache好很多。

我们已经知道,异步处理是搞定大并发的根本,接下来的问题是,如何让一个就绪的socket和一个业务逻辑连接对应起来,这个问题在同步模型下并不存在,因为一个线程只处理一个连接。曾经的event机制比如select,poll,它们只能告诉你socket n就绪了,你不得不自己去通过数据结构来组织socket n和该连接信息之间的关系,典型的如下:

struct conn {    int sd;    void *others;};list conns;

一个链表conns囊括了该线程负责的所有连接,如果select/poll告诉你socket n就绪了,你不得不遍历这个conns链表,比较谁的sd是n,然后取出conn来处理,虽然可以用更加高效的数据结构,但是查找是必不可少的。然而epoll解决了这个问题。

在调用epoll_ctrl将一个socket加入到epoll中时,API会为你提供一个指针,让你直接绑定一个socket描述符和一个指针,一旦socket就绪,取出的是一个结构体,其中包含了与该socket对应的指针,因此你便可以这么做:

conn.sd = sd;conn.others = all;ev.events = EPOLLIN;ev.data.ptr = &conn;epoll_ctl(kdpfd, EPOLL_CTL_ADD, sd, &ev);while (1) {          nfds = epoll_wait(kdpfd, events, 10000, -1);    for (n = 0; n < nfds; ++n) {        conn = events[n].data.ptr;        recv(conn.sd, ....);        ....    }}

conn会一下子取出来。这是合理的方式。毕竟,内核中已经经过socket查找了,一个5元组唯一代表了一个连接,为何要在用户态程序再找一次呢?因此除了epoll不需要遍历所有的被监视socket之外,可以保存用户的指针也是其相对于select/poll的一大优势。nginx正是用的这种方式。我们回到OpenUOM。

使用TCP的OpenUOM

使用TCP的OpenUOM跟nginx几乎是一模一样,其核心处理逻辑如下:

/* 加入侦听socket */context.sd = listener;context.others = dont_care;listen_ev.events = EPOLLIN;listen_ev.data.ptr = context;epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &listen_ev);/* 加入TUN网卡 */tun.sd = tun;tun.others = dont_care;entry.ptr = tun;entry.type = TUN;tun_ev.events = EPOLLIN;tun_ev.data.ptr = entry;epoll_ctl(kdpfd, EPOLL_CTL_ADD, tun, &tun_ev);while(1) {    nfds = epoll_wait(kdpfd, events, 10000, -1);    for (n = 0; n < nfds; ++n) {        if (events[n].data.ptr == context) {            child_sd = accept(context.sd, remote_addr....);            multi_instance *mi = create_mi(child_sd, remote_addr, ...);            entry.ptr = mi;            entry.type = SOCKET;            new_ev.events = EPOLLIN;            new_ev.data.ptr = entry;            epoll_ctl(kdpfd, EPOLL_CTL_ADD, child_sd, &new_ev);            ....        } else if (events[n].data.ptr.type == SOCKET){            multi_instance *mi = events[n].data.ptr;            data = read_from_socket(mi);            // 这里简化了处理&#x
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值