??xml version="1.0" encoding="utf-8" standalone="yes"?>
在网l应用服务器? Z性能和防止阻? l常?x)把逻辑处理和I/O处理分离:
I/O|络U程处理I/O事g: 数据包的接收和发? q接的徏立和l护{?
逻辑U程要对收到的数据包q行逻辑处理.
通常|络U程和逻辑U程之间是通过数据包队列来交换信息, 单来说就是一个生产?消费者模?
q个队列是多个线E在׃n讉K必须加锁, 意味着每次讉K都要加锁。如何更好的如何减少锁竞争次数呢 ?
Ҏ(gu)一 双缓冲消息队?
两个队列Q一个给逻辑U程读,一个给IOU程用来写,当逻辑U程d队列后会(x)自q队列与IOU程的队列相调换?br>IOU程每次写队列时都要加锁Q逻辑U程在调换队列时也需要加锁,但逻辑U程在读队列时是不需要加锁的.
队列~冲区的大小要根据数据量的大进行调整的Q如果缓冲区很小Q就能更及时的处理数据,但吞吐量以及出现资源竞争的几率大多了?/p>
可以l缓冲队列设|最大上限,过上限的数量之后,包丢弃不插入队列?br>另外Q双~冲的实C有不同策略的Q?/p>
一是读操作优先Q就是生产者只要发现空闲缓Ԍ马上swapQ?br>二是写线E只有在当前的缓冲区写满了,才进行swap操作?br>三是上层逻辑按照帧率来处理,每一帧的时候将双层~冲队列调换一下,取一个队列来处理卛_
Ҏ(gu)?提供一个队列容?
提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网lIOU程要给逻辑U程投递消息时Q会(x)从队列容器中取一个空队列来用,直到该队列填满后再攑֛容器中换另一个空队列。而逻辑U程取消息时是从队列容器中取一个有消息的队列来dQ处理完后清I队列再攑֛到容器中?/p>
q样便得只有在寚w列容器进行操作时才需要加锁,而IOU程和逻辑U程在操作自己当前用的队列旉不需要加锁,所以锁竞争的机?x)大大减了?/p>
q里为每个队列设了个最大消息数Q看来好像是打算只有当IOU程写满队列时才?x)将其放回到容器中换另一个队列。那q样有时也会(x)出现IOU程未写满一个队列,而逻辑U程又没有数据可处理的情况,特别是当数据量很时可能?x)很?gu)出现[q个可以通过讄时来处? 如果当前旉-向队列放入第一个包的时?> 50 ms, 将其放回到容器中换另一个队列]?/p>
通常我们逻辑服务器会(x)以场景来划分U程,不同U程执行不同场景.一个线E可以执行多个场?因ؓ(f)玩家属于场景,我们?x)把玩家数?包括其缓冲池丢给场景 d?
最先提出的是基本的NATQ它的生基于如下事实:(x)一个私有网l(域)中的节点中只有很的节点需要与外网q接Q呵呵,q是在上世纪90q代中期提出的)。那么这个子|中其实只有数的节炚w要全球唯一的IP地址Q其他的节点的IP地址应该是可以重用的?br> 因此Q基本的NAT实现的功能很单,在子|内使用一个保留的IP子网D,q些IP对外是不可见的。子|内只有数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节炚w要访问外部网l,那么基本NATp责将q个节点的子|内IP转化Z个全球唯一的IP然后发送出厅R?基本的NAT?x)改变I(y)P包中的原IP地址Q但是不?x)改变I(y)P包中的端?
关于基本的NAT可以参看RFC 1631
另外一UNAT叫做NAPTQ从名称上我们也可以看得出,NAPT不但?x)改变经q这个NAT讑֤的IP数据报的IP地址Q还?x)改变I(y)P数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵Q我没有见到q)QNAPT才是我们真正讨论的主角。看下图Q?br> Server S1
18.181.0.31:1235
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 155.99.25.11:62000 v |
|
NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 10.0.0.1:1234 v |
|
Client A
10.0.0.1:1234
有一个私有网l?0.*.*.*QClient
A是其中的一台计机Q这个网l的|关Q一个NAT讑֤Q的外网IP?55.99.25.11(应该q有一个内|的IP地址Q比?0.0.0.10)。如果Client
A中的某个q程Q这个进E创Z一个UDP
Socket,q个Socketl定1234端口Q想讉K外网L18.181.0.31?235端口Q那么当数据包通过NAT时会(x)发生什么事情呢Q?br> 首先NAT?x)改变这个数据包的原IP地址Q改?55.99.25.11。接着NAT?x)?f)q个传输创徏一个SessionQSession是一个抽象的概念Q如果是TCPQ也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的W一个UDP开始,l束呢,呵呵Q也许是几分钟,也许是几时Q这要看具体的实CQƈ且给q个Session分配一个端口,比如62000Q然后改变这个数据包的源端口?2000。所以本来是Q?0.0.0.1:1234->18.181.0.31:1235Q的数据包到了互联网上变ZQ?55.99.25.11:62000->18.181.0.31:1235Q?br> 一旦NAT创徏了一个Session后,NAT?x)记?2000端口对应的是10.0.0.1?234端口Q以后从18.181.0.31发送到62000端口的数据会(x)被NAT自动的{发到10.0.0.1上。(注意Q这里是?8.181.0.31发送到62000端口的数据会(x)被{发,其他的IP发送到q个端口的数据将被NAT抛弃Q这样Client
A׃Server S1建立以了一个连接?/p>
呵呵Q上面的基础知识可能很多人都知道了,那么下面是关键的部分了?br> 看看下面的情况:(x)
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子Q如果Client A的原来那个Socket(l定?234端口的那个UDP Socket)又接着向另外一个Server
S2发送了一个UDP包,那么q个UDP包在通过NAT时会(x)怎么样呢Q?br> q时可能?x)有两种情况发生Q一U是NAT再次创徏一个SessionQƈ且再ơؓ(f)q个Session分配一个端口号Q比如:(x)62001Q。另外一U是NAT再次创徏一个SessionQ但是不?x)新分配一个端口号Q而是用原来分配的端口?2000。前一UNAT叫做Symmetric
NATQ后一U叫做Cone
NAT。我们期望我们的NAT是第二种Q呵呵,如果你的NAT刚好是第一U,那么很可能会(x)有很多P2P软gq。(可以庆幸的是Q现在绝大多数的NAT属于后者,即Cone
NATQ?/p>
好了Q我们看刎ͼ通过NAT,子网内的计算机向外连l是很容易的QNAT相当于透明的,子网内的和外|的计算Z用知道NAT的情况)?br> 但是如果外部的计机惌问子|内的计机比较困难了Q而这正是P2P所需要的Q?br> 那么我们如果想从外部发送一个数据报l内|的计算机有什么办法呢Q首先,我们必须在内|的NAT上打上一?#8220;z?#8221;Q也是前面我们说的在NAT上徏立一个SessionQ,q个z不能由外部来打Q只能由内网内的L来打。而且q个z是有方向的Q比如从内部某台LQ比如:(x)192.168.0.10Q向外部的某个IP(比如Q?19.237.60.1)发送一个UDP包,那么在q个内网的NAT讑֤上打了一个方向ؓ(f)219.237.60.1?#8220;z?#8221;Q(q就是称为UDP
Hole
Punching的技术)以后219.237.60.1可以通过q个z与内网?92.168.0.10联系了。(但是其他的IP不能利用q个z)?/p>
呵呵Q现在该轮到我们的正题P2P了。有了上面的理论Q实C个内|的L通讯差最后一步了Q那是鸡生蛋还是蛋生鸡的问题了Q两辚w无法d发出q接hQ谁也不知道谁的公网地址Q那我们如何来打q个z呢Q我们需要一个中间h来联p这两个内网L?br> 现在我们来看看一个P2P软g的流E,以下图ؓ(f)例:(x)
Server S Q?19.237.60.1Q?br> |
|
+----------------------+----------------------+
| |
NAT A (外网IP:202.187.45.3) NAT B (外网IP:187.34.1.56)
| (内网IP:192.168.0.1) | (内网IP:192.168.0.1)
| |
Client A (192.168.0.20:4000) Client B (192.168.0.10:40000)
首先QClient Ad服务器,NAT Aơ的Session分配了一个端?0000Q那么Server S收到的Client
A的地址?02.187.45.3:60000Q这是Client A的外|地址了。同PClient BdServer SQNAT
Bl此ơSession分配的端口是40000Q那么Server S收到的B的地址?87.34.1.56:40000?br> 此时QClient A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client
BQ那么他可以从Server S那儿获得B的公|地址187.34.1.56:40000Q是不是Client
A向这个地址发送信息Client Bp收到了呢Q答案是不行Q因为如果这样发送信息,NAT
B?x)将q个信息丢弃Q因L(fng)信息是不误来的Qؓ(f)了安全,大多数NAT都会(x)执行丢弃动作Q。现在我们需要的是在NAT
B上打一个方向ؓ(f)202.187.45.3Q即Client A的外|地址Q的z,那么Client
A发送到187.34.1.56:40000的信?Client Bp收到了。这个打z命令由谁来发呢Q呵呵,当然是Server S?br> ȝ一下这个过E:(x)如果Client A惛_Client B发送信息,那么Client A发送命令给Server SQ请求Server
S命oClient B向Client
A方向打洞。呵呵,是不是很l口Q不q没关系Q想一惛_很清楚了Q何况还有源代码呢(侯老师说过Q在源代码面前没有秘?
8Q)Q然后Client A可以通过Client B的外|地址与Client B通信了?/p>
注意Q以上过E只适合于Cone NAT的情况,如果是Symmetric NATQ那么当Client B向Client
A打洞的端口已l重新分配了QClient B无法知道这个端口(如果Symmetric
NAT的端口是序分配的,那么我们或许可以猜测q个端口P可是׃可能Dp|的因素太多,我们不推荐这U猜端口的Ҏ(gu)Q?/p>
另一文章接上:(x)
下面解释一下上面的文章中没有提及或者说我觉得比较欠~的地方.
U有地址/端口和公有地址/端口:我们知道,现在大部分网l采用的都是NAPT(Network Address/Port Translator)?q个东东的作用是一个对外的对话在经qNAT之后IP地址和端口号都会(x)被改?在这里把一ơ会(x)话中客户自己认ؓ(f)在用的IP地址和端口号成ؓ(f)U有地址/端口,而把l过NAPT之后被改写的IP地址和端口号UCؓ(f)公有地址/端口.或者可以这么理?U有地址/端口是你安人对你的늧而公有地址/端口则是你真正对外公开的名?如何获得用户的私用地址/端口?q个很简单了,而要得到公有地址/端口号就要在q接上另一台机器之后由那台机器看到的IP地址和端口号来表C?
如果明白了上面的东西,下面q入我们的代?在这里解释一下关键部分的实现:
客户端首先得到自qU有地址/l端,然后向server端发送登陆请?server端在得到q个h之后可以知道这个client端的公有地址/l端,server?x)?f)每一个登陆的client保存它们的私有地址/端口和公有地址/端口.
OK,下面开始关键的打洞程.假设client A要向client B对话,但是A不知道B的地址,即知道Ҏ(gu)NAT的原理这个对话在W一ơ会(x)被拒l?因ؓ(f)client B的NAT认ؓ(f)q是一个从没有q的外部发来的请?q个时?A如果发现自己没有保存B的地址,或者说发送给B的会(x)话请求失败了,它会(x)要求server端让B向A打一个洞,q个B->A的会(x)话意义在于它使NAT B认ؓ(f)A的地址/端口是可以通过的地址/端口,q样A再向B(ti)发送对话的时候就不会(x)再被NAT B拒绝?打一个比Ҏ(gu)说明打洞的过E?AxB家做?但是遭到了B的管家NAT B的拒l?理由?我从来没有听我家B提过你的名字,q时A扑ֈ了A,B都认识的朋友server,要求serverlB报一个信,让B去跟家说A是我的朋?于是,B跟管家NAT B?A是我认识的朋?q样A的访问请求就不会(x)再被家NAT B所拒绝?而言?UDP打洞是一个通过server保存下来的地址使得彼此之间能够直接通信的过E?server只管帮助建立q接,在徏立间接之后就不再介入?
下面是一个模拟P2P聊天的过E的源代码,q程很简单,P2PServerq行在一个拥有公|IP的计机上,P2PClientq行在两个不同的NAT后(注意Q如果两个客L(fng)q行在一个NAT后,本程序很可能不能q行正常Q这取决于你的NAT是否支持loopback
translationQ详?a >http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txtQ当Ӟ此问题可以通过双方先尝试连接对方的内网IP来解冻I但是q个代码只是Z验证原理Qƈ没有处理q些问题Q,后登录的计算机可以获得先d计算机的用户名,后登录的计算机通过send
username message的格式来发送消息。如果发送成功,说明你已取得了直接与Ҏ(gu)q接的成功?br> E序现在支持三个命oQsend , getu , exit
send格式Qsend username message
功能Q发送信息给username
getu格式Qgetu
功能Q获得当前服务器用户列表
exit格式Qexit
功能Q注销与服务器的连接(服务器不?x)自动监客h否吊U)
代码很短Q相信很Ҏ(gu)懂,如果有什么问题,可以l我发邮?a href="mailto:zhouhuis22@sina.com">zhouhuis22@sina.com
或者在CSDN上发送短消息。同ӞƢ迎转发此文Q但希望保留作者版?-Q?br> _05/04052509317298.rar"
http://www.ppcn.net/upload/2004_05/04052509317298.rar
另一介l打z技术的Q补充)
UDP打洞技术依赖于由公共防火墙和cone NATQ允?dng)R当的有计划的端对端应用E序通过NAT"打洞"Q即使当双方的主机都处于NAT之后。这U技术在 RFC3027?.1节[NAT PROT] 中进行了重点介绍Qƈ且在Internet[KEGEL]中进行了非正式的描叙Q还应用C最新的一些协议,例如[TEREDO,ICE]协议中。不q,我们要注意的是,"?如其名,UDP打洞技术的可靠性全都要依赖于UDP?br>q里考虑两种典型场景Q来介绍q接的双方应用程序如何按照计划的q行通信的,W一U场景,我们假设两个客户端都处于不同的NAT之后Q第二种场景Q我们假设两个客L(fng)都处于同一个NAT之后Q但是它们彼此都不知?他们在同一个NAT??br>
处于不同NAT之后的客L(fng)通信
我们假设 Client A ?Client B 都拥有自qU有IP地址Qƈ且都处在不同的NAT之后Q端对端的程序运行于 CLIENT A,CLIENT B,S之间Qƈ且它们都开放了UDP端口1234?CLIENT A和CLIENT B首先分别与S建立通信?x)话Q这时NAT A把它自己的UDP端口62000分配lCLIENT A与S的会(x)话,NAT B也把自己的UDP端口31000分配lCLIENT B与S的会(x)话?br>
假如q个时?CLIENT A 想与 CLIENT B建立一条UDP通信直连Q如?CLIENT A只是单的发送一个UDP信息到CLIENT B的公|地址138.76.29.7:31000的话QNAT B?x)不加考虑的将q个信息丢弃Q除非NAT B是一?full cone NATQ,因ؓ(f) q个UDP信息中所包含的地址信息Q与CLIENT B和服务器S建立q接时存储在NAT B中的服务器S的地址信息不符。同L(fng)QCLIENT B如果做同L(fng)事情Q发送的UDP信息也会(x)?NAT A 丢弃?br>
假如 CLIENT A 开始发送一?UDP 信息?CLIENT B 的公|地址上,与此同时Q他又通过S中{发送了一个邀请信息给CLIENT BQ请求CLIENT B也给CLIENT A发送一个UDP信息?CLIENT A的公|地址上。这时CLIENT A向CLIENT B的公|IP(138.76.29.7:31000)发送的信息D NAT A 打开一个处?CLIENT A的私有地址和CLIENT B的公|地址之间的新的通信?x)话Q与此同ӞNAT B 也打开了一个处于CLIENT B的私有地址和CLIENT A的公|地址(155.99.25.11:62000)之间的新的通信?x)话。一旦这个新的UDP?x)话各自向对?gu)开了,CLIENT A和CLIENT B之间可以直接通信Q而无需S来牵U搭桥了?q就是所谓的打洞技?Q?/p>