多个UDP套接字和多个客户端

3
我需要使用UDP套接字编写程序,但我不明白在我的情况下应该做什么。
我有一个服务器和一个客户端。服务器在INADDR_ANY套接字上接收数据报,并为每个唯一的客户端发送大量信息。每个客户端也定期向服务器发送数据。
对于每个联系监听套接字的客户端(称为0),我都会为其创建一个单独的套接字,以便可以向其发送数据而不会阻塞套接字0。我想做的是将与特定客户端的所有通信移动到新套接字中。我能做到吗?目标是使通信更容易,更有效,并避免阻塞任何套接字。
所以我找不到答案的问题是:
- 我能从那个特定客户端的套接字中接收数据吗? - 如果不能,如果我不经常进行recvfrom操作,那么所有客户端都写入套接字0是否会导致它被阻塞? - 如果可以从单独的套接字接收特定客户端的数据,那么这些数据会同时传递到套接字0和特定套接字中吗?
我知道TCP在这方面效果更好,但我必须使用UDP。我该怎么做?是否有处理这种情况的公认“标准”?
评论:我觉得我对UDP套接字的理解有误,但与TCP相比,真的很少有教程。

2
实际上,并不存在“阻塞套接字0”的情况。如果发送将被阻塞,那么你有多少个套接字都无济于事。UDP服务器最好使用单个套接字实现。 - user207421
@EJP 真的吗?我的意思是套接字有限制的内存量。如果一个套接字已经满了,那么对它的 sendto() 操作会阻塞或者在非阻塞模式下失败,对吧?在我看来,为每个客户端创建一个 UDP 套接字是很合理的。我的逻辑有什么问题吗? - m_highlanderish
1
与TCP套接字不同,UDP套接字将始终以网络接口可以接受的速度发送数据。因此,通常情况下您不需要例如两个UDP套接字;相反,您可以创建一个具有两倍SO_SNDBUF缓冲区空间的单个UDP套接字。 - Jeremy Friesner
@m_highlanderish套接字具有套接字发送和接收缓冲区,您可以控制其大小。 - user207421
2个回答

6
我想做的是将与特定客户端的所有通信移至新套接字。我能做到吗?
你可以创建一个新的UDP套接字,将其绑定到不同的端口,然后指示客户端开始将流量发送到新套接字的端口而不是通常的端口;但请注意,这样做实际上并没有任何优势。
如果不能,那么如果我不经常使用recvfrom,让所有客户端都写入套接字0会阻塞它吗?
是的,但通常的解决方案是调用recvfrom()足够频繁以跟上传入流量的速度,并/或增加其SO_RECVBUF缓冲区大小,使其传入数据缓冲区足够大,以便不可能被填满。确保足够频繁地调用recvfrom()的一种方法是创建一个专门的线程,仅在循环中调用recvfrom(),然后将数据移交给另一个线程进行更密集的处理。如果可能,请以较高的优先级运行此网络线程,以确保它不会被其他线程拖延CPU。
如果可能从特定客户端接收数据到单独的套接字,那么数据会同时发送到套接字0和特定套接字吗?
如果有两个线程都在同一个套接字上调用recvfrom(),那么任何给定的传入UDP数据包将被传递到其中一个线程,而另一个线程则不会收到。无法预测哪个线程将接收哪个数据包--这只是取决于在特定线程调用recvfrom()时套接字传入缓冲区中的下一个数据包的运气。通常情况下,多个线程访问单个套接字不是推荐的设计。
我知道TCP对此有更好的解决方案,但我必须使用UDP。我该怎么做?有没有处理这种情况的“标准”方法?
我不知道什么是“标准”,但我通常有一个专用的I/O线程,仅从UDP套接字中读取(如果需要,写入)数据。它将UDP套接字设置为非阻塞模式,然后循环选择(recvfrom())任何传入的UDP数据包,并以线程安全的方式将传入的数据和其源地址/端口信息附加到FIFO队列中,供其他(不那么时间敏感的)线程稍后取出并进行处理。这样即使一个数据包(或一系列数据包)需要相对较长的时间来处理,结果也不会导致数据包被丢弃(尽管可能会暂时增加RAM使用量,因为FIFO变得更大)。

很好的回答,不过我问的不是一个套接字上的两个线程,而是有两个套接字,如果我的客户端发送数据,哪个套接字将接收数据(一个监听所有端口,另一个例如连接到该客户端地址)。我知道我应该怎么做,但我不知道为什么。为什么使用更多的套接字,例如为每个客户端创建一个单独的套接字来发送(不是接收,只是发送)不起作用?为什么总是使用一个套接字更好?我的意思是,在套接字上使用connect()会使发送变得更容易。 - m_highlanderish
请看你的第三段。看这个链接:http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html它说明我可以创建一个UDP套接字并将其连接到特定的地址和端口。这正是我想要的。现在,如果我有一个绑定到IPADDR_ANY的套接字,并创建一个新的套接字,我将从IPADDR_ANY接收到的地址连接到它,那么来自该地址的数据报是否会无问题地传输到新的套接字中?会有冲突吗?我需要使用SO_REUSEPORT吗?如果我绑定了一个IPADDR_ANY,我还能创建一个新的套接字吗? - m_highlanderish
1
根据我的经验,在UDP套接字上调用connect()会做两件事情:(1)它允许您在套接字上调用send()(或write()),并将数据包发送到您连接的地址,(2)它在套接字上安装了一个过滤器,以便源地址和/或源端口与connect()调用中指定的不同的数据包将不会通过该套接字传递到您的代码。(1)是一个小方便(没有它也很容易调用sendto()),而(2)对我来说是个问题,因为在我的应用程序中,我想处理所有传入的UDP数据包,而不仅仅是来自一个源的数据包。 - Jeremy Friesner
关于你的第二条评论,我自己还没有尝试过那个技巧(所以请随意尝试并看看它是否适用于你),但你肯定需要在两个UDP套接字绑定到相同端口时都要使用SO_REUSEADDR (如果可能的话,也要使用SO_REUSEPORT),即使这样,我的直觉是其中的任何一个套接字接收到特定数据包将是不确定/无法预测的- 如果你的connect()连接的套接字接收到别人的数据包,我怀疑该数据包将被丢弃/过滤掉。 - Jeremy Friesner

0

@ Jeremy Friesner: 你写道:“...即使这样,我的直觉是它将是不确定/不可预测的,哪个套接字会接收到任何特定的数据包 - 如果您连接的套接字接收到其他人的数据包,我认为该数据包将被丢弃/过滤。” 在Linux和lwIP中,我正是调查了这一点,并得出结论:后创建的套接字(具有更高的fd编号)是接收套接字,而另一个则不接收。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接