Chinaunix

标题: 谈谈你接触的通讯程序的模型 [打印本页]

作者: duanjigang    时间: 2011-01-01 22:15
标题: 谈谈你接触的通讯程序的模型
本帖最后由 duanjigang 于 2011-01-10 16:33 编辑

################2011-01-06
在1,2,4,5,67,22楼对目前涉及到的三种模型或者说程序的实现方法,做了详尽的描述,欢迎大家积极拍砖,提出改进建议,或者发起实践活动来实现这样的设计思路,并与我们分享您的经验。

推荐下34楼的做法,个人感觉也很不错
########################

应用程序也做了不少了,特别是在通讯这块做了好几年,好几个项目,来来回回,思想和选择经过了好几番转变,经常会想起一些技术上的选择问题,不知道该如何取舍,今晚屋子比较安静,整理下思绪,发起这个话题,顺便听听大家的看法。

   刚开始接触网络编程时,写的最简单的通讯程序,就是server-client一对一的模型,可能还不知道有非阻塞和select等概念,只是觉得测试数据发送过去了,接收到了就很高兴,当面临多连接,多种消息类型时,就开始头大了。大概07年初的时候,我当时的想法,就跟前不久一个同学的设计思路一样,对于一对多的通讯程序,服务器监听,接受到一个客户端请求时,就为该连接创建一个线程,并且把获取到的连接套接字交给线程去使用,当子线程完成通讯或者出错后,就会关闭连接套接字,然后退出。这个模型在我当时看来很厉害了(应该比同学问那个,给每个新获取的连接套接字创建一个进程强点吧,呵呵),当时这个程序也是在客户那里跑起来,基本上线了,但是印象里出了不少问题,多线程程序在我印象里更不好调试。。反正是被这个写法折腾的够苦,后来一提到多线程处理多连接,每个线程一个连接的做法,我就头大。呵呵,不知道有没有人这样写,谈谈自己巧妙的设计或者遇到的问题?
第一种模型,是否可以用以下图来表示?

作者: duanjigang    时间: 2011-01-01 22:29
本帖最后由 duanjigang 于 2011-01-02 10:43 编辑

呵呵,早上看看昨天的文字,发现用词很土。:wink:
后来看到了第二种写法,觉得不错。

客户端程序分为两个线程:连接线程和数据处理线程。
在提出select和poll或者epoll之前,针对多个连接的通讯,大都使用的非阻塞socket吧。

     先说说连接线程,连接线程和数据线程共享一个server列表,或者叫一个server list.这个server列表的每一个节点,代表了
一个将要连接过去的主机,同时还存储了该跟主机相关的信息,最常见的是IP地址,编号,socket描述符,发送缓冲区,发送数据指针,数据总长度等信息。连接线程要做的事情很简单,遍历主机列表的每一个节点,如果发现某个主机的连接状态处于“未连接”状态,则尝试连接该主机,当然这个连接也是有超时限制的。如果连接成功,存储该到该主机的连接的socket描述符,修改连接标志位。标明该主机已经被连接上,其实也就是通知数据处理线程,该主机可以进行通讯了。
      再说数据处理线程,数据处理线程顾名思义,就是处理数据线程,从客户端的角度来说,数据处理线程大多数情况下是做出发送数据的状态。在这个模型中,也是如此,数据处理线程也遍历主机列表,检查每个主机的连接状态,如果该主机已经连接上,并且发送缓冲区有数据未发送出去,则使用非阻塞的方式向该主机发送数据,如果发送失败,则关闭socket,并且修改主机的连接状态为未连接状态,以告知连接线程,这个主机已经断开了,你丫需要重新去连接它了。如果发送成功,则更新发送缓缓冲区的数据指针,注意,这种发送没必要在一个while循环中一直发送,直到发送完成,个人觉得发送一次就可以了,不过好像也有人尝试发送最多三次。个人理解,为了减少在一台主机上的长时间发送而造成对其它主机数据发送的延迟影响,还是只发送一次好。
第二种方式的客户端程序可以用这个图来描述下。

作者: yangsf5    时间: 2011-01-02 01:25
图是红叉叉。。
没必要1个连接一个线程把。
作者: duanjigang    时间: 2011-01-02 09:01
图是红叉叉。。
没必要1个连接一个线程把。
yangsf5 发表于 2011-01-02 01:25



    嗯,是的,呵呵,最初是这么设计的,而且现在有些刚接触网络通讯类程序的人也会下意识的去这么考虑。
我是想从一开始到现在,把所有遇到过的写法大致上都总结下,大家一起讨论每种的问题,优缺点和性能比较。
作者: duanjigang    时间: 2011-01-02 11:03
本帖最后由 duanjigang 于 2011-01-02 11:06 编辑

接着来说第二种模型的服务端程序,对于服务端程序来说,跟客户端程序类似,也是两个线程工作。
      一个连接接收线程,一般来说,都是主线程来负责,创建socket后,bind,listen。。然后在一个死循环当中尝试accept来自不同主机的连接请求。每收到一个连接请求,就把该连接的IP地址,端口等信息存储到连接列表当中,并且初始化接收缓冲区等信息。
    另外一个是数据接收线程,该线程遍历连接列表,尝试从每一个连接读取数据,如果读取失败,则关闭连接套接字,释放该连接节点。如果接收成功,则检测是否是一个完整的消息,是的话,则进行处理,不是一个完整的消息,则继续缓存,更新接收缓冲区的指针。
  服务器端程序相对客户端程序来说,处理的事务少点。
服务端程序的结构和工作流程可以用下图来表示下:

作者: duanjigang    时间: 2011-01-02 11:39
第二种双线程的方式已经是一种不错的模型了,在实际中应用也不少,上面量段文字和图片说明了服务端和客户端的基本工作方式和数据结构梗概。不过有些细节还是需要注意并加以处理的:
  我们都知道,服务端和客户端都是采用非阻塞方式进行数据的发送和接收的。这样就有一个特点:对于专注从事于数据发送的客户端程序来说,如果客户端到服务端的网络出现中断,客户端的发送操作会出错,这时,客户端程序能够发现这个故障,并且能够进行正确的处理(关闭socket,后面尝试重新连接)。而对于服务器端程序来说,因为它遍历连接列表进行数据接收,采用的是非阻塞方式,即使客户端的连接已经断开,他调用recv操作,返回的结果跟没有收到数据是一样的,都是0(这个结论我应该没有记错吧,如果有错,望指出)。也就是说,服务端通过只读取的操作是无法发现客户端的断开状态的。从系统资源角度考虑,会造成很多未完全关闭的连接,另外,从服务端程序来考虑,也是无法忍受的,比如服务端采用的连接列表长度为1000,如果有几十个客户端断开连接并且重新连接几次,这样在服务端的连接列表里会增加上百个貌似正常实际上却是无效的连接节点,时间再长点,就会导致服务端列表的溢出,另外,从后面的话题的角度出发,列表过长,会导致接收线程遍历的节点次数增大,浪费过多的CPU时间。
