网络IO与逻辑处理的分离

 

http://blog.csdn.net/lfhfut/article/details/1128759

不错的博客,服务器开发的重点

http://www.cnblogs.com/my_life/articles/5389009.html

 

 在ACE的sample中,对数据包的处理都是在接收到数据后立即进行,而在实际的网络应用中,由于某些逻辑处理可能会占用比较长的时间,因此有可能会阻塞网络数据包的接收,导致对方发送数据失败。


解决的方法是将网络IO与逻辑处理相分离,分别在独立的线程中运行,使用消息队列来进行数据缓冲。这样即使某个数据包的处理时间过长,也不会影响到IO线程的数据接收。

当然,如果逻辑处理的效率一直低于IO,问题将会越来越严重,此时可以通过为消息队列设置高水位来阻塞IO线程的数据接收,或者使用线程池的方式进行并发的逻辑处理,提高逻辑处理的效率。

【线程池:如何保证同一个客户端的消息的顺序处理?  把不同的client hash到不同的逻辑线程上,今后来自该客户端的所有的消息都由该逻辑线程来处理,这就保证了顺序】

 

网络IO与逻辑处理分离的实现比较简单,从ACE_Task分别派生两个线程,一个作为网络IO,一个作为逻辑处理。从ACE_Task派生的对象自动拥有了ACE_Message_Queue消息队列,网络IO线程其实不需要消息队列,可以从ACE_Task_Base派生。

IO线程接收到的数据都放到逻辑线程的消息队列中,当然,头部要加上一个标识,用于指出是哪个客户端发来的数据包。

在逻辑线程中使用一个映射表来保存每个Socket Handler到其处理器的对应。逻辑线程从消息队列中取出数据包后,根据其头部的标识找出对应的处理器来处理该消息。

 

一个可能的网络线程类申明为:
class RealmNetwork_Thread : public ACE_Task_Base
{
public:
   RealmNetwork_Thread();
   virtual ~RealmNetwork_Thread();

   virtual int svc();

   int open();
   int close();

private:
   bool running_;
   ACE_Proactor * proactor_;
};

 

逻辑处理线程类申明为:
class RealmLogic_Thread : public ACE_Task<ACE_MT_SYNCH>
{
public:
   RealmLogic_Thread();
   virtual ~RealmLogic_Thread();

   virtual int svc();

   int open();
   int close();

private:
   bool running_;

   typedef std::map<unsigned int, Realm_Client_Service *> ClientServiceMap;
   ClientServiceMap client_service_map_;
};

 

网络IO线程的实现使用 创建一个可正常结束的Proactor服务器 中的方法,只是在handle_read_stream()中收到完整的数据包后要使用putq()方法将该数据包发送到逻辑线程的消息队列中,另外在连接建立和连接关闭时也要通知逻辑线程,以进行相应的映射建立和删除操作。

逻辑线程的实现就是循环地从消息队列中取数据包并执行相应的处理即可。


一个应用的实例见 WOW用户认证的方式-SRP 中的服务器。 

 

=========================================================

http://blog.csdn.net/blade2001/article/details/4370823

双缓冲消息队列-减少锁竞争

在网络应用服务器端, 为了性能和防止阻塞, 经常会把逻辑处理和I/O处理分离:
 I/O网络线程处理I/O事件: 数据包的接收和发送, 连接的建立和维护等.
 逻辑线程要对收到的数据包进行逻辑处理.
 
通常网络线程和逻辑线程之间是通过数据包队列来交换信息, 简单来说就是一个生产者-消费者模式.
这个队列是多个线程在共享访问必须加锁, 意味着每次访问都要加锁。如何更好的如何减少锁竞争次数呢 ?


方案一  双缓冲消息队列: 
 两个队列,一个给逻辑线程读,一个给IO线程用来写,当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。
 IO线程每次写队列时都要加锁,逻辑线程在调换队列时也需要加锁,但逻辑线程在读队列时是不需要加锁的.
 
