关于服务器套接字编程模型的问题

11

在过去的几个月中,我一直在使用C++和Java实现socket服务器。我编写了一个小型Java服务器,用于处理托管在网站上的Flash应用程序的输入并进行处理,并成功编写了一个能够处理多玩家2D游戏客户端输入的C++服务器。我在其中一个项目中使用了TCP,而在另一个项目中使用了UDP。现在,我有一些问题,在网上找不到答案,希望一些专家可以帮助我。 :)

假设我想要在C++中构建一个服务器,用于处理来自数千个独立和/或Web应用程序的输入,那么我应该如何设计我的服务器呢?到目前为止,我通常为每个连接的用户创建一个新的唯一线程,但我怀疑这不是正确的方法。

此外,如何确定通过网络发送的数据包的布局;数据通常以二进制或文本状态发送到网络上吗?当您将数据发送到不同媒体(例如C++服务器到Flash应用程序)时,如何处理序列化对象?

最后,是否有易于使用的库通常支持可移植性(例如在Windows机器上开发和部署到Linux机器上),除了boost asio?

谢谢。

6个回答

10

看起来你有一些问题。我会尽力回答我能看到的内容。

1. 如何处理网络服务器中的线程?

我建议先看看在服务器上生成的工作线程上正在做什么样的工作。为每个请求生成一个新线程并不是个好主意……但如果并行请求的数量很小,每个线程上执行的任务速度很快,那么也许不会有什么问题。

如果你真的想以正确的方式去做,你可以拥有一个可配置/动态的线程池,在该线程池空闲时就回收工作线程。这样你就可以设置最大线程池大小。你的服务器将工作到线程池大小......然后让进一步的请求等待,直到有一个工作线程可用。

2. 如何格式化数据包中的数据?

除非你正在开发完全新的协议......否则你不需要真正担心这个。除非你正在处理流媒体(或另一种可以接受数据包丢失/损坏的应用程序),否则你可能不会在这个应用程序中使用UDP。TCP/IP 可能是你最好的选择......而这会为你规定数据包设计。

3. 序列化时我应该使用哪种格式?

你在网络上传输数据的序列化方式取决于将使用你服务的应用程序类型。二进制序列化通常更快,需要传输到网络上的数据量更少。使用二进制序列化的缺点是,在一个语言中的二进制序列化可能在另一个语言中无法正常工作。因此,连接到服务器并使用服务的客户端很可能要用与你使用的相同的语言来编写。

XML 序列化是另一种选择。它需要更长时间传输,并有更多的数据传输到网络上。使用类似 XML 序列化这样的东西的好处是,你不会受到可以连接到你的服务器并使用你的服务的客户端类型的限制。

你必须选择最适合你需求的方法。

尝试不同的选项,弄清楚哪种最适合你。希望你能找到比我在这里提到的任何东西都更快、更可靠的东西。