作者: duanjigang    时间: 2011-01-02 11:56
本帖最后由 duanjigang 于 2011-01-02 19:10 编辑

接着6楼的问题。
  既然只读取的操作不能发现连接的断开,为什么不增加一个发送操作?是的,这个想法很好,不过我们要明白,这个发送操作的目的只是为了检测客户端连接的状态。
  既然服务端会通过发送数据的方法来检测客户端的在线状态,那么客户端就不能置之不理,礼尚往来还是有必要的,因为如果客户端不理会服务端的发送数据的话,服务端发送到客户端接收缓冲区的数据就不会被读取,如果不存在超时丢弃或者覆盖的话,接收缓冲区总会被写满的。因此,客户端还是需要去读取服务端发送过来的检验数据的。
   跟据上面的情形,可以增加这样一项工作,在服务器端的主机列表的每个节点上存储接收到客户端数据的最新时间,然后定义一个超时时间,如果超过这个时限没有收到来自某个主机的数据,则尝试向该主机发送一个握手消息,这时就能检测是否连接已经断开了。
   对于客户端而言,为每一个服务器保存一个发送数据的最新时间,如果最后一次发送数据的时间到目前的时间差超过这个时限,则尝试从服务端读取握手消息,这样就能把服务器端发过来的握手消息接收过来,比较理想的解决了上面讨论中可能出现的问题。
     这种非阻塞的模型,有些人认为是比较浪费CPU时间的,不过通过一些方法可以适当降低CPU使用的,在连接列表不是特别长,并且每个连接都不是特别空闲的情况下,性能还是很不错的。
后面有时间,我把这种模型的实现整理下,放出来大家可以参考下。
作者: Godbach    时间: 2011-01-02 17:58
duan 兄好文章,受益匪浅。
作者: duanjigang    时间: 2011-01-02 18:59
duan 兄好文章,受益匪浅。
Godbach 发表于 2011-01-02 17:58



    呵呵,只是想把自己遇到的各种情形列举下,希望大家能够讨论每种模型的应用范畴和优缺点。今天还没写完,明天接着总结:wink:
老赵多多提点意见啊
作者: duanjigang    时间: 2011-01-02 19:12
到目前为止,提到了两种模型,欢迎大家积极拍砖讨论喔。
关于select 和epoll等模型,欢迎熟悉的朋友积极发表感受。我明天写写基于ICE的模型(也是我目前正在使用的方式)。:wink:
作者: gvim    时间: 2011-01-02 20:03
本帖最后由 gvim 于 2011-01-02 20:29 编辑