队列缓冲区的大小要根据数据量的大小进行调整的,如果缓冲区很小,就能更及时的处理数据,但吞吐量以及出现资源竞争的几率大多了。
可以给缓冲队列设置最大上限,超过上限的数量之后,将包丢弃不插入队列。
另外,双缓冲的实现也有不同策略的,
 一是读操作优先,就是生产者只要发现空闲缓冲,马上swap,
 二是写线程只有在当前的缓冲区写满了,才进行swap操作。
 三是上层逻辑按照帧率来处理,每一帧的时候将双层缓冲队列调换一下,取一个队列来处理即可.

 

方案二  提供一个队列容器: 
 提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时,会从队列容器中取一个空队列来使用,
 直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取,处理完后清空队列再放回到容器中。 
  这样便使得只有在对队列容器进行操作时才需要加锁,而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁,所以锁竞争的机会大大减少了。 
  这里为每个队列设了个最大消息数,看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一 
个队列,而逻辑线程又没有数据可处理的情况,特别是当数据量很少时可能会很容易出现
[这个可以通过设置超时来处理, 如果当前时间-向队列放入第一个包的时间> 50 ms, 就将其放回到容器中换另一个队列]。 

 

方案3:  Netty或muduo的方式,每个客户端维护自己的发送接收队列  ------------------我添加的


通常我们逻辑服务器会以场景来划分线程,不同线程执行不同场景.一个线程可以执行多个场景.因为玩家属于场景,我们会把玩家数据,包括其缓冲池丢给场景 去处理.

 

Ref link:  http://groups.google.com/group/dev4server/browse_thread/thread/4655f8ab1248347a?hl=zh-CN

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

这一段在做网关的设计,有一个相信初学者容易疑惑的地方就是多工作线程下是否存在数据包乱序问题.查阅了一下文档资料.有这么一句话:

The CreateIoCompletionPort function associates an I/O completion port

with one or more file handles. When an asynchronous I/O operation

started on a file handle associated with a completion port is

completed, an I/O completion packet is queued to the port.

CreateIoCompletionPort函数会使一个I/O完成端口与一个或多个文件句柄发生关联。当与一个完成端口相关的文件句柄上启动的异步

I/O操作完成时,一个I/O完成包就会进入到该完成端口的队列中。

比如说接收数据,当我们未曾调用WSARecv()这样的接收数据之前,不会发生完成操作.这样就保证了我们数据在接收完成前的序列化,这样我们可以为

每一个连接定义相应的收发缓冲区保证序列的严格执行,在进行接收操作前对该缓冲区进行一些,比如说移动缓冲区指针,统计流量等操作.最后一步才进行

read操作.

对于IOCP我们会定义足够多的工作线程去处理IO完成事件,这就会涉及执行逻辑与工作线程之间的资源竞争.逻辑线程要对数据包进行逻辑处理.如何更好

的如何减少锁竞争次数,我参考了一下网上的一些方法.下面这些引自网络:

 

"对于如何减少锁竞争次数的优化方案,Ghost Cheng提出了一种。提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网

络IO线程要给逻辑线程投递消息时,会从队列容器中取一个空队列来使用,直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容

器中取一个有消息的队列来读取,处理完后清空队列再放回到容器中。

  这样便使得只有在对队列容器进行操作时才需要加锁,而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁,所以锁竞争的机会大大减少了。

  这里为每个队列设了个最大消息数,看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一

个队列,而逻辑线程又没有数据可处理的情况,特别是当数据量很少时可能会很容易出现。Ghost Cheng在他的描述中没有讲到如何解决这种问题,但

我们可以先来看看另一个方案。

  这个方案与上一个方案基本类似,只是不再提供队列容器,因为在这个方案中只使用了两个队列,arthur在他的一封邮件中描述了这个方案的实现及部

分代码。两个队列,一个给逻辑线程读,一个给IO线程用来写,当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。所以,这种方案下加锁的次数

