UDP并发服务器思路

本文探讨了UDP并发与TCP并发的区别,指出TCP通常使用多线程和epoll模型处理并发连接,而UDP服务器通常采用迭代方式处理请求。在UDP并发中,服务器可以为每个客户端创建新的socket和绑定端口,或者利用epoll进行多路复用。通过线程或epoll技术,UDP服务器能够有效地处理来自多个客户端的并发请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UDP并发与TCP并发的区别

在TCP并发编程中,通常使用one loop per thread的并发模型,也就是使用多个线程,每个线程中都有一个epoll loop,无论是使用epoll还是poll或select,在观察有无数据就绪时,都是针对多个文件描述符。如果只有一个文件描述符,那么进程只要观察那一个文件描述符即可。

网络编程中,一个Socket对应一个文件描述符。在TCP的并发中,服务器在监听端口初始化一个socket套接字描述符,接受客户端后就与每个客户端的连接有一个不同的文件描述符,所以TCP并发中有多个socket套接字描述符。但是,UDP协议的服务器没有真正意义上的“连接”的概念。在消息监听端口和响应请求都只有一个socket套接字描述符。

UDP怎么考虑并发?

《UNP · 卷1》中UDP章节描述了一句话:一般来说大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。也就是服务器等待客户端的请求,然后读取请求后处理,再发回响应。如果是简单的处理响应还可以,如果每个处理都很耗时,那么就不得不考虑在UDP服务器做并发处理。

并发常见的思路就是多线程。服务器读取一个新的请求后,可以交给一个线程处理,该线程在处理之后直接将响应内容发给客户端。

虽然可以多线程处理读取的每个消息,但如果UDP服务器与多个客户端交互,却没有多个socket,这样效率并不是很高。典型的解决方法就是:在服务器为每个客户端创建一个新的socket套接字并绑定一个新的端口,客户端以后就需要以这个新的socket套接字与服务器通信。

总的来说:UDP并发服务器针对多个客户端,可以创建多个socket。针对多个请求,可以使用多线程(线程池)进行处理。

UDP 并发编程模型

  • 多个socket伪代码
for (; ;)
{
    //等待新的客户端连接
    recvfrom(&from_addr);
    //每有一个新的客户端,创建一个线程
    pthread_create(&tid, NULL, thread_fun, &from_addr);
}

//线程函数
void* thread_fun(void* arg)
{
    peer = socket(AF_INET, SOCK_DGRAM, 0);
    servaddr.sin_port = htons(0);	//绑定端口
    bind(peer, (struct sockaddr*)&servaddr, sizeof(servaddr));
    //将这个套接字和客户端地址连接,之后就可以使用write/read或send/recv这些函数,且不用再关心客户端地址
    connect(peer, (struct sockaddr*)&from, sizeof(from));
    //处理请求的loop
}
  • 使用epoll进行处理伪代码
  1. UDP服务器创建socket,并设置socket为REUSEADDRREUSEPORT和非阻塞同时再bind服务器地址local_addr:
listen_fd = socket(PF_INET, SOCK_DGRAM, 0);
fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFD, 0)|O_NONBLOCK)
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
bind(listen_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));
  1. 创建epoll fd,并将listen_fd添加到epoll中,并监听其可读事件:
epoll_fd = epoll_create(100);
ep_event.events = EPOLLIN | EPOLLET;
ep_event.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ep_event);
while (1) 
{
    in_fds = epoll_wait(epoll_fd, in_events, 1000, 5000);
  1. epoll_wait返回时,如果返回的是listen_fd, 调用recvfrom接受client第一个UDP包,并根据recvfrom返回client地址,创建一个新的socket套接字new_fd,设置new_fd为REUSEADDRREUSEPORT和非阻塞,同时bind本地地址local_addr然后connect上recvfrom返回的client地址:
    for (i = 0; i < in_fds; i++)
    {
        if(in_events[i].data.fd = listen_fd)
        {
            recvfrom(listen_fd, buf, sizeof(buf), 0, (struct sockaddr *&client_addr, &client_len);
            new_fd = socket(PF_INET, SOCK_DGRAM, 0);
            fcntl(new_fd, F_SETFL, fcntl(new_fd, F_GETFD, 0)|O_NONBLOCK);
            setsockopt(new_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            setsockopt(new_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
            bind(new_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));
            connect(new_fd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr));
  1. 将新创建的new_fd加入到epoll中并监听其可读事件:
            client_ev.events = EPOLLIN;
            client_ev.data.fd = new_fd;
            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &client_ev);
        }
        else if (in_events[i].events & EPOLLIN)
        {
  1. 当epoll_wait返回时,如果返回的是new_fd,那么调用recvfrom来接收特定client的UDP包:
            recvfrom(new_fd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&client_addr, &client_len);
            data->fd = new_fd;
            data-> ptr= process(recvbuf); /*data中包括socket信息*/
            ev.data.ptr = data;
            ev.events = EPOLLOUT | EPOLLET;
            epoll_ctl(epoll_fd,EPOLL_CTL_MOD,new_fd,&ev);
        }
        else if (in_events[i].events & EPOLLOUT)
        {
            sockfd = data->fd;
            send( sockfd, data->ptr, strlen((char*)data->ptr), 0 );
            ev.data.ptr = data;
            ev.events = EPOLLIN | EPOLLET;
            epoll_ctl(epoll_fd,EPOLL_CTL_MOD,sockfd,&ev);
        }
        else
        {
        }
    }
}
UDP (User Datagram Protocol) 是一种无连接的、不可靠的数据报网络协议,它主要用于对实时性要求高的应用,如在线游戏、视频会议等。在处理大量并发连接时,UDP 提供了一种简单直接的方式: 1. **并发处理**:UDP 的数据传输是异步的,每个数据包都是独立发送的。这意味着你可以同时处理多个客户端的请求,每个请求作为一个单独的数据包来处理,不需要预先建立连接。 2. **单线程或多线程设计**:为了高效地处理并发,可以使用单线程模型,因为UDP层本身不会阻塞。对于每个收到的数据包,解析并响应即可。如果你希望更进一步优化,也可以考虑使用多线程或异步IO框架,将接收和处理任务分开,提高效率。 3. **缓冲区管理**:为了减少频繁的内存分配和拷贝操作,通常会预设一个较大的接收缓冲区,一次性接收多个数据包,然后逐个处理。 下面是一个简单的Python示例,使用socket库来模拟UDP服务器接收并发请求: ```python import socket def handle_client(client_socket): data = client_socket.recv(1024) print(f"Received from {client_address}: {data.decode('utf-8')}") # 这里处理数据逻辑,例如回复一个简单的消息 client_socket.sendall(b"Message received") server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_socket.bind(('localhost', 9999)) print("Listening for UDP requests...") while True: data, client_address = server_socket.recvfrom(1024) handle_client(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) # 创建新的流式套接字来处理这个请求 ``` 在这个例子中,`handle_client`函数负责处理来自不同客户端的请求,服务器持续监听新到来的数据包,并创建一个新的子套接字来处理每个独立的连接。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

code_peak

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

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

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

打赏作者

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

抵扣说明:

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

余额充值