我觉得多进程也是一个可以考虑的设计方向,很多时候,比多线程优势更大。线程的优势在于调度和资源量,问题在大量多线程系统本身的复杂和调试的困难。
两个实例:1 是postgresql的服务模型,进程+shared mem。共享内存只处理全局状态,每一个进程自我处理资源,现在大部分OS的任务切换性能不在是瓶颈。代码很清晰,性能也很高,网上有个图,可以看看。 不好的一点是内存消耗量稍大。如果需要控制进程数量,一般前面有连接池。
2 是基于actor模型的通讯方式,如erlang,(虽然此进程非彼进程,但是实质是一样的"进程”)众所周知的是它的扩展性和分布特性。
另外,每进程的耦合较低,如果不需要处理事务的话,我更倾向于进程,这样可以在适当的时候轻松的把计算分布到其它物理资源上处理(比如erlang)。

我做的关乎server的设计几乎是这个模式的。
作者: gvim    时间: 2011-01-02 20:23
回复 10# duanjigang


个人觉得既然是讨论模型的话,还是需要界定模型适用的领域,模型的解决方案,模型解决的问题,模型具有那些好的非功能性特性,模型的缺点,和其它模型的比较等等。
就像<design pattern>讨论的那样。
作者: duanjigang    时间: 2011-01-02 20:51
我觉得多进程也是一个可以考虑的设计方向,很多时候,比多线程优势更大。线程的优势在于调度和资源量,问题 ...
gvim 发表于 2011-01-02 20:03



    能说说多进程的任务是怎么划分的吗?呵呵,数据通讯,还有处理怎么划分的。
作者: duanjigang    时间: 2011-01-02 20:53
回复  duanjigang


个人觉得既然是讨论模型的话,还是需要界定模型适用的领域,模型的解决方案,模型解 ...
gvim 发表于 2011-01-02 20:23



    嗯,对的,我想从具体的引用谈起,可能更容易讨论起来,而后再把这些模型或者说程序的写法就行比较和总结
作者: gvim    时间: 2011-01-02 22:16
回复 13# duanjigang


回归到你给出的第一种图,不过线程换成进程。
我过去倾向于用线程,但是尝试很多之后设计和调试都随着状态或功能越复杂而越复杂,再加上我个人目标领域的关键约束不在内存上,并且感谢现代操作系统,进程和线程切换的代价已经十分靠近,所以慢慢回归到最简单的方式,现在看来真是越简单越美。直到了解了erlang的处理模式之后,更加深了这个认识。
作者: leiing    时间: 2011-01-03 00:21
楼主都是用长连接吗?
长连接和短连接应该怎么选?如网游服务器,IM服务器?
作者: duanjigang    时间: 2011-01-03 10:22
回复  duanjigang


回归到你给出的第一种图,不过线程换成进程。
我过去倾向于用线程,但是尝试很多之 ...
gvim 发表于 2011-01-02 22:16



    呵呵,请教一个问题,多线程,多进程,相当于单线程有什么优势?通讯性能更高?还是程序的处理逻辑更简单?givm熊能否说点详细的感受。谢谢
如果用单线程的话,都不需要进程的切换。
作者: duanjigang    时间: 2011-01-03 10:26
楼主都是用长连接吗?
长连接和短连接应该怎么选?如网游服务器,IM服务器?
leiing 发表于 2011-01-03 00:21



    惭愧,一提到长连接和短连接,我都有些含糊,不知道你说得是不是开发人员主动控制的连接方式。
比如长连接就是连接上目标主机后一直不断开?短连接就是通讯完成后关闭连接,下次事务再次连接?
如果是这样的话,我一般的选择就是,如果通讯很频繁,而且1对N的N不是特别大的话,就长连接,如果通讯周期很长,比如5分钟传输一次数据,搞成短连接最好吧。
这是个人的一点看法,不知对否?
我们目前接触到的情况是1对8的连接到1对300的连接,都用的恒久连接。因为几乎每分钟内都有数据传输。
作者: dreamice    时间: 2011-01-03 13:31
到目前为止,提到了两种模型,欢迎大家积极拍砖讨论喔。
关于select 和epoll等模型,欢迎熟悉的朋友积极发 ...
duanjigang 发表于 2011-01-02 19:12



    有空了只要没人抢先的话我总结,呵呵
研究了有一段时日了:wink:
作者: duanjigang    时间: 2011-01-03 16:16
有空了只要没人抢先的话我总结,呵呵
研究了有一段时日了
dreamice 发表于 2011-01-03 13:31



    老江赶紧整啊,我最近读别人的代码,select写的,还不错。epoll这块还没多少经验,新版开张,你要多多支持喔。
等咱把这些可能的模型都列出来,总结完了,再发起个实践活动,做下性能和易用性的测试。久而久之,就能形成一些稳定的模型供人试用了
作者: havey    时间: 2011-01-05 18:08
说的很好,一个很实用的话题,谢谢这些无私的朋友!
作者: duanjigang    时间: 2011-01-06 09:34
本帖最后由 duanjigang 于 2011-01-06 09:50 编辑

呵呵,感冒耽搁了几天,今天把使用ICE中间件的这种方式补充上来:wink:

第三种方式,结合ICE中间件进行设计。
ICE提供了一个多系统,多语言互通的中间件,通过使用slice语言定义一个.ice文件来实现不同程序的通讯数据结构和调用方法的定义。
然后调用slice2XX(xx=cpp,java,php..)来生成对语言的代码。对于开发人员来说,只需要把生成的java或者cpp代码包含到自己的工程中来,客户端填写数据到对象中,调用定义的函数就行,服务端继承接口中定义的虚函数,实现数据处理即可。调用也十分简单,从感性上理解,客户端调用数据处理方法,服务端的方法就被调用了,它是一个类似于RPC的机制。
    有了ICE这个强有力的助手,数据发送和接收变得异常容易了,然后我们需要处理的就是连接的维护。
    服务器端不需要维护连接,因为对他来说,服务端就是一个类似回调函数的机制,它并不关心或者不清楚在某一个时刻,自己会被哪个远程主机调用。客户端需要维护连接,因此,类似于模型2中的方法,在客户端独立一个线程,一直尝试连接未连接上的主机即可。
    讲讲这种模型下我的一般设计方法:
     主线程启动后创建两个线程,一个客户端线程,一个服务端线程,服务端线程运行后,就会处于阻塞状态,一直等待消息的到来,客户端在运行后则会一直尝试去发送数据,而主线程下来要做的事情就是:遍历它跟客户端线程共享的那个主机列表,尝试连接每一个没有连接上的主机。
    这就是第三种模型,希望有改进意见的朋友积极发表看法和意见,谢谢!
作者: duanjigang    时间: 2011-01-06 09:52
第三种模型的一个好处就是,由于ICE使得客户端和服务端的代码变得简单起来,我们可以很轻松的把客户端的功能和服务端的功能用一个程序中的两个线程来实现。这相对于1和2的模型来说,方便多了。
作者: myxxn    时间: 2011-01-06 16:33
而对于服务器端程序来说,因为它遍历连接列表进行数据接收,采用的是非阻塞方式,即使客户端的连接已经断开,他调用recv操作,返回的结果跟没有收到数据是一样的,都是0(这个结论我应该没有记错吧,如果有错,望指出)


如果是非阻塞,正常情况下如果没有数据,recv函数会返回-1,并且将错误代码置为EAGAIN,如果recv返回0,其实代表的是远端socket被关闭了

我也对服务器程序设计感兴趣,大家多交流。
作者: duanjigang    时间: 2011-01-06 16:55
如果是非阻塞,正常情况下如果没有数据,recv函数会返回-1,并且将错误代码置为EAGAIN,如果recv返回0, ...
myxxn 发表于 2011-01-06 16:33



    嗯,谢谢纠正,我晚上查下代码,看看这块具体处理流程!
作者: wucailiuxing    时间: 2011-01-07 10:55
这个话题开的太好了,我对这个话题也非常感兴趣。
一般我常用的就是第二种方式,专门开一个监听线程来处理连接,对已经连接好的分到空闲的工作线程中处理,在实际的过程中,我也遇到了客户端不正常断开,造成资源浪费的情况,我采取的方式是再开一个监控线程,来扫描判断每个连接的状态。
作者: duanjigang    时间: 2011-01-07 11:01
如果是非阻塞,正常情况下如果没有数据,recv函数会返回-1,并且将错误代码置为EAGAIN,如果recv返回0, ...
myxxn 发表于 2011-01-06 16:33



    呵呵,对您的说法我再次做了验证。
对于阻塞和非阻塞的socket,如果server端一直保持recv的操作,对于发送端的异常退出,close并且正常退出,或者没有数据,server端通过recv是不能检测到的,当返回值<0时,errno都是EAGIN,因此能不能这么说,纯粹的recv工作的socket是不能检测对端的退出的?
如果这样的话,还是需要在没有收到数据一段时间后通过发送心跳包来检测连接的状态的。:wink:
不知道我的说法有无纰漏?或者您再做做实验,指出我可能存在的问题,谢谢!
作者: duanjigang    时间: 2011-01-07 14:34
本帖最后由 duanjigang 于 2011-01-07 14:35 编辑

把myxnn给我的消息贴下,相信他应该不会介意吧,也是为了跟大家分享,呵呵:
你好,那个recv的问题我又做了一个实验,
在阻塞模式下,client自动关闭时,server端的recv收到的却是是0
非阻塞模式下我没来得及重新做,但是我之前是用epoll做的,当client关闭连接时  我会收到一个可读事件,这个时候再去读的话,也是收到的0
您的邮箱可以告诉我一下吗? 我把我的测试代码发你看看有没有问题
我的测试环境:
操做系统:Ubuntu 8.04.3 (Linux 2.6.24-24-generic x86_64 )
g++:g++ (GCC) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)

