基于内核的(Linux)数据中继,可在两个TCP套接字之间传输数据。

7
我写了一个TCP中继服务器,它像点对点路由器(超级节点)一样工作。
最简单的情况是两个打开的套接字之间的数据中继:
客户端A <--- > 服务器 <--- > 客户端B
然而,服务器必须为大约2000个这样的A-B对提供服务,即4000个套接字...。
在用户空间中有两种已知的数据流中继实现(基于socketA.recv() --> socketB.send()和socketB.recv() --> socketA.send()):
使用select/poll函数(非阻塞方法) 使用线程/分叉(阻塞方法)
我使用了线程,所以在最坏的情况下,服务器会创建2 * 2000个线程!我不得不限制堆栈大小,它能工作,但是这是正确的解决方案吗?
我的问题的核心是:是否有一种避免在用户空间中两个套接字之间进行活动数据中继的方法?
似乎有一种被动的方法。例如,我可以从每个套接字创建文件描述符,创建两个管道并使用dup2()——与标准输入/输出重定向相同的方法。然后两个线程就不用于数据中继,并且可以完成/关闭。
问题是服务器是否应该关闭套接字和管道,以及如何知道管道何时断开以记录此事实?
我还发现了"socket pairs",但我不确定它是否适用于我的目的。
您会建议什么解决方案以卸载用户空间并限制线程数?
一些额外的解释:
服务器有定义的静态路由表(例如,具有ID_B的ID_A-配对标识符)。客户端A连接到服务器并发送ID_A。然后服务器等待客户端B。当A和B配对时(两个套接字都打开),服务器开始数据中继。
客户端是对称NAT后面的简单设备,因此N2N协议或NAT穿越技术对他们来说太复杂了。
感谢Gerhard Rieger的提示:
我知道避免在用户空间中进行读写、recv/send有两种内核空间方法: sendfile 刮送 两者都有关于文件描述符类型的限制。 dup2不能帮助在内核中做某事,据我所知。

Man pages: splice(2), splice(2), vmsplice(2), sendfile(2), and tee(2).

Related links:


1
针对这么多连接,您应该考虑使用几个线程和 epoll(4) 的组合。 - Some programmer dude
1
那么,你可以使用类似 libev 的东西。 - Hasturkun
谢谢。然而,它仍然是用户空间中的活动继电器。我相信存在一种被动方法。服务器等待客户端的ID,并带有5秒超时,因此线程似乎是配对阶段的自然选择。 - nopsoft
好的简短比较:http://www.win.tue.nl/~aeb/linux/lk/lk-12.html。我只是在每个线程中使用了阻塞 recv()。然而,为了限制线程数,我可以在一个线程中使用 epoll() 或 FASYNC。 - nopsoft
1
另一个提示:http://www.kegel.com/c10k.html - nopsoft
2个回答

6

3
即使是只有2000个并发连接的负载,我也不会选择使用线程。因为它们具有最高的堆栈和切换开销,这是因为确保您可以在任何地方被中断要比仅在特定位置被中断更昂贵。只需使用epoll()和splice(如果您的套接字是TCP,则似乎是这种情况),您就可以完美解决问题。您甚至可以使epoll工作在事件触发模式下,在该模式下您只需要注册一次您的文件描述符。
如果您绝对希望使用线程,请使用每个CPU核心一个线程来分担负载,但如果您需要这样做,这意味着您正在以亲和性、每个CPU插座上的RAM位置等方面发挥重要作用的速度上玩耍,而这似乎不是您问题的情况。因此,我假设在您的情况下单个线程就足够了。

谢谢。最好使用EPOLLONESHOT还是ADD/DEL?就像这里 https://dev59.com/uFHTa4cB1Zd3GeqPVNyk 或者http://rg4.net/archives/375.html ? - nopsoft
从未尝试过EPOLLONESHOT,虽然它可能是EPOLLET的一种有用且优雅的替代方案。我认为应该从标准的ADD/DEL开始以限制复杂性,如果需要的话再进行优化。 - Willy Tarreau

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