我很喜欢你的回答。只是想快速澄清一下:是否有一种序列化方案可以在多种语言中兼容?例如,Java本地序列化是否与.NET兼容? - Pablo Santa Cruz
如果你在谈论二进制序列化格式,那么我并不知道有这样的存在。基于文本的序列化(比如XML,JSON等)是唯一的选择,因为它们允许编程语言自己处理底层表示(例如,在C#中的 List<T> 可能转换成 Java 中的 T[])。 - Justin Niessner
今天早上我意识到我忘了提到COM+和DCOM。这些技术在组件之上提供了二进制兼容性层。您必须使用兼容的COM类型...但它是跨语言的二进制序列化。 - Justin Niessner
2
在我看来,Protocol Buffers 也适合于二进制序列化。 - Camilo Díaz Repka
1
这里是关于二进制序列化的内容,可以在http://en.wikipedia.org/wiki/Hessian_(web_service_protocol)上找到。 - Muxa

7
就服务器设计而言,我认为你是正确的:虽然“每个套接字一个线程”是一种简单易行的方法,但它不如其他服务器设计模式具有良好的可扩展性。
我个人喜欢“通信线程/工作线程”方法,其中一组动态数量的工作线程处理由生产者线程生成的所有工作。
在这种模型中,您将拥有一些线程池中的线程等待另一组处理网络I/O的线程生成的任务。
我发现Richard Stevens的UNIX Network Programming是这种网络编程方法的绝佳资源。尽管它的名称如此,但它在Windows环境中也非常有用。
关于数据包的布局(在我看来,您应该发布一个不同的问题,因为这是完全不同的问题),在选择文本与二进制方法时存在权衡。

使用 TEXT(即 XML)可能更容易解析和记录,并且通常更简单,而二进制协议应该在处理速度和网络数据包大小方面提供更好的性能,但您将不得不处理更复杂的问题,例如字节序等。

希望能对您有所帮助。


3
虽然之前的回答提供了很好的方向,但为了完整起见,我想指出线程并不是实现优秀套接字服务器性能的绝对要求。一些例子可以在这里找到:这里。还有许多可扩展性的方法 - 线程池、预派生进程、服务器池等。

2
1)最后,除了boost asio之外,是否有任何常用的易于使用的库支持可移植性(例如在Windows机器上开发并在Linux上部署)?
ACE库是另一个选择。它非常成熟(自90年代初就存在)并得到广泛应用。关于它与Boost ASIO的比较的简要讨论可在Riverace网站上找到。请记住,由于ACE必须长期支持大量旧平台,因此它不像Boost ASIO那样充分利用现代C ++功能。
2) 假设我想用C++构建一个服务器来处理来自成千上万的独立应用程序和/或Web应用程序的输入,我该如何设计我的服务器呢?到目前为止,我通常为每个连接创建一个新的独立线程,但我怀疑这不是正确的方法。
有许多常用的方法,包括但不限于:每个连接一个线程(你所描述的方法)和线程池(Justin所描述的方法)。它们各有优缺点。很多人已经看过了它们之间的权衡。一个好的起点可能是Thread Pool Pattern维基百科页面上的链接。
Dan Kegel的“The C10K Problem”网页有很多关于提高可伸缩性的有用笔记。
3) 另外,如何确定通过网络发送的数据包的布局;数据通常以二进制或文本状态发送到网络上吗?当您将序列化对象发送到不同的媒体(例如C++服务器到Flash应用程序)时,如何处理它们?
也许最常用的方法是使用二进制格式来传输数据,因为它可以更有效地利用网络带宽。但是,如果需要进行人类可读的调试或诊断,则使用文本格式可能更方便。在将序列化对象发送到不同的媒体时,您需要确保它们都能够正确地解析和使用相同的序列化格式。
我同意其他人的观点,发送二进制数据通常是最有效的。boost serialization库可用于将数据编组为二进制格式(以及文本格式)。成熟的二进制格式包括XDRCDR。例如,CORBA使用CDR格式。公司ZeroC定义了ICE编码,据说比CDR更高效。
有很多二进制格式可供选择。我的建议是避免重复造轮子,至少要阅读一些关于这些二进制格式的信息,以便您不会遇到这些现有二进制格式旨在解决的相同问题。
话虽如此,已经存在许多中间件可以为大多数需求提供预定义解决方案。例如,OpenSpliceOpenDDS都是OMG 数据分发服务标准的实现。DDS专注于通过发布-订阅模型等方式高效地分发数据,而不是远程调用函数。我更熟悉OMG定义的技术,但我相信还有其他中间件实现适合您的需求。

1

你仍然需要一个套接字来处理每个客户端,但是想法是创建一个大小为X(比如50)的套接字池,当你接近(比如90%)消耗所有这些套接字时,创建另一个大小为X的套接字池。在某些时候,客户端连接、发送数据和断开连接后,一些套接字将可用于使用,您可以使用它们(搜索套接字池以获取此信息)。

数据布局总是很困难的。如果所有客户端和服务器都将使用相同的硬件和操作系统,则可以以二进制格式发送数据,但是那里有许多陷阱(字节对齐位于列表顶部)。发送格式化文本始终更容易,但在带宽和处理能力方面显然更昂贵,因为您必须在发送之前从机器格式更改为文本格式,当然,在接收者处再次更改回来。

关于序列化,我很抱歉,我无法帮助您,也无法提供库(我太嵌入式了,没有使用过这些)


似乎你在混淆套接字和线程。 - Nikolai Fetissov
是的,我在开头混淆了术语(现已修复) - KevinDTimm

0
关于服务器套接字和序列化(编组)。最重要的问题是在选择中可读可写状态下增长套接字数量。我不是在讨论FD_SET的限制,这是可以简单解决的。我所说的是随着处理可用数据的评估套接字而未读取的套接字的信令时间增加和问题数据积累的增长。因此,解决方案甚至可能超出软件界限,并需要多处理器模型,其中处理器的角色受到限制:一个读写,N个处理。在这种情况下,当选择返回并发送到另一个处理单元时,所有可用的套接字数据应该已经被读取。
对于传入的数据也是如此。
关于编组。当然,二进制格式更可取,因为性能更好。顺便说一下,在UNICODE的条件下XML也有同样的问题。但是,同志们,这并不是简单地将长整型或整数值复制到套接字流中。但在这种情况下,即使htons,htonl也有帮助(它以NW格式发送/接收,并且操作系统负责数据转换)。但更安全的方式是发送数据跟随表示标头,其中公开了最/最低有效位的格式,字节顺序和IEEE数据类型。这很有效,我还没有遇到过失败的情况。
祝大家一切顺利。Simon Cantor

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