作者: duanjigang    时间: 2011-01-07 14:38
呵呵,应该跟操作系统关系不大吧。recv本身的操作就是从本地缓冲区中读取数据,应该跟发送端关系不大。
因此,对端状态的探测还是需要心跳包的。
作者: myxxn    时间: 2011-01-07 15:33
本帖最后由 myxxn 于 2011-01-07 15:38 编辑

回复 29# duanjigang

恩,心跳协议肯定是必不可少的
不过我想心跳协议重点不在于检查连接是否端掉,连接是否中断从系统里面是肯定可以探测到的,
关键心跳协议能检测到“被远程调用端”是否“死”掉,比如说请求一个远端的服务,连接虽然没断,
但是服务器已经死循环了,这个时候通过心跳协议的超时机制来检测,这时就应该主动把连接断掉,然后重新请求一个可用的服务
作者: ashlv    时间: 2011-01-08 12:10
我觉得ESB是我见过的算是比较好的通讯模型了
作者: duanjigang    时间: 2011-01-08 12:19
本帖最后由 duanjigang 于 2011-01-08 12:22 编辑
我觉得ESB是我见过的算是比较好的通讯模型了
ashlv 发表于 2011-01-08 12:10



哈哈,另外一篇文章就在说ESB。。我还是孤陋寡闻了
作者: maclaurin    时间: 2011-01-09 23:05
我常用的通讯模型:
多reactor,用round_robin处理reactor的负载均衡,reactor与TCP连接为一对多的关系。
发送的数据使用消息队列,单独线程发送,处理超时重发以及EWOULDBLOCK重发问题。
心跳协议必须的,定时器可以集成到reactor里。
服务器间通讯现在直接用protocol buffer了,方便。
作者: jiang1013nan    时间: 2011-01-09 23:45


这个是我在最近打算换工作,总结以前项目的时候随便画的。

服务器主要分为两个层次:通讯层和业务处理层。(采用epoll网络模型)

在通讯层又分为两个层次:
1)与客户端的数据交互
      该模块只负责接受请求、接受数据、发送数据(各为一个独立的线程),接收到客户端的数据之后将数据放入到数据队列中,发送数据的时候从数据发送队列中读取数据进行发送;
2)与业务层的数据交互
      该模块负责将从客户端读取的数据发送给业务处理进程和从业务处理进程读取数据,与业务处理进程通信采用的方式为:用fifo实现事件通知机制,用共享内存实现queue达到数据传输的目的

业务处理层比较简单,用select接收到事件通知后,读取数据,处理数据,再讲处理结果放到数据队列中,然后在用fifo来通知通讯层去读取数据,处理模型为reactor模型;

通讯层与业务处理层的数据是轮训方式nconnect++/业务处理层进程数。
作者: qiangtoucao121    时间: 2011-01-10 10:27
楼上的不错
作者: duanjigang    时间: 2011-01-10 16:21
这个是我在最近打算换工作,总结以前项目的时候随便画的。

服务器主要分为两个层次:通讯层和业务处 ...
jiang1013nan 发表于 2011-01-09 23:45



    jiang103nan兄弟这个模型不错,我最近看了epoll后也是想把以前的模型改成你描述这样的。
对了,性能测试有做过吗?性能如何,并发如何?
作者: duanjigang    时间: 2011-01-10 16:24
我常用的通讯模型:
多reactor,用round_robin处理reactor的负载均衡,reactor与TCP连接为一对多的关系。
...
maclaurin 发表于 2011-01-09 23:05



    嗯,听说protocol buffer是很不错的一个咚咚,有空研究下。兄弟有空关于您使用的模型能详细说点不?
到数据结构,算法层面。呵呵
作者: duanjigang    时间: 2011-01-10 16:32
同请教33楼和34楼两个问题:
第一:你们的通讯程序独立于应用程序吗???
也就是说把通讯做成纯粹的通讯程序,要增加应用业务,对通讯而言只需要修改配置文件或者根本什么都不需要做?

第二:通讯程序发送的数据是分包的吗?对于大数据量发送怎么做的?比如A应用通过我们的通讯程序想发送一个文件到B应用,怎么做的?
谢谢!
作者: jiang1013nan    时间: 2011-01-10 20:51
回复 36# duanjigang

在实际生产环境中达到了8K人左右额。
作者: maclaurin    时间: 2011-01-10 22:00
同请教33楼和34楼两个问题:
第一:你们的通讯程序独立于应用程序吗???
也就是说把通讯做成纯粹的通讯 ...
duanjigang 发表于 2011-01-10 16:32



第一:你们的通讯程序独立于应用程序吗???
独立,一个前置通讯网关对应多个应用服务器。

第二:通讯程序发送的数据是分包的吗?对于大数据量发送怎么做的?比如A应用通过我们的通讯程序想发送一个文件到B应用,怎么做的?
应用层分包?如果是TCP,应用层不管分包的事,如果是UDP的话,就要考虑分包,要用到应用层协议里的frame_seq来维护包和包之间的上下文关系。发送文件,linux下用sendfile,windows下TransmitFile。

关于通讯模型,最近一直想用epoll_reactor来模拟proactor,boost::asio是实现了,可惜C用不上。
作者: jiang1013nan    时间: 2011-01-10 22:59
回复 40# maclaurin


    是的。对于处理UDP,目前我们没有好的处理方案对于通讯与业务分离的架构。在实际生产环境中,我们采用的是TCP的方式。
作者: 一介村夫    时间: 2011-01-10 23:21
本帖最后由 一介村夫 于 2011-01-10 23:24 编辑

多线程模型就像人弹,有人要死,却拉上一大堆垫背的。

我再说一遍:多进程模型是用于每个进程干自己的事,互相不影响;多线程模型是用于所有线程协调干一件事,每个线程负责一个部分。

多线程模型中的线程,彼此的工作内容应该是不同的,同时又是互补的,大家完成的是同一件事的不同部分。基于这样的理解进行设计,就不存在“人弹”的可能性。

所以在一般通讯服务程序的场合,绝对应该使用多进程模型。

