我正在用c ++建立一个聊天服务器(允许用户之间的私人消息)......这对我来说是一个挑战,我已经达到了一个死点...我不知道什么可能更好
顺便说一下:我对C ++几乎不熟悉;这就是我想要挑战的原因......所以如果还有其他最佳方式,多线程等等......请告诉我。
我有一个运行的c ++应用程序,它有一个套接字数组,在每个循环中读取所有输入(循环遍历所有套接字)(我估计是1秒循环)并将其存储到DB(需要日志),以及之后,再次在所有套接字上循环,发送每个套接字所需的内容。
优点:包含一个单一流程。易于开发。 缺点:我认为它几乎不可扩展,而且只有一个失败的焦点......我的意思是,20k插槽的性能怎么样?
我有一个c ++应用程序正在监听连接。 当收到连接时,它会分叉处理该套接字的子进程...读取并保存用户所有输入的数据库。并在每个循环中检查DB上所有必需的输出以写入套接字。
优点:如果守护程序足够小,每个套接字的进程可能更具可伸缩性。同时,如果进程失败,所有其他进程都将保持联机状态。 缺点:更难开发。可能是它消耗了太多资源来维护每个连接的进程。
您认为哪种选择最好?欢迎任何其他想法或建议:)
答案 0 :(得分:2)
正如评论中所提到的,还有一个替代方案是使用select()
或poll()
(或者,如果您不介意使您的应用程序特定平台,请{ {3}})。我个人建议poll()
,因为我觉得它更方便,但我认为至少在某些版本的Windows上只能使用select()
- 我不知道在Windows上运行对你来说是否重要
这里的基本方法是首先将所有套接字(包括侦听套接字,如果您正在侦听连接)添加到结构中,然后根据需要调用select()
或poll()
。此调用将阻止您的应用程序,直到至少有一个套接字有一些数据要读取,然后您被唤醒并通过准备好读取的套接字,处理数据然后再次跳回阻塞。您通常在循环中执行此操作,例如:
while (running) {
int rc = poll(...);
// Handle active file descriptors here.
}
这是一种编写主要受IO限制的应用程序的好方法 - 即它处理网络(或磁盘)流量的时间远远超过实际使用CPU处理数据的时间。
正如评论中所提到的,另一种方法是为每个连接分叉一个线程。这非常有效,您可以在每个线程中使用简单的阻塞IO来读取和写入该连接。我个人会建议不要采用这种方法,原因有很多,其中大多数都是个人偏好。
首先,它需要处理需要一次写入大量数据的连接。套接字不能保证一次写入所有待处理数据(即它发送的金额可能不是您请求的全部金额)。在这种情况下,您必须在本地缓冲挂起的数据并等到套接字中的空间发送它。这意味着在任何给定时间,您可能正在等待两个条件 - 套接字已准备好发送,或者套接字已准备好读取。当然,在发送所有待处理数据之前,您可以避免从套接字读取,但这会导致处理数据的延迟。或者,您可以在该连接上使用select()
或poll()
- 但如果是这样,为什么还要使用线程,只需处理所有连接。您还可以为每个连接使用两个线程,一个用于读取,一个用于写入,如果您不确定是否可以始终在一次调用中发送所有消息,这可能是最好的方法,尽管这会使线程数增加一倍你需要哪些可以使你的代码更复杂,并略微增加资源使用。
其次,如果您计划处理许多连接或高连接周转率,则线程对系统的负担比使用select()
或朋友更多。在大多数情况下,这不是特别重要的事情,但它是大型应用程序的一个因素。这可能不是一个实际问题,除非你写的东西就像是一秒钟处理数百个请求的网络服务器,但我认为提及参考是相关的。如果你正在编写这种规模的东西,你最终可能最终会使用混合方法,在这种方法中,你将多个进程,线程和非阻塞IO的组合多路复用。
第三,一些程序员发现线程很难处理。您需要非常小心地使所有共享数据结构都是线程安全的,可以使用独占锁定(互斥锁),也可以使用其他人为您执行此操作的库代码。有很多例子和库可以帮助你解决这个问题,但我只是指出需要小心 - 多线程编码是否适合你。忘记锁定某些内容并让代码在测试中正常工作相对容易,因为线程不会碰巧遇到数据结构,然后在更高负载下发生这种情况时会发现难以诊断的问题在现实世界。谨慎和纪律,编写健壮的多线程代码并不难,我不反对(尽管epoll()
),但您应该了解所需的护理。在某种程度上,这适用于编写任何软件,当然,这只是程度问题。
除了这些问题之外,线程对于许多应用程序来说是一种非常合理的方法,并且有些人似乎发现它们比使用select()
的非阻塞IO更容易处理。
至于你的方法,A会起作用但浪费CPU,因为无论是否有实际有用的工作,你都必须每秒醒来。此外,您在处理消息时会引入最多一秒的延迟,这可能会对聊天服务器造成刺激。一般来说,我会建议像select()
这样的方法比这更好。
选项B可以正常工作,但是当您想要在连接之间发送消息时,您将不得不使用像管道之类的东西来在进程之间进行通信,这有点痛苦。您最终必须等待传入的管道(用于发送数据)以及套接字(用于接收数据),因此您最终会遇到同样的问题,必须等待两个文件句柄类似select()
或线程的东西。实际上,正如其他人所说,线程是分别处理每个连接的正确方法。单独的进程也比线程更昂贵(尽管在Linux等平台上,fork()
的写时复制方法意味着它实际上并不太糟糕。)
对于只有几十个连接的小型应用程序而言,在线程和进程之间进行选择的技术并不是很多,这在很大程度上取决于哪种风格对您更有吸引力。我个人会使用非阻塞IO(有些人称之为异步IO,但是opinions vary)并且我已经写了很多代码来执行此操作以及许多多线程代码,但它&#39 ; s仍然只是我个人的意见。
最后,如果你想编写可移植的非阻塞IO循环,我强烈建议调查that's not how I would use the term(或者可能libev,但我个人觉得前者更容易使用且性能更高。这些库在不同平台上使用不同的原语,例如select()
和poll()
,因此您的代码可以保持不变,并且它们也倾向于提供稍微方便的接口。
如果您对此有任何疑问,请随时提出。