会比较多一些,IO线程每次写队列时都要加锁,逻辑线程在调换队列时也需要加锁,但逻辑线程在读队列时是不需要加锁的。"

 

思考了一下,对于IOCP我们应该尽量避免的是阻塞发生在工作线程,当我们所有工作线程对同一资源操作的时候会带来更多工作线程的等待.我提出下面一种

方案,是基于1个原则,就是CPU的处理速度远远大于网络IO速度.这样我倾向于为每一个客户端的连接建立一个自己的队列,暂时定义为.128个数据包

上限.工作线程处理的时候仅仅会锁住该连接的接收队列(发送下面会讲到),逻辑处理线程会对每个连接的接收队列轮循.每个连接队列每次最大处理12个数

据包.

当缓冲区将要溢出或队列满的时候我会认为该客户端连接非法

 

对于发送数据,会在IOCP上屏蔽发送完成包.使其不响应发送完成事件.

 

不知各位大侠有没有认同的. :)

 

  

 

 

Kouga  

09/7/19

 

 

嘛~那种队列和咱马蜂窝似的水桶异曲同工啊~喵~

 

既然用了IOCP,就不要阻塞!那样效率会高很多的~

 

 

 

2009/7/18 liam <liam...@hotmail.com>

- 显示引用文字 -

 

 

 

--

签名是什么东西??

  

 

 

寒冰千醉   

09/7/19

 

 

不阻塞当然是最理想的, 我见到过一种设计是: 一个IOCP端口,一个工作者线程方式,就不用加锁,但这只是工作线程之间。

但接收数据时,把IOCP的工作线程与应用层线程分开(比如查数据库等等),

IOCP工作线程相当于生产者,向队列里放接收到的数据包。 有一个专门的检查线程,向队列里取数据。

这里的队例是多个线程在共享访问必须加锁。

  

 

 

Kouga  

09/7/19

 

 

恩,被访问的队列必须要上锁,但是可以使用几个队列循环使用,接收线程和工作线程轮流去锁队列即可。

 

简单的就比如swap下的双队列,由工作线程进行安全的翻转即可。

 

2009/7/19 张晓衡 <zxh1...@gmail.com>

- 显示引用文字 -

 

 

 

--

签名是什么东西??

  

 

 

寒冰千醉   

09/7/19

 

 

我有一个问题,就是从队列从取消息时,我只使用了一个线程。 有没有必要使用多个线程呢? 有时候上层业务很复杂,怕一个线程忙不过来。

如果是多个线程,怎么处理消息顺序问题呢?因为有些线程会执行的快些,有些会慢些,并不是先从队列中取出消息的线程先完成任务。

我当时为了完成任务没有去细想,不知道大家有什么好方法,使用多个线程来取数据,又要保证处理消息顺序不乱。

  

 

 

z_kris 

09/7/20

 

 

第二种方法实际上就是双缓冲,操作系统底层也有使用,实际上如果实现得不错的话,是一种比较好比较成熟的方案了。

缓冲区的大小要根据数据量的大小进行调整的,如果缓冲区很小,就能更及时的处理数据,但吞吐量以及出现资源竞争的几率大多了。另外,双缓冲的实现也有不

同策略的,一是读操作优先,就是生产者只要发现空闲缓冲,马上swap,二是你说的,写线程只有在当前的缓冲区写满了,才进行swap操作。两种策略的

优劣需要自己仔细权衡。

- 显示引用文字 -

  

 

 

Nicolas Tian   

09/7/20

 

 

对于双队列调换(双缓冲)方案,可不可以这样理解:如果认为网络IO的速度大于逻辑处理的速度,在调换时机上应该读操作优先,即当逻辑线程读完自己的队

列后就会将自己的队列与IO线程的队列相调换;如果认为逻辑处理的速度大于网络IO的速度,在调换机制上应该是写操作优先,即当IO线程将自己的队列写