进程与线程,看着相似,实际的出发点和作用是不同的,所以不能乱用,要根据应用模型进行选择。再说得浅显点就是:在一个多线程模型中,任何两个线程都不应该使用同一个函数作为入口点!
作者: jiang1013nan    时间: 2011-01-11 09:05
回复 42# 一介村夫


嗯。有一定道理嗯。
但是也存在多个线程干一类事情,但是相互独立的事情。
作者: duanjigang    时间: 2011-01-11 09:53
第一:你们的通讯程序独立于应用程序吗???
独立,一个前置通讯网关对应多个应用服务器。

第二 ...
maclaurin 发表于 2011-01-10 22:00



    恩,大致明白了。有个问题喔,通讯程序相当于做成了一个独立的中间件了。这样的话,应用程序其实不必再关注通讯程序使用到的协议了吧?
如果你们的通讯已经独立于应用了,我想应该是这样吧?那么,这时是不是都应该用TCP来实现?为了可靠起见。谢谢
作者: 一介村夫    时间: 2011-01-11 10:00
回复  一介村夫


嗯。有一定道理嗯。
但是也存在多个线程干一类事情,但是相互独立的事情。
jiang1013nan 发表于 2011-01-11 09:05



    一个进程崩溃,毁掉的是一大批事情,而不仅仅是出问题的那一个,这不合理。
而且在这种模型中,你无法中断一个已经出现问题的独立事情。

进程和线程在系统开销上的那点差别,远远小于你应用程序写得好与不好的差别,不同的人为同一个事情写程序,开销差别都很大,何必在乎进程和线程开销间的这一点差别呢?真在乎系统开销,就先认真写好自己的应用程序吧。

所以,一定要选择适合的模型做适合的事情。
作者: duanjigang    时间: 2011-01-11 10:17
多线程模型就像人弹,有人要死,却拉上一大堆垫背的。

我再说一遍:多进程模型是用于每个进程干自己的事 ...
一介村夫 发表于 2011-01-10 23:21



    阿村说的很精辟,应该是多年的经验感言!
但是有些人出于节省代码量的想法还是违背这些原则了,比如为了使用公共的全局变量,节省代码量,把通讯的server端和client端做成了两个线程。
呵呵,我就这么搞过
关于数据处理部分,现在完全做成了独立的进程,每个消息处理插件一个子进程,也是充分考虑到“人弹”这个因素了。
看样子通讯程序本身,还应该再分离出来。这样才合理。
作者: duanjigang    时间: 2011-01-11 10:23
当然:连接和数据发送在一个进程中用多线程,这是合理的。
但是连接和数据发送线程所属的进程和数据接收部分,应该独立开,是两个进程
作者: swxlion    时间: 2011-01-11 12:02
回复  duanjigang

在实际生产环境中达到了8K人左右额。
jiang1013nan 发表于 2011-01-10 20:51



    关于这个数据,是在怎么样的情况下获得的?
不知单台服务器是怎样的配置情况:操作系统、CPU位数、CPU个数x单个CPU核数、内存大小,网卡/网络带宽限制、非业务相关压力负载情况(比如在同一台机器上,是否有web服务器正在提供非被测业务的服务?)。每秒发包数、平均包大小。因为在我以前的性能测试经验中,这些对最终性能数据影响很大。如果没有全部说明的话,则对比性就不强了。
作者: qianhulou    时间: 2011-01-11 12:42
我一直都用的阻塞, 但是对于非阻塞 我个人认为和阻塞差不多.  对端主动关闭, RECV返回0, 对端异常关闭 非close() shutdown的 RECV返回-1, 如果没有数据来 RECV也返回-1, 根据errno来区分是哪中情况. 心跳包 是用来检测路由的, 比如A->switch->B , switch那里网线掉了, A和 B一般情况是检测不到的, 除非那个交换机有这个功能或者SOCKET设置了那个TCP自测的选项.如果A->B直连的话, 只要网线中间不断开, 有没有心跳包 效果一样....
作者: ruifengzhangyi    时间: 2011-01-11 14:48
有个问题想请教ls各位大拿
采用第一种模型
while(1)
{
accept()
pthread_create()
}
是否存在一些弊端,对于高并发的请求的情况下,服务器容易当掉,只能采取没有办法的办法在pthread_create()
后睡眠几微秒,可以一定程度的缓解。
作者: jiang1013nan    时间: 2011-01-11 15:14
回复 45# 一介村夫


   嗯。没错。关键是合适的场景选择合适的模型,其中一个线程出现问题,一个进程都会崩溃。多个进程则不会出现同样的问题,apache就是多个子进程干同样的事情。
作者: jiang1013nan    时间: 2011-01-11 15:19
回复 47# duanjigang


如果将读和写分别放在不同的进程中,就意味着server和client至少保持一个连接,代价是不是太高啦?如果将读和写分别放在不同的线程中,只需要保持一个连接。
作者: jiang1013nan    时间: 2011-01-11 15:21
回复 44# duanjigang


  是的,没错。

通讯层就是类似于数据中间件,负责client与业务处理层面的数据接受与发现。如果涉及到udp的话,采用这种方式似乎有些困难额。
作者: jiang1013nan    时间: 2011-01-11 15:23
回复 46# duanjigang


    我认同将通讯和业务进行分离,通讯只负责数据的接受;业务负责处理数据,很大情况下,业务处理出现崩溃的情况更多一些。
作者: jiang1013nan    时间: 2011-01-11 15:30
回复 48# swxlion


    64位,8核,16G内存
作者: jiang1013nan    时间: 2011-01-11 15:32
该版内有很多牛人额。继续学习,继续接受大家的BS哈。

继续去找工作哈。
作者: 网鬼    时间: 2011-01-11 17:55
拜读学习。。。
作者: logicBaby    时间: 2011-01-11 23:28
多线程。。。一旦core掉就悲剧了。。。。。。

所以我们的都是多进程的,而且有专门的维护进程定期监控各任务进程的状态。

我们的模型很简单,tcpSvr接到链接后派生相应的XXXTask进程完成请求的功能,完成后推出。或者可以预先启动数个XXXTask进程等待tcpSvr的任务分派。
作者: gvim    时间: 2011-01-12 00:24
本帖最后由 gvim 于 2011-01-12 00:25 编辑
多线程。。。一旦core掉就悲剧了。。。。。。

