gpt4 book ai didi

c++ - 多用户聊天服务器C++

转载 作者:行者123 更新时间:2023-11-28 03:18:23 30 4
gpt4 key购买 nike

我正在用c ++构建一个聊天服务器(允许用户之间的私人消息)……这对我来说是一个挑战,但我已经陷入僵局……我不知道有什么更好的选择。

顺便说一句:我对C ++并不陌生;这就是为什么我要挑战...所以,如果还有其他最佳方法,多线程等...请告诉我。

选项A

我正在运行一个c ++应用程序,它具有一个套接字数组,在每个循环(我猜是1秒循环)中读取所有输入(遍历所有套接字)并将其存储到DB(需要一个日志),然后,再次遍历所有套接字,发送每个套接字所需的内容。

优点:包含一个流程。易于开发。
缺点:我认为它几乎没有可扩展性,并且只有一个故障点……我的意思是,使用20k套接字的性能如何?

选项B

我有一个侦听连接的C ++应用程序。
接收到连接后,它将派生一个处理该套接字的子进程...将用户的所有输入读取并保存到DB。并在每个循环中检查DB的所有必需输出以写入套接字。

优点:如果守护程序足够小,则每个套接字有一个进程可能更具可伸缩性。同时,如果某个进程失败,则其他所有进程将保持联机状态。
缺点:难以开发。可能是因为它消耗过多的资源来维护每个连接的进程。

您认为最佳选择是什么?任何其他想法或建议,欢迎:)

最佳答案

如评论中所述,还有一个替代方法是使用select()poll()(或者,如果您不介意使应用程序特定于平台,则使用epoll()之类的东西)。我个人建议使用poll(),因为我觉得它更方便,但是我认为至少某些版本的Windows仅提供select()-我不知道在Windows上运行是否对您很重要。

这里的基本方法是,首先将所有套接字(包括侦听套接字,如果正在侦听连接)添加到结构中,然后根据需要调用select()poll()。此调用将阻塞您的应用程序,直到至少一个套接字要读取一些数据,然后您被唤醒并经过准备读取的套接字,然后处理数据,然后再次跳回阻塞状态。通常,您可以循环执行此操作,例如:

while (running) {
int rc = poll(...);
// Handle active file descriptors here.
}


这是编写主要受IO限制的应用程序的好方法-即与使用CPU实际处理数据相比,它花费在处理网络(或磁盘)流量上的时间要多得多。

正如评论中提到的那样,另一种方法是为每个连接派生一个线程。这非常有效,您可以在每个线程中使用简单的阻塞IO来读写该连接。就个人而言,出于种种原因,我会建议您不要使用此方法,其中大部分是个人喜好。

首先,处理需要一次写入大量数据的连接很麻烦。套接字不能保证一次写入所有未决数据(即,其发送的数量可能不等于您请求的全部数量)。在这种情况下,您必须在本地缓冲待处理的数据,然后等待套接字中有足够的空间发送数据。这意味着在任何给定时间,您可能正在等待两个条件-套接字已准备好发送,或者套接字已准备好读取。当然,您可以避免在所有未决数据发送完毕之前从套接字读取数据,但这会在处理数据时引入延迟。或者,您可以仅在该连接上使用 select()poll()-但是,如果这样,为什么还要使用线程,只需以这种方式处理所有连接即可。您也可以为每个连接使用两个线程,一个用于读取,一个用于写入,如果您不确定是否总是可以在一个调用中发送所有消息,这可能是最好的方法,尽管这会使所需的线程数量增加一倍这会使您的代码更加复杂,并稍微增加资源使用量。

其次,如果您打算处理许多连接或连接流量很高,则与使用 select()或Friends相比,线程在系统上的负担更大。在大多数情况下,这并不是特别重要的事情,但这是大型应用程序的一个因素。除非您正在编写每秒处理数百个请求的网络服务器之类的东西,否则这可能不是一个实际的问题,但是我认为提到此点与参考有关。如果要编写这种规模的东西,您可能最终还是会使用混合方法,在这种方法中,您将进程,线程和非阻塞IO的某种组合相互叠加在一起。

第三,一些程序员发现线程处理起来很复杂。您需要非常小心,以使所有共享数据结构都具有线程安全性,既可以使用互斥锁(互斥锁),也可以使用其他人的库代码为您做到这一点。有很多示例和库可以帮助您解决此问题,但我只是指出需要注意-多线程编码是否适合您是一个问题。忘记锁定某些内容并让您的代码在测试中可以正常工作相对容易,因为线程不会碰到这种数据结构,然后在现实世界中负载较高时会发现难以诊断的问题。通过细心和纪律,编写健壮的多线程代码并不难,我也不反对(尽管 opinions vary),但是您应该意识到所需要的细心。在某种程度上,这适用于编写任何软件,这只是程度的问题。

除了这些问题之外,线程对于许多应用程序来说是一种非常合理的方法,并且某些人似乎发现它们比使用 select()的非阻塞IO更加易于处理。

至于您的方法,A可以工作,但会浪费CPU,因为您必须每秒唤醒一次,而不管是否有实际的有用工作要做。另外,您在处理消息时可能会延迟一秒钟,这可能会给聊天服务器带来不便。总的来说,我建议像 select()这样的方法比这更好。

选项B可以工作,但是当您想在连接之间发送消息时,您将不得不使用管道之类的东西在进程之间进行通信,这有点麻烦。您最终将不得不同时等待传入管道(用于发送数据)和套接字(用于接收数据),因此最终会遇到相同的问题,必须等待两个类似以下内容的文件句柄 select()或线程。确实,正如其他人所说,线程是分别处理每个连接的正确方法。单独的进程也比线程占用更多的资源(尽管在Linux等平台上, fork()的写时复制方法实际上并不算太糟糕)。

对于仅具有数十个连接的小型应用程序,从技术上讲,在线程和进程之间没有太多选择,这很大程度上取决于哪种样式更适合您。我个人将使用非阻塞IO(有人将其称为异步IO,但 that's not how I would use the term),并且我编写了很多代码来执行此操作以及许多多线程代码,但实际上,这仍然只是我个人的看法。

最后,如果您想编写可移植的非阻塞IO循环,我强烈建议您调查 libev(或者可能是 libevent,但就我个人而言,我发现前者更易于使用且性能更高)。这些库在不同平台上使用不同的原语,例如 select()poll(),因此您的代码可以保持不变,并且它们还倾向于提供稍微更方便的接口。

如果您对此还有其他疑问,请随时提问。

关于c++ - 多用户聊天服务器C++,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16188996/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com