C++ 在多线程UDP服务器中使用多个套接字

C++ 在多线程UDP服务器中使用多个套接字,c++,multithreading,sockets,C++,Multithreading,Sockets,我需要编写一个服务器,它应该能够接收多个客户机连接(FCFS顺序),并且能够在单独的线程中处理每个客户机,这样处理看起来就像所有客户机都是并发的。所使用的传输协议应为UDP。此外,客户机和服务器之间还会发生多次数据传输往返,从客户机接收的数据必须存储在服务器端,以便对该客户机进行进一步处理 Requirement: 为了实现这一点,我采用了队列/线程池机制。最初,我创建了一个固定数量的线程池,并有一个队列数据结构来存储客户机地址。这个队列在所有线程之间共享,因此我使用“互斥”来锁定/解锁这个

我需要编写一个服务器,它应该能够接收多个客户机连接(FCFS顺序),并且能够在单独的线程中处理每个客户机,这样处理看起来就像所有客户机都是并发的。所使用的传输协议应为UDP。此外,客户机和服务器之间还会发生多次数据传输往返,从客户机接收的数据必须存储在服务器端,以便对该客户机进行进一步处理

Requirement: 
为了实现这一点,我采用了队列/线程池机制。最初,我创建了一个固定数量的线程池,并有一个队列数据结构来存储客户机地址。这个队列在所有线程之间共享,因此我使用“互斥”来锁定/解锁这个队列。在主服务器线程中,我创建一个套接字,将其绑定到一个全局端口/地址,然后在“recvfrom”调用上阻塞服务器。任何想要与服务器通信的客户端都会向侦听全局端口地址的主线程服务器发送一条“Hi”消息。主服务器线程在接收到这个“Hi”数据报后,将客户机的sockaddr结构推送到队列上,并返回到“recvfrom”调用上的block以接受其他客户机

任何在队列上获得锁的池线程,都会弹出客户机的地址并开始单独处理该客户机。也就是说,池线程现在将创建一个套接字,将其绑定到与全局端口不同的端口,并使用弹出的客户端地址将此端口地址发送给客户端。在客户端,在发送“Hi”消息后,它会阻塞“recvfrom”调用,一旦它从服务器接收到消息(使用不同的端口),它就会存储该端口/地址,从那时起,它只使用池服务器线程发送的这个新端口与服务器通信

Implementation:
我注意到在许多堆栈溢出问题中,在多线程UDP服务器中,一个套接字总是足够的,但是根据我的要求,我认为我需要多个UDP套接字,每个套接字都适合一个客户端。请提供我的建议,我的方法线程UDP服务器为我的要求

编辑:改进设计

感谢您对我的设计的所有评论和讨论。在掌握了您关于单套接字连接和上下文切换的所有观点之后,我想到了一个改进的设计。请提供关于我改进设计的反馈

在这个实现中,我将只使用一个绑定到全局已知端口地址的全局UDP套接字。就像前面一样,我将首先创建一个固定数量的线程,比如说5个线程。然后在所有线程中的单个套接字上执行recvfrom()。无论哪个线程首先接收到数据报,都将能够从该数据报中了解三件事:

  • 客户端sockaddr(客户端地址)
  • 客户端数据(发送的实际数据)
  • 进程号(与数据一起发送。这是为了让服务器知道必须对客户端数据进行何种处理)
基于上述信息,服务器线程将根据进程号处理数据,并将应答发送给从中接收数据的同一客户端。在处理数据并发送回复(如有必要)后,此线程将返回到对recvfrom()调用的阻塞

此外,关于各种客户机的详细信息将存储在全局哈希表和映射中(所有线程都可以访问),以便任何线程都可以根据特定的客户机访问该进程


客户端的更改:服务器应该能够知道它必须对客户端数据进行什么处理。因此,客户端将随数据一起发送进程号。

socket一次就足够了,因为每次收到数据包时,您都可以通过使用其源地址(src_addr)调用recvfrom来识别客户端:

现在,这个解决方案只有一个接收点。这意味着服务器必须接收数据,然后将其传递给应该执行该过程的线程。这会降低单套接字解决方案的并行性,因为您只使用一个端口

在我看来,您的解决方案在并行性方面更好,因为您的线程运行在不同的端口上,因此它们可以并行地服务于多个客户机,特别是如果您的机器有多个处理器和多个网卡,可以用来服务您的客户机。在最好的情况下,您可能有多个线程并行运行,在不同的处理器中,每个线程为来自不同网络接口的客户端提供服务。 编辑


在与David讨论了您的设计之后,他说服了我,使用多个套接字将导致多个上下文切换,这可能会降低服务器性能。因此,在这种情况下,最好使用多个线程,但对所有客户机使用一个套接字。

好吧,我不确定这些需求来自何方——主要是针对现实生活中的程序或某种任务/面试等

假设这是一个现实生活中的项目,我将继续,因为我不会帮助任何人完成任务:)

UDP不是面向连接的协议(即使UDP套接字可以“连接”,但这只会增加混乱)。它是一个面向消息的协议。现在,设计线程模型的第一个问题总是“我在这里需要线程模型吗?”如果您有可以独立执行的操作,并且需要花费大量时间,那么您将需要线程。在您的情况下,这取决于您对消息真正做了什么。让我们假设,您对它进行了一些处理,与潜在的并发客户端数量相比,这个处理时间是非常重要的(请记住,有成功的单线程Web服务器!简单地执行所有单线程请求并没有什么错)

就设计而言,我不认为将新端口提供给
Clarification needed:
recvfrom(int sockfd, void *buf, size_t len, int flags,
             struct sockaddr *src_addr, socklen_t *addrlen);