所以我们的都是多进程的,而且有专门的维护进程定期监控各 ...
logicBaby 发表于 2011-01-11 23:28


简单的异常监控,如果是unix下的采用父子模式,我目前用的一种监控方案是用子进程事件来管理,比如user1,user2,SIGCHLD,父进程就可以作为各子进程的维护进程。这样不用定期,反正子进程有异常了事件就过来了。
当然,这样做是比较粗略的监控方式。如果要做精细监控就不合适了。
作者: gvim    时间: 2011-01-12 01:19
呵呵,请教一个问题,多线程,多进程,相当于单线程有什么优势?通讯性能更高?还是程序的处理逻辑更简单?givm熊能否说点详细的感受。谢谢
如果用单线程的话,都不需要进程的切换
duanjigang 发表于 2011-01-03 10:22


优势不少,劣势在内存上。
一般用途的通讯服务器必然涉及并发处理,所以很难看到有单线程的通讯服务产品。只要涉及并发,不管是进程还是线程都要切换。比较新的unix/linux基本切换开销相差很小,当然并不是说没有差距。我测过适合我的服务负载的吞吐差别不大,PC 4核4G,测试目标好像是2000还是3000并发,记不太清楚了。
摩尔定理也是设计上的预测,如果可以通过加强机器性能,我宁愿选择代码的维护,扩展和进程任务的相对单一性。
就如同用烂的OOP一样,更多的设计增加了层,也增加了复杂性。当然,依据设计对象不同也可能是必须的。
作者: 还魂僵尸    时间: 2011-01-13 09:04
接着6楼的问题。
  既然只读取的操作不能发现连接的断开,为什么不增加一个发送操作?是的,这个想法很好, ...
duanjigang 发表于 2011-01-02 11:56



    这个说白了就是一个心跳检测?如果用TCP,个人比较倾向于用TCP本身的心跳,这样,服务器和客户端可以分别自己选择是否处理心跳,交给传输协议自己处理吧……
作者: 一介村夫    时间: 2011-01-13 10:46
这个说白了就是一个心跳检测?如果用TCP,个人比较倾向于用TCP本身的心跳,这样,服务器和客户端 ...
还魂僵尸 发表于 2011-01-13 09:04



    这个心跳太弱了,拔网线之类的没法检测。
作者: duanjigang    时间: 2011-01-13 11:29
还有人对ICE这个中间件使用过的没?说说使用感受和自己的做法。
这个话题已经收集了不少意见和看法,过几天筹划下,发起一个通讯类中间件实践的项目,欢迎大家踊跃参与喔
也可以就这个中间件的功能和自己的设计方法发表意见。
作者: wheniwasyoung    时间: 2011-01-14 09:20
回复  duanjigang


  是的,没错。

通讯层就是类似于数据中间件,负责client与业务处理层面的数据接 ...
jiang1013nan 发表于 2011-01-11 15:21



    为什么UDP就不太可行呢,我觉得UDP一样,同样通讯层跟业务层分开,对于大数据,可以接收后组包
作者: 贺兰云天    时间: 2011-01-14 10:08
为什么UDP就不太可行呢,我觉得UDP一样,同样通讯层跟业务层分开,对于大数据,可以接收后组包
wheniwasyoung 发表于 2011-01-14 09:20



    恩,可以的,UDP自己再分包?组包?么非又要用UDP实现一个可靠的流式传输?:wink:
作者: wucailiuxing    时间: 2011-01-14 11:04
UDP传输进行分包的方式 有两种情况是必须要考虑的 一种就是接收的包顺序错乱,这种情况还是比较好处理的 第二种情况就是包丢失 这个情况处理起来比较复杂
作者: wheniwasyoung    时间: 2011-01-14 16:58
UDP传输进行分包的方式 有两种情况是必须要考虑的 一种就是接收的包顺序错乱,这种情况还是比较好处理的 第 ...
wucailiuxing 发表于 2011-01-14 11:04



   要实现重发机制
作者: yan8790511    时间: 2011-01-15 01:27
mark,坐看高手论道
作者: gawk    时间: 2011-01-15 14:16
第二种双线程的方式已经是一种不错的模型了,在实际中应用也不少,上面量段文字和图片说明了服务端和客户端 ...
duanjigang 发表于 2011-01-02 11:39

man recv
If  no messages are available at the socket, the receive calls wait for
       a message to arrive, unless the socket is nonblocking  (see  fcntl(2)),
       in  which case the value -1 is returned and the external variable errno
       is set to EAGAIN or EWOULDBLOCK.  The receive calls normally return any
       data  available,  up  to  the requested amount, rather than waiting for
       receipt of the full amount requested.


RETURN VALUE
       These calls return the number of bytes received,  or  -1  if  an  error
       occurred.   The  return  value will be 0 when the peer has performed an
       orderly shutdown.


ERRORS
       These are some standard errors generated by the  socket  layer.   Addi鈥
       tional  errors may be generated and returned from the underlying proto鈥
       col modules; see their manual pages.

       EAGAIN or EWOULDBLOCK
              The socket is marked nonblocking and the receive operation would
              block, or a receive timeout had been set and the timeout expired
              before data was received.  POSIX.1-2001 allows either  error  to
              be  returned for this case, and does not require these constants
              to have the same value, so a portable application  should  check
              for both possibilities.
作者: shtr    时间: 2011-01-23 02:36
我不赞成楼上各位所说的用进程取代线程以防止异常终止的情况,可能是我代码写的少,不明白为什么会发生异常终止。且不说这个,个人觉得好的架构应该是平台无关的,在Windows平台,如何实现fork?CreateProcess?另外,个人喜欢LF线程池模型,简单,稳定。
作者: duanjigang    时间: 2011-01-23 11:42
我不赞成楼上各位所说的用进程取代线程以防止异常终止的情况,可能是我代码写的少,不明白为什么会发生异常 ...
shtr 发表于 2011-01-23 02:36



    从科研的角度讲不应该出现异常终止,那就是对代码要求考虑到一切情况,而且进行处理。从工程的角度讲,还是要考虑的。