满了后才会将自己的队列与逻辑线程的队列相调换。简单来说,就是“谁慢谁是大爷”。

另外,楼主想讨论的其实是自己的方案的可行性吧:

“对于IOCP我们应该尽量避免的是阻塞发生在工作线程,当我们所有工作线程对同一资源操作的时候会带来更多工作线程的等待。我提出下面一种方案,是基

于一个原则,就是CPU的处理速度远远大于网络IO速度。这样我倾向于为每一个客户端的连接建立一个自己的队列(暂时定义为128个数据包上限)工作线

程处理的时候仅仅会锁住该连接的接收队列(发送下面会讲到),逻辑处理线程会对每个连接的接收队列轮循。每个连接队列每次最大处理12个数据包。当缓冲

区将要溢出或队列满的时候我会认为该客户端连接非法。对于发送数据,会在IOCP上屏蔽发送完成包,使其不响应发送完成事件。”

 

没有经验,不敢乱放,顶出来等高手解答。

 

- 显示引用文字 -

  

 

 

关中刀客   

09/7/20

 

 

上层逻辑按照帧率来处理,每一帧的时候将双层缓冲队列调换一下,取一个队列来处理即可。一般不会出现逻辑慢过io吧,可以给缓冲队列设置最大上限,超过

上限的数量之后,将包丢弃不插入队列。

- 显示引用文字 -

> > > 不知各位大侠有没有认同的. :)- 隐藏被引用文字 -

>

- 显示引用的文字 -

  

 

 

liam   

09/7/20

 

 

呵呵,自己想的方案.其实对于上面每一种都能成为一个成功案例,只不过是承载人数多少问题.想找到一个最优解.我期望能支持到8K以上的并发连接量.

前面做的服务器是没有网关.在尝试.

 

On 720日, 下午439分, Nicolas Tian <pillgr...@vip.sina.com> wrote:

- 显示引用文字 -

> > > 不知各位大侠有没有认同的. :)- 隐藏被引用文字 -

>

- 显示引用的文字 -

  

 

 

liam   

09/7/20

 

 

对于MMorpg类型来讲,我们针对每一个客户端数据池单线程执行.对于所有连接来讲由多个线程处理.这里不用考虑真正意义上的数据包顺序,因为网络也

是未定的

通常我们逻辑服务器会以场景来划分线程,不同线程执行不同场景.一个线程可以执行多个场景.因为玩家属于场景,我们会把玩家数据,包括其缓冲池丢给场景

处理.

- 显示引用文字 -

  

 

 

avalon 

09/7/21

 

 

其实异步io模型就是普通的windows编程模型,用一个thread来getmessage ,然后dispatch出去,至于handler,可以用多线程,也可以一个线程,甚至多进程,这取决于逻辑需要了。

 

  

 

 

Michael    

09/8/11

 

 

我的想法和Nicolas相反,是"谁快谁是大爷"。如果逻辑处理速度大于网络IO速度,当逻辑线程处理完自己的队列后同IO的队列调换;如果网络IO

速度大于逻辑处理速度,当IO队列满之后同逻辑线程队列调换。

===========================================================

http://www.cnblogs.com/my_life/articles/5340365.html

http://yaocoder.blog.51cto.com/2668309/1374280 很好的博客,可按部就班的学习(已变成下面的网址.)

https://blog.51cto.com/yaocoder
https://blog.csdn.net/u012730075/article/details/39481271

===============================

http://www.10tiao.com/html/335/201411/201788569/1.html

http://justdo2008.iteye.com/blog/1936795  

http://www.cnblogs.com/tianzhiliang/archive/2010/10/28/1863684.html   Socket服务器整体架构概述

 

https://my.oschina.net/u/1859679/blog/1438724   王者荣耀架构

 

https://it.zuocheng.net/tier-architecture-summary-zh

http://www.cnblogs.com/meibenjin/p/3604389.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值