我同意你的架构讨论平台无关的东西这种说法。不过线程和进程的概念本来在windows和linux都是支持的。只是实现不一样而已,而接口都差不多是一样的形式
作者: 一介村夫    时间: 2011-01-23 13:37
关于线程和进程,从来没有说谁只能用来干什么,就像你用牛刀杀鸡,也未尝不可,方便不方便、顺手不顺手,自己感觉就是了。
看一看经典的教科书,讲线程部分的举例,都是用的生产者/消费者模型,从来没有用多通道并行模型的。再看看经典的开源软件,比如apache之类的,也都是用多进程而不是多线程的。
在很长一段时间里,我也同样对多进程和多线程之间的区别没有概念,直到有一天设计一个产品时我忽然明白了,多线程和多进程应该是面向不同应用模型的,它们的设计出发点就有不同,是用来干不同的事情的。于是我在这里说出来,就是希望别人少走这样的弯路。
如果你一时还接受不了,那就在以后的工作中慢慢摸索和体会吧。
作者: ruifengzhangyi    时间: 2011-01-25 10:10
有个问题想请教ls各位大拿
采用第一种模型
while(1)
{
accept()
pthread_create()
}
是否存在一些 ...
ruifengzhangyi 发表于 2011-01-11 14:48



    这个问题难道就没有人知道吗?大拿们。
作者: duanjigang    时间: 2011-01-25 10:59
本帖最后由 duanjigang 于 2011-01-25 11:02 编辑
有个问题想请教ls各位大拿
采用第一种模型
while(1)
{
accept()
pthread_create()
}
是否存在一些 ...
ruifengzhangyi 发表于 2011-01-11 14:48



    呵呵,有两点请斟酌:
第一:是否应该使用多线程来处理每个连接的请求?
第二:是否应该动态创建线程来处理每个请求?为什么不在初始化时创建好一个线程组,在接受连接的时候只是去分配任务给线程,这样效率高点吧。

关于第一个问题,我现在的做法是数据的接收和发送各用一个进程,因为他们独立,并不存在什么协作关系。(这大抵也是按照村夫说的原则来考虑的吧)
数据处理,是用别的进程来完成,也就是说,我并不支持用多线程来处理请求或者数据,具体原因,参考“一介村夫”等人的回帖。

关于第二个问题:我以前做过一个程序采用的是多线程处理,目标就是通过多线程来自动登录2000台服务器,检查这些机器上配置文件是否正常。
一开始想到动态线程,就觉得麻烦,因为我写代码喜欢按部就班的步骤,先设置初始状态,然后开启工作状态最后是结束状态。而且对于所有模块都是这个原则,如果在数据已经到来时,创建线程,初始线程的状态,这样对我个人来说,感觉逻辑很乱,而且动态创建线程对于响应要求较高的应用是有效率问题的。
我当时的做法是:2000台机器,配置文件中设置线程个数,比如50个,然后每个线程能分配40个节点,然后给每个线程都分配了独立的任务节点来存储数据,这样就能达到完全的并发独立的去执行,一台机器的执行任务需要3秒钟,40个节点最快120秒,当然最后实际时间是4分钟多(我想这是由于CPU个数不足50个,不能达到完全的并发和网络资源的限制引起的吧?),不过这个结果对我来说已经很好了。

说面得瑟了这么多,归根结底一个目标,设计程序时,尽量避免动态的开销和申请资源,在初始化时把能做的事情做完,这样可能你就能对程序执行的结果和时空消费做出估算了。而且也利于程序的稳定。呵呵
作者: duanjigang    时间: 2011-01-25 11:02
我提到的那个多线程应用的实例在这里
http://linux.chinaunix.net/bbs/thread-1028015-1-1.html
可以参考下,或者给提出建议,谢谢
作者: dreamice    时间: 2011-01-25 11:06
呵呵,有两点请斟酌:
第一:是否应该使用多线程来处理每个连接的请求?
第二:是否应该动态创 ...
duanjigang 发表于 2011-01-25 10:59



    很赞成段英雄的观点。
其实感兴趣的可以研究一下apache,nginx等web服务器的处理机制,充分利用了进(线)程池,内存池的机制,也就是在初始化的时候,就创建一定数量的进(线)程,当请求服务到达的时候,不必再去为每个请求创建进(线)程来处理。同时,所创建的进(线)程资源还可以重复利用,即一个进程处理完一个请求后,并不直接退出,可接着处理后续的请求服务。这样可以大大的减少进程的创建和销毁带来的系统开销,从而极大的提高效率。
很多时候,如果系统资源紧张,可能导致进程创建失败,内存分配失败。因此,预先创建这样的“池”,不仅有利于对服务资源的统一管理,更有利于其可靠性方面的管理。
作者: duanjigang    时间: 2011-01-25 11:23
呵呵,听dreamice说apache或许是用多进程负责通讯,也就是说接受到的socket能传递个子进程进行数据收发的继续,不知道这个是否真的可以,而且性能如何?
有空做个试验。
我看过下载加速器axel代码,知道它是用单进程多连接来实现通讯的,也就是说通讯是一个进程负责的。
不知道跟多进程通讯的差别在哪?
有知晓的前辈能否说两句?
作者: ruifengzhangyi    时间: 2011-01-25 16:52
呵呵,有两点请斟酌:
第一:是否应该使用多线程来处理每个连接的请求?
第二:是否应该动态创 ...
duanjigang 发表于 2011-01-25 10:59



    很给力啊,多谢!
作者: duanjigang    时间: 2011-01-26 22:12
这个是我在最近打算换工作,总结以前项目的时候随便画的。

服务器主要分为两个层次:通讯层和业务处 ...
jiang1013nan 发表于 2011-01-09 23:45



    34楼的模型貌似有些复杂,呵呵。
从这个帖子的讨论中,我越发觉得数据的收和发应该分开成两个进程。
用两个线程来负责收发,一个线程负责接受连接这种做法是否为了利用公共的连接描述符??也就是说到一个主机使用的是一条连接负责数据收发,还是收发各一条连接??
另外,用epoll的话,是不是连接接受线程和数据接收线程能合并在一起了?
作者: CU第一菜    时间: 2011-01-27 08:57
34楼的模型貌似有些复杂,呵呵。
从这个帖子的讨论中,我越发觉得数据的收和发应该分开成两个进 ...
duanjigang 发表于 2011-01-26 22:12



    epoll并没有改变传统的select或poll等处理方式。大多数模型还是一个通信控制线程,多个数据处理线程。
作者: duanjigang    时间: 2011-01-27 09:41
epoll并没有改变传统的select或poll等处理方式。大多数模型还是一个通信控制线程,多个数据处理线 ...
CU第一菜 发表于 2011-01-27 08:57



    数据处理是独立的,是不是用进程更好些?因为它们之间没有什么需要协作的
作者: CU第一菜    时间: 2011-01-27 09:45
数据处理是独立的,是不是用进程更好些?因为它们之间没有什么需要协作的
duanjigang 发表于 2011-01-27 09:41



    进程和线程没有太大的本质区别,linux下个人偏好还是进程好,因为线程的话,如果主线程挂掉,会导致子线程也崩溃,可能出现一些异常。
也不能说完全没有协作,比如说通信请求到达,需要指派给某个具体的进程来处理,这就是相应的协作关系了。
作者: duanjigang    时间: 2011-01-27 10:06
比如说通信请求到达,需要指派给某个具体的进程来处理,这就是相应的协作关系了。

CU第一菜 发表于 2011-01-27 09:45


这个指的是父进程把接收到的连接套接字传递给子进程继续通讯吗??
这样的话开销会不会很大?我意思通讯数据接收一个进程就够了,数据处理让多个进程来完成···
作者: CU第一菜    时间: 2011-01-27 10:20
这个指的是父进程把接收到的连接套接字传递给子进程继续通讯吗??
这样的话开销会不会很大?我 ...
duanjigang 发表于 2011-01-27 10:06



    设计的原则我觉得可以参考nginx,主进程只负责通信控制,数据处理有子进程来完成。
如果如版主大牛说的父进程进行通讯,那如果多个某些通信要进行多次交互,第一次数据处理由子进程A完成,而后续的数据交互处理,是不是可能再由主进程分派给其他子进程来完成了?
apache里面似乎是一个子进程负责处理完一个完整的tcp连接通信(待核实),让子进程体现会话处理的完整性,是不是也是一种较好的方式。
作者: dreamice    时间: 2011-01-27 10:24
这个指的是父进程把接收到的连接套接字传递给子进程继续通讯吗??
这样的话开销会不会很大?我 ...
duanjigang 发表于 2011-01-27 10:06



  英雄:  通信的开销应该通过其通信方式来判定吧,设计一个比较优化的方式,可能效率还是很高的。
作者: CU第一菜    时间: 2011-01-27 12:13
如果数据的收发走两条连接的话,我们完全可以把通讯只用一个进程来做,数据处理多个子进程来实现 ...
duanjigang 发表于 2011-01-27 10:33



    如果数据发送是以接收到的数据为判断前提的,那还是存在数据接收和发送之间的通信同步了。个人感觉,以tcp连接会话为单位来做为进程处理更合理一些。
作者: 执一    时间: 2011-01-27 13:47
apache中用计分板的方式实现父进程和子进程之间的通信,记分板有点类似于共享内存。主进程和子进程之间又是通过管道来作为控制的,比如说主进程要结束子进程,子进程要向父进程报告错误等。
作者: dreamice    时间: 2011-01-27 14:58
apache中用计分板的方式实现父进程和子进程之间的通信,记分板有点类似于共享内存。主进程和子进程之间又是 ...
执一 发表于 2011-01-27 13:47



    apache的多进程处理图

作者: duanjigang    时间: 2011-01-27 15:05
多谢江总分享,还没看apache的代码,看这个图暂时没啥深切体会,只是觉得跟我目前做的模型很像:wink:
作者: dreamice    时间: 2011-01-27 16:03
多谢江总分享,还没看apache的代码,看这个图暂时没啥深切体会,只是觉得跟我目前做的模型很像
duanjigang 发表于 2011-01-27 15:05



    跟你的机制可能差异还是很大。

apache的主进程只负责控制,在初始化时创建一堆子进程,子进程通过记分牌和管道与主进程通信。
主进程除了控制子进程外,不做通信处理。子进程通过竞争获得监听权,然后进行一系列的处理,因此apache的进程是负责一系列的连接处理的。这样避免了进程间通信,效率还是很可观的。
作者: 执一    时间: 2011-01-27 16:13
跟你的机制可能差异还是很大。

apache的主进程只负责控制,在初始化时创建一堆子进程,子进程 ...
dreamice 发表于 2011-01-27 16:03



    apache的业务处理相对单一,只针对http,且http本身无状态的特性。
如果是多业务复杂的进程池处理模型,该方案就不一定可行了。
作者: duanjigang    时间: 2011-01-27 16:58
apache的业务处理相对单一,只针对http,且http本身无状态的特性。
如果是多业务复杂的进程池处 ...
执一 发表于 2011-01-27 16:13



    嗯,对对对,刚才在QQ跟dreamice就聊到这个了,对于未知业务的数据通讯,这样做是不行的
作者: 执一    时间: 2011-01-27 17:10
嗯,对对对,刚才在QQ跟dreamice就聊到这个了,对于未知业务的数据通讯,这样做是不行的:mrgreen ...
duanjigang 发表于 2011-01-27 16:58



    期待版主大牛的模型
作者: duanjigang    时间: 2012-10-07 14:08
再温习下
作者: bingye1086    时间: 2012-11-06 11:24
回复 94# duanjigang
花了好长时间看完了这篇贴子,考虑只用C语言的情况下总结了三种情况,可能总结的不对,
1、单进程,多线程,每个线程负责一个客户端的数据的接收发送处理
2、单进程,多线程,一个连接线程,一个数据处理线程,使用遍历的方式处理所有连接
以上两种模型在一般的使用环境中,在帖子里好像不被推荐
3、多进程,一个进程负责数据的发送接收,管理网络连接,一个进程负责网络数据的处理


另外,帖子中提到,现在进程和线程切换消耗资源差不多,但好像都是用在服务器上,所以问一下大牛在嵌入式中,这两种切换的消耗是否有明显的差距?


   
作者: tkchks    时间: 2014-07-09 19:18
java怎么实现多进程?
作者: BetonArmEE    时间: 2014-10-11 19:45
提示: 作者被禁止或删除 内容自动屏蔽
作者: mousexqshe    时间: 2015-01-07 16:25
留个记号,以后学习。
作者: wlmqgzm    时间: 2015-10-30 14:58
好吧, 我来晚了,
[C++] ASIO 无锁, 高并发,高可靠网络架构,抗DOS,无锁内存数据库, 奔腾双核CPU32.5万QPS ECHO
http://bbs.chinaunix.net/thread-4189684-1-1.html
作者: mousexqshe    时间: 2016-03-29 11:28
我是来学习的。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2