??xml version="1.0" encoding="utf-8" standalone="yes"?>日本免费久久久久久久网站,精品久久综合1区2区3区激情,国产亚洲成人久久http://www.shnenglu.com/jack-wang/category/10924.htmlzh-cnTue, 28 Mar 2023 17:59:32 GMTTue, 28 Mar 2023 17:59:32 GMT60非boost版本的asio无法识别asio::placeholders::error和boost::system::error_codehttp://www.shnenglu.com/jack-wang/archive/2023/03/28/229784.htmlTue, 28 Mar 2023 03:01:00 GMThttp://www.shnenglu.com/jack-wang/archive/2023/03/28/229784.htmlhttp://www.shnenglu.com/jack-wang/comments/229784.htmlhttp://www.shnenglu.com/jack-wang/archive/2023/03/28/229784.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/229784.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/229784.html改用
std::placeholders::_1

boost::system::error_code
改用
std::error_code

O?img src ="http://www.shnenglu.com/jack-wang/aggbug/229784.html" width = "1" height = "1" />

2023-03-28 11:01 发表评论
]]>
libevent 无法解析的外部符?__imp__WSASendhttp://www.shnenglu.com/jack-wang/archive/2016/05/04/213417.htmlWed, 04 May 2016 08:27:00 GMThttp://www.shnenglu.com/jack-wang/archive/2016/05/04/213417.htmlhttp://www.shnenglu.com/jack-wang/comments/213417.htmlhttp://www.shnenglu.com/jack-wang/archive/2016/05/04/213417.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/213417.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/213417.html

2016-05-04 16:27 发表评论
]]>
UDT协议-ZUDP的可靠数据传输协?http://www.shnenglu.com/jack-wang/archive/2010/04/13/112470.htmlTue, 13 Apr 2010 09:14:00 GMThttp://www.shnenglu.com/jack-wang/archive/2010/04/13/112470.htmlhttp://www.shnenglu.com/jack-wang/comments/112470.htmlhttp://www.shnenglu.com/jack-wang/archive/2010/04/13/112470.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/112470.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/112470.html转:http://blog.csdn.net/windcsn/archive/2006/01/04/570242.aspx

1.   介绍
随着|络带宽时g产品(BDP)的增加,通常的TCP协议开始变的低效。这是因为它的AIMDQadditive increase multiplicative decreaseQ算法彻底减了TCP拥塞H口Q但不能快速的恢复可用带宽。理Z的流量分析表明TCP在BDP增加到很高的时候比较容易受包损失攻凅R?/p>

另外Q承自TCP拥塞控制的不公^的RTT也成为在分布式数据密集程序中的严重问题。拥有不同RTT的ƈ发TCP将不公q_分n带宽。尽在的BDP|络中用通常的TCP实现来相对^{的׃n带宽Q但在拥有大量BDP的网l中Q通常的基于TCP的程序就必须承受严重的不公^的问题。这个RTTZ的算法严重的限制了其在广域网分布式计的效率Q例如:internet上的|格计算?/p>

一直到今天Q对标准的TCP的提高一直都不能在高BDP环境中效率和公^性方面达到满意的E度Q特别是ZRTT的问题)。例如:TCP的修改,RFC1423Q高性能扩展Q,RFC2018QSACKQ、RFC2582QNew RenoQ、RFC2883QD-SACKQ、和RFC2988QRTO计算Q都或多或少的提高了Ҏ率,但最Ҏ的AIMD法没有解决。HS TCPQRFC 3649Q通过Ҏ上改变TCP拥塞控制法来在高BDP|络中获得高带宽利用率,但公qx问题仍然存在?/p>

考虑C面的背景Q需要一U在高BDP|络支持高性能数据传输的传输协议。我们推荐一个应用程序别的传输协议Q叫UDT或基于UDP的数据传输协议ƈ拥有用塞控制法?/p>

本文描述两个正交的部分,UDP协议和UDT拥塞控制法。一个应用层U别的协议,位于UDP之上Q用其他的拥塞法Q然而这些本文中描述的算法也可以在其他协议中实现Q例如:TCP?/p>

一个协议的参考实现叫[UDT]Q详l的拥塞控制法的性能分析在[GHG04]中可以找到?/p>

2.   设计目标
UDT主要用在数量的bulk源共享富裕带宽的情况下,最典型的例子就是徏立在光纤q域|上的网D,一些研I所在这L|络上运行他们的分布式的数据密集E序Q例如,q程讉K仪器、分布式数据挖掘和高分L率的多媒体流?/p>

UDT的主要目标是效率、公q뀁稳定。单个的或少量的UDT应该利用所有高速连接提供的可用带宽Q即使带宽变化的很剧烈。同Ӟ所有ƈ发的必dq_׃n带宽Q不依赖于不同的带宽瓶劲、v始时间、RTT。稳定性要求包发送速率应该一直会聚可用带宽非常快Qƈ且必避免拥塞碰撞?/p>

UDTq不是在瓶劲带宽相对较小的和大量多元短文件流的情况下用来取代TCP的?/p>

UDT主要作ؓTCP的朋友,和TCPq存QUDT分配的带宽不应该过ҎMAX-MIN规则的最大最公q_享原则。(备注Q最大最规则允许UDT在高BDPq接下分配TCP不能使用的可用带宽)。我?/p>

3.   协议说明
3.1. 概述
UDT是双工的Q每个UDT实体有两个部分:发送和接收。发送者根据流量控制和速率控制来发送(和重传)应用E序数据。接收者接收数据包和控制包QƈҎ接收到的包发送控制包。发送和接收E序׃n同一个UDP端口来发送和接收?/p>

接收者也负责触发和处理所有的控制事gQ包括拥塞控制和可靠性控制和他们的相ҎӞ例如RTT估计、带宽估计、应{和重传?/p>

UDTL试着应用层数据打包成固定的大小Q除非数据不够这么大。和TCP怼的是Q这个固定的包大叫做MSSQ最大包大小Q。由于期望UDT用来传输大块数据,我们假定只有很小的一部分不规则的大小的包在UDT session中。MSS可以通过应用E序来安装,MTU是其最优|包括所有包_?/p>

UDT拥塞控制法速率控制和窗口(量控制Q合qv来,前者调整包的发送周期,后者限制最大的位被应答的包。在速率控制中用的参数通过带宽估计技术来更新Q它l承来自Z接收的包Ҏ。同Ӟ速率控制周期是估计RTT的常量,控制参C赖于Ҏ的数据到N度Q另外接收端释放的缓冲区的大?/p>


 


 


3.2. 包结?br>UDT有两U包Q数据包和控制包。他们通过包头的第一位来区分Q标志位Q。如果是0Q表C是数据包,1表示是控制包?/p>

3.2.1.    数据?/p>

 


 

 

 


 


数据包结构如下显C:

0                      1                  3                       4

0  1  2 3 4  5  6  7 8  9 0  1  2  3  4 5  6 7  8  9  0 1  2  3  4 5  6 7  8  9 0  1

0
 包序?br> 
应用数据
 

包序hUDT数据包头中唯一的内宏V它是一个无W号整数Q用标志位后的31位,UDT使用包基的需要,例如Q每个非重传的包都增加序?。序号在到达最大?^31-1的时候覆盖。紧跟在q些数据后面的是应用E序数据?/p>

3.2.2.    控制?br>控制包结构如下:

0                      1                  3                       4

0  1  2 3 4  5  6  7 8  9 0  1  2  3  4 5  6 7  8  9  0 1  2  3  4 5  6 7  8  9 0  1

1
 cd
 保留
 ACK序号
 
控制信息字段
 

?U类型的控制包在UDT中,bit1-3表示q些信息。前32位在包头中必d在。控制信息字D包?Q例如,它不存在Q或者多?2位无W号整数Q这由包cd军_?/p>

UDT使用应答子序LҎ。每个ACK/ACK2包有一个无W号?6位序P它独立于数据包需要。它使用?6-31。应{需要从0刎ͼ2^16-1Q。位16-31在其他控制包中没有定义?/p>

cd
 说明
 控制信息
 
000
 协议q接握手
 1Q?2?UDT版本

2Q?2?内部序?/p>

3Q?2?MSSQ字节)

4Q?2?最大流量窗口大(字节Q?/p>


 


 

 
001
 保活
 没有
 
010
 应答Q位16-31是应{序?br> 1Q?2位包序号Q先前接收到的包序号

2Q?2位,RTTQ微U)

3Q?2位,RTT 变量或者RTTVar (微秒)

4Q?2位,量H口大小Q包的数量)

5Q?2位,q接定w估计Q每U包的数量)
 
011
 Negative应答QNAKQ?br> 丢失信息?2位整数数l,?.9?br> 
100
 保留
 q种cd的控制信息保留作为拥塞警告用,从接收到发送端。一个拥塞警告能被ECN或包延迟增加势的度量方法触发?br> 
101
 关闭
 
 


 

 
110
 应答一个应{(ACK2Q?br> 16-31位,应答序号?br> 
111
 4-15的解?br> 保留来使用
 

注意Q对于数据和控制包来_可以从UDP协议头中得到实际的包大小。包大小信息能被用来得到有效的数据负载和NAK包中的控制信息字D大?/p>

3.3. 定时?br>UDT在接收端使用4个定时器来触发不同的周期事gQ包括速率控制、应{、丢失报告(negative应答Q和重传/q接l护?/p>

UDT中的定时器用系l时间作为源。UDT接收端主动查询系l时间来查一个定时器是否q期。对于某个定时器T来说Q其拥有周期TPQ将定变量t用来记录最qT被设|或复位的时间。如果T在系l时间t0Qt= t0Q被复位Q那么Q何t1Qt1-t>=TPQ是Tq期的条件?/p>

四个定时器是QRC定时器、ACK定时器、NAK定时器、EXP定时器。他们的周期分别是:RCTP、ATP、NTP、ETP?/p>

RC定时器用来触发周期性的速率控制。ACK定时器用来触发周期性的有选择的应{(应答包)。RCTP和ATP是常量|gؓQRCTP=ATP=0.01U?/p>

NAK被用来触发negative应答QNAK包)。重传定时器被用来触发一个数据包的重传和l护q接状态。他们周期依赖于对于RTT的估计。ETPg依赖于连lEXP旉溢出的次数。推荐的RTT初始值是0.1U,而NTP和ETP的初始值是QNTP=3*RTTQETP=3*RTT+ATP?/p>

在每ơbounded UDP接收操作Q如果收C个UDP包,一些额外的必须的数据处理时_时查询系l时间来查四个定时器是否已经q期。推荐的周期_度是微U。UDP接收旉溢出值是实现的一个选择Q这依赖于@环查询的负担和事件周期精度之间的权衡?/p>

速率控制事g更新包发送周期,UDT发送端使用STP来安排数据包的发送。假定一个在旉t0被发送,那么下一ơ包发送时间是Qt0+ STPQ。换句话_如果前面的包发送花费了t’旉Q发送端等待(STP-t’Q来发送下一个数据包Q如果STP-t’ <0Q就不需要等待了Q。这个等待间隔需要一个高_度的实现Q推荐用CPU旉周期_度?/p>

3.4. 发送端法
3.4.1.    数据l构和变?br>AQ?SND PKT历史H口Q一个@环数l记录每个数据包的开始时?/p>

BQ?发送端丢失链表Q发送段丢失列表是一个连接链表,用来存储被接收方NAK包中q回的丢失包序号。这些数字以增加的顺序存储?/p>

3.4.2.    数据发送算?br>AQ?如果发送端的丢失链表是非空的,重传W一个在list中的包,q删除该成员Q到5?/p>

BQ?{待有应用程序数据需要发?/p>

CQ?如果未应{的包数量超q了两量H口的大,转到1。如果不是包装一个新的包q发送它?/p>

DQ如果当前包的序h16nQn是一个整敎ͼ转第2步?/p>

EQ?在SND PKT历史H口中记录包的发送时?/p>

FQ?如果q是自上ơ发送速率降低之后的第一个包Q等外SYN旉?/p>

GQ等外(STP – tQ时_t是第1到第4步之间的L_然后转到1?/p>

3.5. 接收端算?br>3.5.1.    数据l构和变?br>AQ?接收端丢失链表:是一个dupleq接链表Q元素的值包括:丢失数据包的序号、最q丢失包的反馈时间和包已l被反馈的次数。g包序号增序的方式存储?/p>

BQ?应答历史H口Q每个发送ACK的和旉一个@环数l;׃其@环的Ҏ,意味着如果数组中没有更多空间的时候新的值将覆盖老的倹{?/p>

CQ?RCV PKT历史H口Q一个用来记录每个包到达旉的@环数l?/p>

DQ对包窗口:一个用来记录每个探包对之间的旉间隔?/p>

EQ?LRSNQ一个用来记录最大接收数据包需要的变量。LRSN被初始化为初始序号减1?/p>

3.5.2.    数据接收法
AQ?查询pȝ旉来检查RC、ACK、NAK、或EXP定时器是否过期。如果Q一定时器过期,处理事gQ本节下面介l)q复位过期的定时器?/p>

BQ?启动一个时间bounded UDP接收。如果每个包刎ͼ??/p>

CQ?讄exp-count?Qƈ更新ETP为:ETP=RTT+4*RTTVar + ATP?/p>

DQ如果所有的发送数据包已经被应{,复位EXP旉变量?/p>

EQ?查包头的标志位。如果是一个控制包Q根据类型处理它Q然后{1?/p>

FQ?如果当前数据包的需要是16n+1Qn是一个整敎ͼ记录当前包和上个在对包窗口中数据包的旉间隔?/p>

GQ在PKT历史H口中记录包到达旉

HQ?如果当前数据包的序号大于LRSN+1Q将所有在Q但不包括)q两个g间的序号攑օ接收丢失链表Qƈ在一个NAK包中这些序号发送给发送端。如果序号小于LRSNQ从接收丢失链表中删除它?/p>

IQ?nbsp;  更新LRSNQ{1?/p>

3.5.3.    RC定时器到
通过速率控制法来更新STPQ见3.6节)?/p>

q程如下Q?/p>

AQ?按照下面的原则查找接收端所接收到的所有包之前的序P如果接收者丢失链表是I的QACKL是LRSN+1Q否则是在接收丢失队列中的最序受?/p>

BQ?如果应答号不大于曄被ACK2应答的最大应{号Q或{于上次应答的应{号q且两次应答之间的时间间隔小于RTT+4*RTTVarQ停止(不发送应{)?/p>

CQ?分配q个应答一个唯一增加的ACK序列P推荐采用ACK序列h步骤1增加Qƈ且重叠在辑ֈ最大g后?/p>

DQ根据下面的法来计包的抵N度Q用PKT历史H口中的D最q?6个包抵达间隔QAIQ中倹{在q?6个gQ删除那些大于AI*8或小于AI*8的包Q如果最后剩?个|计算他们的^均?AI’)Q包抵达速度?/AI’Q每U包的数量)Q否则是0?/p>

EQ?Ҏ3.7节中的内容ؓ每端QWQ计流量窗口。然后计有效的量H口大小为:最大(WQ可用接收方~冲大小Q,2Q?/p>

FQ?Ҏ下面的算法来计算q接定w估计。如果流量控制快启动阶段Q?.7Q一直l,q回0Q否则计最q?6个对包间隔(PIQ,q些值在对包H口中,那么q接定w是1/PIQ每U包的数量)?/p>

GQ打包应{序列号Q应{号QRTTQRTT 变量Q有效的量H口大小q估计连接,他们放入ACK包中Q然后发送出厅R?/p>

HQ?记录ACK序列P应答号和q个应答的开始时_q放入历史窗口中?/p>

3.5.4.    处理NAK定时器到?br>Ø       查找接受方的丢失链表Q找到所有上ơ反馈时间是Qk*QRTT+4*RTTVar ) Q前的包Qk当前q个包的反馈ơ数?Q如果没有反馈丢失,停止?/p>

Ø       压羃W一步中得到的序P?.9Q,然后在一个NAK包中发送他们到发送方?/p>

Ø       如果不是停止量控制快启动阶Dc?/p>

3.5.5.    处理EXP定时?br>AQ?nbsp;  如果发送端的丢失链表不是空的,停止

BQ?nbsp;  所有未应答的包攑ֈ发送端的丢失链表中

CQ?如果(exp-count>16)q且自上ơ从Ҏ接收C个包以来的L间超q?U,或者这个时间已l超q?分钟了,q被认ؓ是连接已l断开Q关闭UDTq接?/p>

DQ如果没有数据,也就没有应答Q发送一个保zdl对端,否则所有未应答包的序号攑օ发送丢失列表中?/p>

EQ?nbsp;  更新exp-count为:exp-count= exp-count+1

FQ?nbsp;  更新ETP为:ETP=exp-count*QRTT+4*RTTVarQ?ATP?/p>

3.5.6.    收到应答?br>AQ?nbsp;  更新最大的应答序号

BQ?更新RTT和RTTVar为:RTT = rttQ?RTTVar = rvQrtt和rv是ACK包中的RTT和RTTVar倹{?/p>

CQ?nbsp;  更新NTP和ETP为:NTP=RTT+4*RTTVarQETP=exp-count*QRTT+4*RTTVarQ?ATP?/p>

DQ?nbsp; 更新q接定w估计QB=QB*7+bQ?8Qb是ACK包带的倹{?/p>

EQ?nbsp;  更新量H口大小为ACK中的倹{?/p>

FQ?nbsp;  发送ACK2包,q设|与ACK序号相同的应{号到对?/p>

GQ?nbsp; 复位EXP定时?/p>

3.5.7.    当收到NAK包的时?br>AQ?所有NAK包中带的序号攑օ发送方的丢失列表中

BQ?通过速率控制来更新STPQ见3.6Q?/p>

CQ?复位EXP定时?/p>

3.5.8.    当收到ACK2?br>Ø         在ACK历史H口中根据接收到的ACK2序列h找行营的ACK包?/p>

Ø         更新曄被应{的最大应{号

Ø       ҎACK2的到达时间和ACKd旉计算新的rtt|q且更新RTT和RTTVargؓQ?/p>

RTTVar = (RTTVar *3 +abs(rtt-RTT)/4

RTT = (RTT *7+rtt)/8

RTT和RTTVar的初始值是0.1U和0.05U?/p>

Ø       更新NTP和ETP为:

NTP = RTTQ?/p>

ETP = (exp-count +1)* RTT+ATP

3.5.9.    当收Czd的时?br>什么也不做

3.5.10.             当收到连接握手和关闭包的时?br>?.8?/p>

3.6. 速度控制法
3.6.1.    速率控制快启?br>STP被初始ؓ最的旉_ֺQ?个CPU周期?毫秒Q。这是在快启动阶D,一般收C个ACK包其携带的估计带宽大?q个阶段停止了。包的发送周期被讄?/WQW是ACK携带的流量窗口的大小?/p>

快启动阶D仅仅在开始一个UDTq接的时候发生,且不会在UDTq接的以后再出现。在快启动阶D之后,下面的算法就要工作了?/p>

3.6.2.    当RC定时器时间到
1Q?nbsp; 如果在上一个RCTP旉内,没有收到一个ACKQ停?/p>

2Q?nbsp; 计算在上个RCTP旉内的丢失率,计算Ҏ是根据d发送的包与NAK反馈中d丢失包的数量。如果丢q大于0.1%Q停止?/p>

3Q?nbsp; 下个RCTP旉内发送包的增加数量如下计:(inc)

If (B<=C) inc = 1/MSS

Else inc = max (10^(ceil(log10((B-C)*MSS*8)))*Beta/MSS,1/MSS)

B是连接容量估计,C是当前的发送速度。两个都计算为每U多个包。MSS是以字节计算的;Beta是gؓ0.0000015的常量?/p>

4Q?nbsp; 更新STPQSTP=QSTP*RCTPQ?QSTP*inc + RCTPQ?/p>

5Q?nbsp; 计算真正的数据发送周期(rspQ,从SND PKT历史H口中得刎ͼ如果QSTP<0.5 *rspQ设|STP为(0.5 * rspQ?/p>

6Q?nbsp; 如果QSTP<1.0Q,讄STP?.0?/p>

3.6.3.    当收到NAK包时
3.6.3.1.          数据l构和变?br>1Q?nbsp; LSDQ自上次速率降低后发送的最大序?/p>

2Q?nbsp; NumNAKQ自上次LSD更新以后的NAK数量

3Q?nbsp; AvgNAKQ当最大序号大于LSD时两ơ事件之间的NAKUd的^均数?/p>

4Q?nbsp; DRQ在1到AvgNAK之间的随机^均数?/p>

3.6.3.2.          法
1Q?nbsp; 如果NAK中最大的丢失序列号大于LSDQ?/p>

增加STP为:STP=STP*Q?+1/8Q?/p>

更新AvgNAK为:AvgNAK = QAvgNAK *7 +NumNAKQ?8

更新DR

复位 NumNAK = 0

记录LSD

2Q?nbsp; 否则Q增加NumNAK按照1个步骤增加;如果NumNAK % DR = 0Q增加STP为:STP=STP*Q?+1/8Q;记录LSD?/p>

3.7. 量控制法
量控制H口大小QWQ初始值是16?/p>

3.7.1.    当ACK定时器到的时?br>1Q?nbsp; 量控制快启动:如果没有NAK产生或者W没有到达或超q?5个包Qƈ且AS>0Q流量窗口大更Cؓ应答包的L量?/p>

2Q?nbsp; 否则Q如果(AS>0Q,W更新为:QAS是包的到N度Q?/p>

W= ceil (W *0.875+AS* (RTT +ATP) *0.125)

3Q?nbsp; 限制W到对Ҏ大流量窗口大?/p>

3.8. q接建立和关?br>一个UDT实体首先作ؓ一个SERVER启动Q当一个客L需要连接的时候其发送握手包。客L在从服务端接收到一个握手响应包或时间溢Z前,应该每隔一D|间发送一个握手包Q时间间隔由响应旉和系loverhead来权衡)?/p>

握手包有如下信息Q?/p>

1Q?nbsp; UDT版本Q这个值是兼容的目的。当前的版本?

2Q?nbsp; 初始序号Q这是发送这个UDT实体来用于发送数据包的v始序受它必须是一个在1刎ͼ2^31-1Q之间的随机倹{另外,q个值在合理的时间历史窗口中不应该重复?/p>

3Q?nbsp; MSSQ数据包的大(通过IP有效负蝲来度量)

4Q?nbsp; 最大的量H口大小Q这是接收到握手信息的UDT实体允许的最大流量窗口大,H口大小通常限制为接收端的数据结构大?/p>

服务器接收到一个握手包之后Q比较MSS值和他自q值ƈ讄它自qgؓ较小的倹{结果g在握手响应中被发送到客户端,另外q有服务器的版本信息Q初始序列号Q最大流量窗口大?/p>

版本字段用来查两端的兼容性。初始序列号和最大流量窗口大用于初始化接收到这个握手包的UDT实体参数?/p>

服务器在W一步完成以后就准备发送或接收数据。然而,只要从同一个客L接收M握手包,其应该发送响应包?/p>

客户端一旦得到服务器的一个握手响应其p入发送和接收数据状态。设|它自己的MSS为握手响应包中的值ƈ初始化相应的参数为包中的|序列受最大流量窗口)。如果收CQ何其他的握手信息Q丢掉它?/p>

如果其中的UDT实体要关闭,它将发送一个关闭信息到对端Q对Ҏ到这个信息以后将自己关闭。这个关闭信息通过UDP传输Q仅仅发送一ơ,q不保证一定收到。如果消息没有收刎ͼҎ根据时间溢出机制来关闭q接?/p>

3.9. 丢失信息的压~方?br>NAK包中携带的丢׃息是一?2-bit整数的数l。如果数l的中数字是一个正常的序号Q第1位是0Q,q意味着q个序号的包丢失了,如果W?位是1Q意味着从这个号码开始(包括该号码)C一个数l中的元素(包括q个元素|之间的包Q它的第1位必L0Q都丢失?/p>

例如Q下面的NAK中携带的信息Q?/p>

0x00000002, 0x80000006, 0x0000000B, 0x0000000E

上面的信息表明序号ؓQ?Q?Q?Q?Q?Q?0Q?1Q?4的包都丢了?/p>

4.   效率和公qx?br>UDT能够充分利用当前有线|络的独立于q接定w的可用带?、RTT、后台共存流、给定的q接比特错误率。UDT在没有数据包丢失的情况下?bits/s?0%带宽需要一个常量时_q个旉?.5U。UDTq不适合无线|络?/p>

UDT的确满单瓶劲网l拓扑的最?最公qx。在多个瓶劲情况下,Ҏ最大最原则它能保证较瓶劲连接或者至一半的q等׃n(it guarantees that flows over smaller bottleneck links obtain at least half of their fair share according to max-min rule)。RTT对公qx都一点媄响?/p>

当和大块的TCP共存的时候,TCP能占用比UDT更多的带宽,除了三种情况Q?/p>

1Q?nbsp;     |络BDP非常大,TCP不能利用他们的公q_享带宽。这U情况下QUDT占用TCP不能利用的带宽?/p>

2Q?nbsp;     q接定w是如此的,从而导致UDT的带宽估计技术不能最有的工作Q模拟显C个极限连接容量大U是100kb/s?/p>

3Q?nbsp;     在用FIFO队列作ؓ|络路径的网l中Q如果队列大大于BDPQTCP的共享带宽随着队列大小的增加而降低。然而,抵达UDT的共享带宽是Q队列大通常过实际路由?交换机提供的数量?/p>

当短QtimewiseQ类似web的TCP和的q发UDT共存的时候,UDT在TCP上的效果非常小?/p>

更多的分析在[GHG03]?/p>

5.   安全考虑
UDTq没有用特定的安全机制Q相反,它依赖于应用E序提供的授权和底层提供的安全机制?/p>

然而,׃UDP是无q接的,UDT实现应该查所有达到的包是否是预期的来源。这是从socket的APIq接概念中承而来Q其q接只是接收指定来源的数据?/p>


6.UDT SOURCE CODE LINK
      http://sourceforge.net/projects/dataspace


本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/windcsn/archive/2006/01/04/570242.aspx



2010-04-13 17:14 发表评论
]]>
(TCP-over-UDP library)ZUDP协议之上实现通用、可靠、高效的TCP协议http://www.shnenglu.com/jack-wang/archive/2010/04/12/112342.htmlMon, 12 Apr 2010 08:35:00 GMThttp://www.shnenglu.com/jack-wang/archive/2010/04/12/112342.htmlhttp://www.shnenglu.com/jack-wang/comments/112342.htmlhttp://www.shnenglu.com/jack-wang/archive/2010/04/12/112342.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/112342.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/112342.html转:http://www.shnenglu.com/fwxjj/archive/2009/03/17/76923.html

随着互联|应用广泛推q,出现了越来越多的|络应用Q其中基于p2p思想的各U网l技术的产品也越来越多的出现在我们的视野当中。从最早闻名的Napster到现在的Bittorrent、eMule、skype{品,P2Pq种|络应用模式已经从各个方面深入h心。这些品在各自的网l实现技术上Q都以各自的Ҏ解决着同样面的一个问题,如何让他们的软g产品在各异的|络拓扑l构中顺利的q行P2P通信?br> 众所周知Q在当今的网l拓扑结构中Q普遍存在用NAT讑֤来进行网l地址转换Q而让应用E序能跨这些NAT讑֤q行全双工的通信Q就成ؓ非常重要的一个问题。对于实现跨NAT通信可以采取很多U办法(对于能够直接q接、反向连接的情况不在此列Q:首先是通过服务器进行{发,q是比较_暴的方法,而且在用户量大的时候,转发服务器需要付出相当大的代PW二Q可以用NATIK技术。而大家知道关于NATIK中QUDPIK的成功率比起TCPIK要高出许多Q这一点这里将不做多述Q可以参考Bryan Ford的文章《Peer-to-Peer Communication Across Network Address Translators》(http://www.brynosaurus.com/pub/net/p2pnat/Q。因此在UDP协议上构Z些大型的|络应用E序可能会成为很多h的需求?br> 当然也可能基于更多的原因Q会有很多h希望能在UDP协议上进行大型应用程序的构徏。然而UDP协议本n存在着不通信不可靠的~点Q于是对于基于UDPq行可靠通信的需求就现出来了。目前在|络上有许多人正做着q一工作QUDT、RakNet、eNet{都是构建在UDP之后|络可靠通信开发库。然后这些库开发时都针对了一些特D应用来q行设计的,不具备通用性。比如RakNet是ؓ游戏应用而设计,对于实时性等游戏相关的网l需求有很好的支持,对于大批量数据传输却有点力所不及。而UDTZ一U基于带宽速率控制的拥塞控制算法进行设计(http://udt.sourceforge.net/doc/draft-gg-udt-01.txtQ,主要用在数量的bulk源共享富裕带宽的情况下,最典型的例子就是徏立在光纤q域|上的网D,而在ISP提供带宽有限的情况下q行却显得消耗资源ƈ性能不。甚臛_能被防火墙,或ISP服务商判断ؓ恶意带宽使用d。这些都使用得他们不能被q泛地用于各U网l应用程序。另外大家也陆箋发现目前的UDT实现版本存在的一些问题。比如UDT做服务端接收q接ӞL新开一个端口与客户端进行连接,q样会带来几个问题:1Q较多客Lq接上来Ӟ服务端新打开的众多端口中可能有的端口会被防火墙拦截而导致通信p|Q?Q如果客L处于Symmetric NAT和Port-Restricted Cone NAT后面Ӟ导致服务器端与客户端连接无法成功徏立,3Q由于udp端口数最大值有限,所以UDT服务器端可接收的q接C因些受限。再有就是不仅仅是UDT库,基本上所有的UDP-based可靠通信库,都未提供I越proxy代理的功能(socks5Q;再有是对UDP打洞技术有的支持得不完善或q不支持?br> Zq些原因Q得我需要开发一个基于UDP协议之上实现一个可靠、高效、通用的通信库,来满x目前所开发的目的需要。TCP协议法已经是经q多斚w及多q的验证Q是最具通用性,且可靠高效的。虽然UDT{各U库指出TCP在这h那样的网l环境下存在不Q但众多实现当中他仍然是最通用、可靠、高效的。相信有许多我一P需要这么一个开发库Q所以我打算在开发过E中Q陆l公开相关的文档及q个开发库?br> 
二、设计目?br> 
 TDP主要的目标就是在UDP层之上实现TCP的协议算法,使得应用E序能够在UDP层之上获得通用、可靠、高效的通信能力?br> TDP|络开发库所实现的算法,都来自久l考验的TCP协议法Q网上有着非常多的参考资料。在实现当中Q参考最多的是Richard Stevens的《TCP/IP详解》?br> TDP提供的用于开发的应用E序接口与Socket API非常相像Q姑且称之ؓTDP Socket APIQ基本上的函数名与参数等都与Socket API怸_但是TDP Socket API的API接口都位于命名空间TDP当中。只要用过Socket APIq行开发过的朋友,都会用TDP库进行开发。下图ؓTDP及TDP Socket API所处在的协议栈应用中的位置Q以及与TCP协议栈应用的Ҏ?br> 
 
三、协议说?br> 
 1Q协议格?br> 
 TDP的实现的法虽然与TCP实现的算法是大致相同?但TDP的协议格式只是从TCP协议格式获得参考,但ƈ不完全与他相同。TDP的协议格式如下:
 
 
 
 接下来介l一下协议格式的各个字段含义?br> 4位首部长度:表示用户数据在数据包中的起始位置?br> LIVQ连接保zL志,用于表示TDPq接通\存活状态?br> ACKQ确认序h效?br> PSHQ接收方应该快这个报文段交给应用层?br> RSTQ重接?br> SYNQ同步序L来发起一个连接?br> FINQ发端完成发送Q务?br> 16位窗口大:接收端可接收数据的窗口大?br> 选项Q只有一个选项字段Qؓ最长报文大,即MSS。TDP选项格式与TCP选项格式一_kind=0时表C选项l束Qkind=1时表C无操作Qkind=2时表C最大报文段长度。如下图Q?br> 
 
 
 数据Q用户通过TDP传输的数据?br> 
 2QTDPq接建立与终?br> 
 TDP的连接徏立与l止可以参考TCP的状态变q图(此图的详l解释请参考《TCP/IP详解 卷一》第18?Q如下:
 
 
 2.1q接建立
 
 2.1.1三次握手
 q接建立分要l过三次握手q程Q?)客户端发送一个SYND到指明客户打算q接的服务器的端口,报文D中要设|客L初始序号?Q服务器发回包含服务器的初始序号的SYN报文D作为应{。同Ӟ确认序可|ؓ客户的初始序号加1,q设|ACK位标志报文段为确认报文段?Q客L必须确认序可|ؓ服务器初始序号加1,Ҏ务器的SYN报文D进行确认?br> TDP在全局l护一个初始序L子,q个初始序号为随时生的32位整数?br> q接建立的超时和重传初始gؓ3U,时采用指数退避算法,3U超时后时gؓ6U,然后?2U,24U?#8230;…。连接徏立最长时间限制ؓ75U?br> 
 2.1.2 NAT UDP PUNCH模式
 当TDP工作模式是NAT UDP PUNCHӞ在三ơ握手之前,向对端NAT端口及预端口间隔默?ms发送默认ؓ10个LIV报文D,一来用于打开自已的NAT端口Q二来是用于q入对端NAT端口。默认值可以由用户E序讄。这时的LIV报文D中初始序号及确认序号都??br> 当接收到对端LIV报文D后Q立卛_止LIV报文D发送,发出SYN报文D进行连接徏立。这时有两种可能Q其一是另一端直到接收到该SYN报文D之前,都没有接收到LIV报文D,或是刚接收到但没有来得及发送SYN报文D,此时会如上文描q的正常模式下连接徏立的q程一_经历三ơ握手。基二是另一端在接收到该SYN报文D之前,也已l发送出SYN报文D,此时双方都需要对SYN报文D进行确认,可以UC为四ơ握手?

 2.1.3 最大传输报文大(MSSQ?br> TCP报文D在q接建立旉要通报MSSQ在TDP的实C也进行通报Q默认通报?460字节Q符合以太网标准Q这个默认值允?0字节的IP首部?字节的UDP首部?2字节的TDP首部Q以适合 1500字节的IP数据报)默认值可以由用户E序讄?br> TCP在对端地址为非本地IPӞ默认通报?36字节。TDP之所以默认通报?460Q是因ؓTDP在数据传输过E中Q实C路径MTU发现技术,通过实际发现的MTUQ进行MSS的动态调_以尽量避免报文段在网l中的传输生分片的情况。\径MTU发现技术在传输数据一节中q行描述?br> 
 2.1.4 半打开q接及连接保z?br> 半打开q接是指对端异常关闭Q如|线拔掉、突然断늭情况引发一端导演关闭,而另一端的q接却仍然认接处于打开当中Q这U情늧之ؓ半打开q接。TDP中的一个TDP SOCKET描述W由本地IP、本地端口、远端IP、远端端口唯一定。当q端客户端连接请求到来时Q服务端接收到一个新的TDP SOCKET描述W,当这一个描q符唯一定信息已经存在ӞҎ的连接请求发送RST报文D,通知光|连接请求。对于旧的连接,׃zL制自动发现是否ؓ半打开q接Q如果是半打开q接Q则自动关闭该连接。这里RST报文D与TCP中的RST报文D|些不一PTCP的RST报文D工作描q请参考《TCP/IP详解 卷一》?br> q接建立之后QTDPq接需要启动保zL制。TCPq接在没有数据通信的情况下也能保持q接Q但TDPq接不行。TDPq接在一定时间段内如果没有数据交互的话,主动发送保zLIV报文Dc这个时间段ҎTDPq接工作模块不同有所差异Q在NAT UDP PUNCH模式下,q个旉D默认gؓ1分钟Q大多数的NAT中,UDP会话时旉?Q?分钟左右Q;而在常规模块下这个时间段默认gؓ5分钟。默认值可以由用户E序讄Q用L序需要指明两U模块下的保zL间周期。这里TDP的保zL制与TCP中的保活机制完全不一PTCP的保zL制描q请参考《TCP/IP详解 卷一》?br> 
 2.2q接关闭
 
 TDPq接与TCPq接一h全双工的Q因此每个方向必d独地q行关闭。客hl服务器一个FIN报文D,然后服务器返回给客户端一个确认ACK报文Qƈ且发送一个FIN报文D,当客h回复ACK报文后(四次握手Q,q接q束了?br> TDPq接的一端接收到FIN报文D|Q如果还有数据要发送,需要l将数据q行发送完成,然后才发出FIN报文D;如果q有数据未从~存中取出,取出数据,q进行确认,直到所有确认完成之后,然后才发出FIN报文D(此时如果有ؕ序的报文D|况不q行处理Q。上面的描述也表现出QTDP是支持半关闭的,当一端发出FIN报文D|Q仍然允许接收另一端数据。但是半关闭可能Dq接永远停留在状态图中FIN_WAIT_2状态中Q此时保zL制仍然在工作当中Q如果对端已l关闭,那么保活机制在到时立卛_闭这一q接?br> 
 下图是一个典型的q接建立与连接关闭的C意图,此图摘自《TCP/IP详解 卷一》?br> 
 
 
四、TDP传输数据?br> 
 1Q传输的报文D?br> 
 在TDP工作q程中传输的所有报文段Q只有SYN报文DcFIN报文Dc数据报文段是可靠的之外Q其它报文段如ACK报文DcLIV报文DcRST报文D늭都不是可靠的。SYN报文D与FIN报文D传输中都占用一个序P数据报文D在传输中根据传输的数据字节数占用相应的序号Q其它报文段不占用传输序受?br> 成功接收数据报文D,应当按序对下一个期望的数据报文D늚序号作ؓ认序号发送ACK报文D进行确认。当出现接收Cؕ序的数据报文D|Q将乱序数据报文D|序缓存,q发送期望报文段的ACK报文D进行确认。ACK报文D늚发送ƈ非即时的Q也q是对应接收数据报q行一对一认发送。ACK报文D는200ms定时触发发送,也就是说ACK报文D要l受最?00ms的时延进行发送。ACK报文D对此时期望的数据序可行确认,因此q不是与接收数据报相对应。ACK报文D|不可靠的Q当丢失时对端将无法了解接收情况Q因此发送方会有一个超时机Ӟ如果发现认的ACK报文D超Ӟ发送方重发该数据报,q一点在W五节进行详l描q?br> 
 2Q\径MTU发现及MSS通告
 
 前面已经提到要在q接建立q程中会通告初始MSSQ这个值可以由用户E序q行讄。但q个初始值是一个静态的。当通信的两个端点之间跨多个网l时Q用设|的MSSq行报文D发送时Q可能导致传输的IP报文分片情况的生。ؓ了避免分片情늚产生QTDP在数据传输过E中q行动态的路径MTU发现Qƈq行MSS的更新及通告?br> TDP创徏UDP SOCKETӞ卛_描述W设|IP选项Z允许q行分片Qsetsockopt (clientSock, IPPROTO_IP, IP_DONTFRAGMENT,(char*)&dwFlags, sizeof(dwFlags))Q。在发送数据时以当前MSS大小D行数据发送,如果q回gؓ错误码WSAEMSGSIZEQ?0040Q表CZؓ报文D尽寸大于MTUQ需要进行IP分片传输。此Ӟ~减MSS大小再次q行报文D发送,直至不再q回错误码WSAEMSGSIZEQ?0040Q。当MSS变更q能成功发送报文段后,需要向对端通报新的MSS倹{每ơMSS~小后,默认?0U,TDP默认扩大MSS大小Q以查是否\径MTU增大了(默认值可以由用户E序讄Q,之后?0*2U?0*2*2U进行检,如果三次都未发现MTU增大则停止进行检。见RFC1191描述Q网l中MTU值的个数是有限的Q如下图描述Q摘自RFC1191Q。因此MSS的扩大及~减Q可依据一些由q似值按序构成的表,依照此表索引q行MSS值的扩大与羃减计?br> 
 TDP中MSS与MTU之间关系的计公式如下:
 MSS = MTU – 20(IP首部) – 8(UDP首部) – 12(TDP首部)?br> 
 3QNagle法
 
 有些认ؓl受时g的捎带ACK发送是Nagle法Q其实不是。经受时延的捎带ACK发送是TCP的通常实现Q在TDP中也是如此。而Nagle法是要求一个TCPQTDP也是如此Q连接上最多只能有一个未被确认的未完成的报文D,在该报文D늚认到达之前不能发送其他的报文Dc相反,TCPQTDP也是如此Q在q个时候收集这些报文段Q关在确认到来时合ƈ作ؓ一个报文段发送出厅RNagle法对于处理应用E序产生大量报文段的情况,有利于避免网l中׃发送太多的包而过载(q便是发送端的糊涂窗口综合症Q关于糊涂窗口综合症在下文将做更详细描述Q?br> Nagle法适用于生大量小报文D늚情况Q但有时我们需要关闭Nagle法。一个典型的例子是XH口pȝ服务器:消息(鼠标UdQ必L时g地发送,以便行某U操作的交互用户提供实时的反馈?br> 默认的TDP实现中Nagle法是关闭的Q用L序可以设|打开它?br> 
 4Q窗口大通告与滑动窗?br> 
 双方接收模块需要依据各自的~冲区大,怺通告q能接受Ҏ数据的尺寸。双方发送模块则必须ҎҎ通告的接收窗口大,q行数据发送。这U机制称之谓滑动H口Q它是TDP接收方的量控制Ҏ。它允许发送方在停止ƈ{待认前可以连l发送多个分l(依据滑动H口的大)Q由于发送方不必每发一个分l就停下来等待确认,因此可以加速数据的传输?br> 参照《TCP/IP详解 卷一 20.3滑动H口》一节,滑动H口在排序数据流上不时的向右UdQ窗口两个边沿的相对q动增加或减了H口的大,关于H口Ҏ的运动有三个术语Q窗口合拢(当左Ҏ向右Ҏ靠近Q、窗口张开Q当双沿向右移动)、窗口收~(当右Ҏ向左UdQ。RFC文强烈不要在实现当中出现窗口收~的情况出现Q在我们的实C也将不会出现?br> 当遇到快的发送方与慢的接收方的情冉|Q接收方的窗口会很快被发送方的数据填满,此时接收方将通告H口大小?,发送方则停止发送数据。直到接收方用户E序取走数据后更新窗口大,发送方可以l箋发送数据;另外Q因为ACK报文D|可能丢失Q发送方可能没有成功接收到更新的H口大小Q因此发送方启动一个坚持定时器Q当坚持定时器超Ӟ发送方发送一个字节的数据到接收方Q尝试检查窗口大的更新?br> 在Nagle法中接到过p涂H口l合症,在这里要q一步进行描q。糊涂窗口综合症是指众多量数据的报文段通过q接q行交换Q而不是满长度的报文段Q这导致连接占用过多带宽,降低传输速率。糊涂窗口综合症产生是分两端的,接收方可以通告一个小的窗口(而不是一直等到有大的H口时才通告Q,发送方也可以发送少量的数据Q而不是等待其他的数据以便发送一个大的报文段Q。要以采用如下方法避免这一现象Q?br> 1Q接收方不通告窗口。通常的算法是接收方不通告一个比当前H口大的H口Q可以ؓ0Q,除非H口可以增加一个报文段大小(也就是将要接收的MSS)或者可以增加缓存空间的一半,不论实际有多?br> 2Q发送方避免出现p涂H口l合症的措施是只有以下条件之一满时才发送数据:(a)可以发送一个满长度的报文段Q?b)可以发送至是接收斚w告H口大小一半的报文D;(c)可以发送Q何数据ƈ且不希望接收ACKQ也是_我们没有q未被确认的数据Q或者该q接上不能用Nagle法?br> 
 5QPUSH标志
 
 PSUH标志的作用是发送方使用PUSH标志通知接收方将所收到的数据全部提交给接收q程。在TDP实现中,用户E序q不需要关心PUSH标志。因为TDP实现从不接收到的数据推q交付给用户E序Q因此这个标志在TDP的实C是被忽略的?br> 
五、TDP时与重?br> 
 1Q带宽时延乘U与拥塞
 
 每个|络通道都有一定的定wQ可以计通道的容量大:
 Capacity(bit) = bandwidth(b/s) * round-trip time(s)
 q个g般称之ؓ带宽时g乘积。这个g赖于|络速度和两端的RTTQ可以有很大的变动。不论是带宽q是时g均会影响发送方与接收方之间通\的容量?br> 当数据到达一个大的网l通道q向一个小的网l通道发送,发生拥塞现象。另外当多个输入到达一个\由器Q而\由器的输出流于q些输入的d时也会发生拥塞。TDP时与重传机制刚采用TCP的拥塞控制算法来q行发送端的流量控制?br> 
 2Q往q时间与重传时旉量
 
 时与重传中最重要的部分就是对一个给定连接的往q时_RTTQ的量。由于\由器和网l流量均会发生变化,因此一般认为RTT可能l常会发生变化,TDP应该跟踪q些变化q相应地改变相应的超时时间?br> 首先是必L量在发送一个带有特别序L字节和接收到包含字节的确认之间的RTT。由于数据报文段与ACK之间通常没有一一对应的关p,如下图(摘自《TCP/IP详解 卷一》图20.1Q中Q这意味着发送方可以量到的一个RTTQ是在发送报文段4和接收报文段7之间的时_用M表示所量到的RTT?br> Ҏ[Jacobson 1988]描述Q见《TCP/IP详解 卷一》参考文献)Q用A表示被^滑的RTTQ均g计器Q,用D表示被^滑的均值偏差,用Err表示刚得到的量l果M与当前RTT估计器之差,则可以计下一个超旉传时_用RTO表示下一个超旉传时_?br> A = 0 Q未q行量往q时间之前,A的初始|
 D = 3 Q未q行量往q时间之前,D的初始|
 RTO = A + 2D = 6 Q未q行量往q时间之前,RTO的初始|
 A = M + 0.5 (W一ơ测量到往q时间结果,对RTT估计器计初始?
 D = A / 2 Q第一ơ测量到往q时间结果,对均值偏差D计算初始|
 RTO = A + 4D Q第一ơ测量到往q时间结果,对均值偏差RTO计算初始|
 之后的计方法如下:
 Err = M – A
 A <- A + gErr
 D <- D + h(|Err| - D)
 RTO = A + 4D
 其中g是常量增量,取gؓ1/8(0.125)Qh也是帔R增量Q取gؓ1/4(0.25)?br> 
 
 
 Karn法QKarn法是解x谓的重传多义性问题的。[Karn and Partridge 1987]规定Q见《TCP/IP详解 卷一》参考文献)Q当一个超时和重传发生Ӟ在重传数据的认最后到达之前,不能更新RTT估计器,因ؓ我们q不知道ACK对应哪次传输Q也许第一ơ传输被延迟而ƈ没有被丢弃,也有可能是第一ơ传输的ACK被gq丢弃)。ƈ且,׃数据被重传,RTO已经得到了一个指数退避,我们在下一ơ传输时使用q个退避后的RTO。对一个没有被重传的报文段而言Q除非收C一个确认,否则不要计算新的RTO?br> 在Q何时候对每个q接q行仅测量一ơRTT|在发送一个报文段Ӟ如果l定q接的定时器已经被用,则该报文D不被计Ӟ反之如果l定q接的定时器未被使用Q则开始计时以量RTT倹{即q每个发出报文D都q行量RTT|同一旉D里只能有一个RTT值测量行行,不会q行q行多个RTT值测量?br> 
 3Q慢启动
 
 如果发送方一开始便向网l发送多个报文段Q直臌到接收方通告H口大小为止。当发送方与接收方在同一局域网Ӟq种方式是可以的。但如果在发送方与接收方之间存在多个路由器和速率较慢的链路时Q就可能出现问题。一些中间\由器必须~存分组Qƈ有可能耗尽存储器的I间Q将来得降低TCPq接的吞吐量。于是需要一U叫“慢启?#8221;的拥塞控制算法?br> 慢启动ؓ发送方增加一个拥塞窗口,CؓcwndQ当与另一个网l的L建立q接Ӟ拥塞H口被初始化?个报文段。每收到一个ACKQ拥塞窗口就增加一个报文段Qcwnd以字节ؓ单位Q但慢启动以报文D大ؓ单位q行增加Q。发送方取拥塞窗口与通告H口中的最g为发送上限。拥塞窗口是发送方使用的流量控Ӟ而通告H口是接收方使用的流量控制?br> 发送方开始时发送一个报文段Q然后等待ACK。当收到该ACKӞ拥塞H口?增加?,卛_以发送两个报文段。当收到q两个报文段的ACKӞ拥塞H口增加ؓ4。这是一U指数增加的关系?br> 
 4Q拥塞避?br> 
 慢启动算法增加拥塞窗口大到某些点上可能辑ֈ了互联网的容量,于是中间路由器开始丢弃分l。这通知发送方它的拥塞H口开得太大。拥塞避免算法是一U处理丢失分l的Ҏ。该法假定׃分组受到损坏引v的丢失是非常的Q远于1Q)Q因此分l丢失就意味着在源L和目标主Z间的某处|络上发生了拥塞。有两种分组丢失的指C:发生时和接收到重复的确认。拥塞避免算法与慢启动算法是两个独立的算法,但实际中q两个算法通常在一起实现?br> 拥塞避免法和慢启动法需要对每个q接l持两个变量Q一个拥塞窗口cwnd和一个慢启动门限ssthresh。算法的工作q程如下Q?br> 1) 对一个给定的q接Q初始化cwnd?个报文段Qssthresh?5535个字节?br> 2) TCP输出例程的输Z能超qcwnd和接收方通告H口的大。拥塞避免是发送方使用的流量控Ӟ而通告H口则是接收方进行的量控制。前者是发送方感受到的|络拥塞的估计,而后者则与接收方在该q接上的可用~存大小有关?br> 3) 当拥塞发生时Q超时或收到重复认Q,ssthresh被设|ؓ当前H口大小的一半(cwnd和接收方通告H口大小的最|但最ؓ2个报文段Q。此外,如果是超时引起了拥塞Q则cwnd被设|ؓ1个报文段Q这是慢启动)?br> 4) 当新的数据被Ҏ认Ӟ增加cwndQ但增加的方法依赖于我们是否正在q行慢启动或拥塞避免。如果cwnd于或等于ssthreshQ则正在q行慢启动,否则正在q行拥塞避免。慢启动一直持l到我们回到当拥塞发生时所处位|的半时候才停止Q因为我们记录了在步?中给我们刉麻烦的H口大小的一半)Q然后{为执行拥塞避免?br> 慢启动算法初始设|cwnd?个报文段Q此后每收到一个确认就?。这会ɽH口按指数方式增长:发?个报文段Q然后是2个,接着??#8230;…。拥塞避免算法要求每ơ收C个确认时cwnd增加1/cwnd。与慢启动的指数增加比v来,q是一U加性增ѝ我们希望在一个往q时间内最多ؓcwnd增加1个报文段Q不在q个RT T中收C多少个ACKQ,然而慢启动根据这个往q时间中所收到的确认的个数增加cwnd?br> 处于拥塞避免状态时Q拥塞窗口的计算公式如下Q引公式参照BSD的实玎ͼsegsize/8的值是一个匹配补充量Q不在算法描q当中)Q?br> cwnd <- cwnd + segsize * segsize / cwnd + segsize / 8
 
 5Q快速重传与快速恢?br> 
 ׃我们不知道一个重复的ACK是由一个丢q报文D引LQ还是由于仅仅出C几个报文D늚重新排序Q因此我们等待少量重复的ACK到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理ƈ产生一个新的ACK之前Q只可能产生1 ~ 2个重复的ACK。如果一q串收到3个或3个以上的重复ACKQ就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段Q而无需{待时定时器溢出。这是快速重传算法。接下来执行的不是慢启动法而是拥塞避免法。这是快速恢复算法?br> q个法通常按如下过E进行实玎ͼ
 1) 当收到第3个重复的ACKӞssthresh讄为当前拥塞窗口cwnd的一半。重传丢q报文Dc设|cwnd为ssthresh加上3倍的报文D大?br> 2) 每次收到另一个重复的ACKӞcwnd增加1个报文段大小q发?个分l(如果新的cwnd允许发送)?br> 3) 当下一个确认新数据的ACK到达Ӟ讄cwnd为ssthreshQ在W?步中讄的|。这个ACK应该是在q行重传后的一个往q时间内Ҏ?中重传的认。另外,q个ACK也应该是对丢q分组和收到的W?个重复的A C K之间的所有中间报文段的确认。这一步采用的是拥塞避免,因ؓ当分l丢失时我们当前的速率减半?br> 
六、代理socks5支持
 
 参照RFC1928、RFC1929Q在TDP实现中,支持匿名通过socks5代理以及用户?密码验证方式通过socks5代理?br> ׃socks5代理是工作于q输层上Q因此连接当中对IP层选项的设|都没有效果。socks5代理起到的作用只是应用数据的转发Q但q已l基本上能支持大部分用户E序的应用需求。在使用socks5代理q行工作中,路径MTU的发现机Ӟ无法有效工作,此时MSS默认?36QMTU默认?76Q?用户E序可以修改使用的MSS倹{?br> 
七、安全考虑
 
 TDP协议及算法方面ƈ不对数据的安全性做M考虑Q用L序在传输数据时如果对安全性有要求Q可以自行在应用数据层做相应的工作。但TDP实现中,会提供一个简单的AES256位加解密ҎQ提供给用户E序使用。用L序可以调用该加解密方法,Ҏ据进行加密然后再通过|络q行发送,接收时将加密数据进行解密再会用户E序数据逻辑处理模块q行处理?br> 
八、定时器
 
 如BSD的TCP实现cMQTDP也ؓ每条q接建立了六个定时器Q简要介l如下:
 1Q?#8220;q接建立”定时器,在发送SYN报文D徏立一条新的连接时启动。如果没有在75U内收到响应Q连接徏立将中止?br> 2Q?#8220;重传”定时器,在发送数据时讑֮。如果定时器已超时而对端的认q未到达Q将重传数据。重传定时器的值是动态计的Q取xRTT与该报文D被重传的次数?br> 3Q?#8220;延迟ACK”定时器,收到必须认但无需马上发出认的数据时讑֮。等?00ms后发送确认响应。如果,在这200ms内,有数据要在该q接上发送,延迟的ACK响应可随数据一起发送回对端Q称为捎带确认?br> 4Q?#8220;坚持”定时器,在连接对端通告接收H口?,Ll箋发送数据时讑֮。坚持定时器在超时后向对端发?字节的数据,判定对端接收H口是否已经打开。坚持定时器的值是动态的计算的,取决于RTT|?U与60U之间取倹{?br> 5Q?#8220;保活”定时器。TDPq接在一定时间段内如果没有数据交互的话,主动发送保zLIV报文Dc即?#8220;保活”定时器超Ӟ说明没有数据交互Q则发送保zL据包。保zd时器默认旉?分钟Q用L序可以进行设|?br> 6QTIME_WAIT定时?也可UCؓ2MSL定时器(实现中,一个MSL?分钟Q。当q接状态{UdTIME_WAITӞ卌接主动关闭时Q定时器启动?br> 
九、开发接?br> 
 使用TDPq行|络E序开发是非常Ҏ的,它的开发接口(APIQ与socket API是非常相似的Q尤其是对应功能的函数名U都是一致的Q需要注意的是TDP的所有API都处于名U空间TDP之下。开发接口见下表Q?br> 
函数 描述 
TDP::accept 接受一个链?nbsp;
TDP::bind l定本地地址C个TDP::SOCKET句柄 
TDP::cleanup 清除TDP全局资源Q一个进E中只需要调用一?nbsp;
TDP::close 关闭已打开的TDP::SOCKET句柄Qƈ关闭q接 
TDP::connect q接到服务器?nbsp;
TDP::getlasterror 获得TDP最后的一个错?nbsp;
TDP::getpeername dq接的对端的地址信息 
TDP::getsockname dq接的本地的地址信息 
TDP::getsockopt dTDP的选项信息 
TDP::listen {待客户端来q接 
TDP::recv 接收数据 
TDP::select {待集合中的TDP SOCKET改变状?nbsp;
TDP::send 发送数?nbsp;
TDP::setsockopt 修改TDP的选项信息 
TDP::shutdown 指定关闭q接上双工通信的部分或全部 
TDP::socket 创徏一个TDP SOCKET 
TDP::startup 初始化TDP全局信息Q一个进E中只需要调用一?nbsp;
 



2010-04-12 16:35 发表评论
]]>
TCP短连接与长连?/title><link>http://www.shnenglu.com/jack-wang/archive/2010/03/24/110419.html</link><dc:creator>王</dc:creator><author>王</author><pubDate>Wed, 24 Mar 2010 03:32:00 GMT</pubDate><guid>http://www.shnenglu.com/jack-wang/archive/2010/03/24/110419.html</guid><wfw:comment>http://www.shnenglu.com/jack-wang/comments/110419.html</wfw:comment><comments>http://www.shnenglu.com/jack-wang/archive/2010/03/24/110419.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/jack-wang/comments/commentRss/110419.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/jack-wang/services/trackbacks/110419.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt">  <br>  </p> <p style="FONT-SIZE: 10pt">刚接触TCP/IP通信设计的hҎ范例可以很快~出一个通信E?<br>序,据此一些h可能会认为TCP/IP~程很简单。其实不Ӟ <br>TCP/IP~程h较ؓ丰富的内宏V其~程的丰富性主要体现在 <br>通信方式和报文格式的多样性上?</p> <p style="FONT-SIZE: 10pt"><br>一。通信方式 </p> <p style="FONT-SIZE: 10pt">主要有以下三大类: </p> <p style="FONT-SIZE: 10pt">(一)SERVER/CLIENT方式 </p> <p style="FONT-SIZE: 10pt">1.一个Client方连接一个Server方,或称点对?peer to peer)Q?<br>2.多个Client方连接一个Server方,q也是通常的ƈ发服务器方式?<br>3.一个Client方连接多个Server方,q种方式很少见,主要 <br>用于一个客户向多个服务器发送请求情c?</p> <p style="FONT-SIZE: 10pt"><br>(?q接方式 </p> <p style="FONT-SIZE: 10pt">1.长连?</p> <p style="FONT-SIZE: 10pt">Client方与Server方先建立通讯q接Q连接徏立后不断开Q?<br>然后再进行报文发送和接收。这U方式下׃通讯q接一?<br>存在Q可以用下面命o查看q接是否建立Q?</p> <p style="FONT-SIZE: 10pt">netstat –f inet|grep 端口??678)?</p> <p style="FONT-SIZE: 10pt">此种方式常用于点对点通讯?</p> <p style="FONT-SIZE: 10pt"><br>2.短连?</p> <p style="FONT-SIZE: 10pt">Client方与Server每进行一ơ报文收发交易时才进行通讯q?<br>接,交易完毕后立x开q接。此U方式常用于一点对多点 <br>通讯Q比如多个Clientq接一个Server. </p> <p style="FONT-SIZE: 10pt"><br>(?发送接收方?</p> <p style="FONT-SIZE: 10pt">1.异步 </p> <p style="FONT-SIZE: 10pt">报文发送和接收是分开的,怺独立的,互不影响。这U方 <br>式又分两U情况: </p> <p style="FONT-SIZE: 10pt">(1)异步双工Q接收和发送在同一个程序中Q有两个不同?<br>子进E分别负责发送和接收 <br>(2)异步单工Q接收和发送是用两个不同的E序来完成?</p> <p style="FONT-SIZE: 10pt">2.同步 </p> <p style="FONT-SIZE: 10pt">报文发送和接收是同步进行,既报文发送后{待接收q回报文?<br>同步方式一般需要考虑时问题Q即报文发上d不能无限{?<br>待,需要设定超时时_过该时间发送方不再{待读返回报 <br>文,直接通知时q回?nbsp; </p> <p style="FONT-SIZE: 10pt"><br>实际通信方式是这三类通信方式的组合。比如一般书上提供的 <br>TCP/IP范例E序大都是同步短q接的SERVER/CLIENTE序。有?<br>l合是基本不用的Q比较常用的有h值的l合是以下几U: </p> <p style="FONT-SIZE: 10pt">同步短连接Server/Client <br>同步长连接Server/Client <br>异步短连接Server/Client <br>异步长连接双工Server/Client <br>异步长连接单工Server/Client </p> <p style="FONT-SIZE: 10pt">其中异步长连接双工是最为复杂的一U通信方式Q有时候经 <br>怼出现在不同银行或不同城市之间的两套系l之间的通信?<br>比如金卡工程。由于这几种通信方式比较固定Q所以可以预 <br>先编制这几种通信方式的模板程序?</p> <p style="FONT-SIZE: 10pt"><br>?报文格式 </p> <p style="FONT-SIZE: 10pt">通信报文格式多样性更多,相应地就必须设计对应的读写报文的?<br>收和发送报文函数?</p> <p style="FONT-SIZE: 10pt">(一)d与非d方式  </p> <p style="FONT-SIZE: 10pt">1.非阻塞方?</p> <p style="FONT-SIZE: 10pt">dC停地q行d作,如果没有报文接收刎ͼ{待一D|间后 <br>时q回Q这U情况一般需要指定超时时间?</p> <p style="FONT-SIZE: 10pt">2.d方式 </p> <p style="FONT-SIZE: 10pt">如果没有报文接收刎ͼ则读函数一直处于等待状态,直到有报文到达?</p> <p style="FONT-SIZE: 10pt"> </p> <p style="FONT-SIZE: 10pt">(?循环d方式 <br>  </p> <p style="FONT-SIZE: 10pt">1.一ơ直接读写报?</p> <p style="FONT-SIZE: 10pt">在一ơ接收或发送报文动作中一ơ性不加分别地全部d或全?<br>发送报文字节?</p> <p style="FONT-SIZE: 10pt">2.不指定长度@环读?</p> <p style="FONT-SIZE: 10pt">q一般发生在短连接进E中Q受|络路由{限Ӟ一ơ较长的?<br>文可能在|络传输q程中被分解成了好几个包。一ơ读取可能不 <br>能全部读完一ơ报文,q就需要@环读报文Q直到读完ؓ止?</p> <p style="FONT-SIZE: 10pt">3.带长度报文头循环d </p> <p style="FONT-SIZE: 10pt">q种情况一般是在长q接q程中,׃在长q接中没有条件能?<br>判断循环d什么时候结束,所以必要加长度报文头。读函数 <br>先是d报文头的长度Q再Ҏq个长度去读报文.实际情况中, <br>报头的码制格式还l常不一P如果是非ASCII码的报文_q必?<br>转换成ASCII,常见的报文头码制有: <br>(1)n个字节的ASCII?<br>(2)n个字节的BCD?<br>(3)n个字节的|络整型?</p> <p style="FONT-SIZE: 10pt"> </p> <p style="FONT-SIZE: 10pt">以上是几U比较典型的d报文方式Q可以与通信方式模板一?<br>预先提供一些典型的APId函数。当然在实际问题中,可能q?<br>必须~写与对Ҏ文格式配套的dAPI. </p> <p style="FONT-SIZE: 10pt">在实际情况中Q往往需要把我们自己的系l与别h的系l进行连接, <br>有了以上模板与API,可以说连接Q何方式的通信E序都不存在问题?</p> <p style="FONT-SIZE: 10pt"><br>本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/wgl_suc102/archive/2008/01/23/2060828.aspx</a></p> <img src ="http://www.shnenglu.com/jack-wang/aggbug/110419.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/jack-wang/" target="_blank">王</a> 2010-03-24 11:32 <a href="http://www.shnenglu.com/jack-wang/archive/2010/03/24/110419.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>拼包函数及网l封包的异常处理http://www.shnenglu.com/jack-wang/archive/2010/02/18/107993.htmlWed, 17 Feb 2010 18:27:00 GMThttp://www.shnenglu.com/jack-wang/archive/2010/02/18/107993.htmlhttp://www.shnenglu.com/jack-wang/comments/107993.htmlhttp://www.shnenglu.com/jack-wang/archive/2010/02/18/107993.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/107993.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/107993.html拼包函数及网l封包的异常处理(含代? 收藏
本文作者:sodme
本文出处Q?a >http://blog.csdn.net/sodme
声明Q本文可以不l作者同意Q意{载、复制、传播,但Q何对本文的引用都请保留作者、出处及本声明信息。谢谢!

  常见的网l服务器Q基本上?*24时q{的,对于|游来说Q至要求服务器要能q箋工作一周以上的旉q保证不出现服务器崩溃这LN性事件。事实上Q要求一个服务器在连l的满负药转下不出M异常Q要求它设计的近乎完,q几乎是不太现实的。服务器本n可以出异常(但要可能少得出Q,但是Q服务器本n应该被设计得以健壮Q?#8220;病灾”打不垮它Q这p求服务器在异常处理方面要下很多功夫?/p>

  服务器的异常处理包括的内定w常广泛,本文仅就在网l封包方面出现的异常作一讨论Q希望能Ҏ从事相关工作的朋友有所帮助?/p>

  关于|络包斚w的异常,M来说Q可以分Z大类Q一是封包格式出现异常;二是包内容Q即包数据Q出现异常。在包格式的异常处理方面,我们在最底端的网l数据包接收模块便可以加以处理。而对于封包数据内容出现的异常Q只有依靠游戏本w的逻辑d以判定和验。游戏逻辑斚w的异常处理,是随每个游戏的不同而不同的Q所以,本文随后的内容将重点阐述在网l数据包接收模块中的异常处理?/p>

  为方便以下的讨论Q先明确两个概念Q这两个概念是ؓ了叙q方面,W者自行取的,q无标准可言Q:
  1、逻辑包:指的是在应用层提交的数据包,一个完整的逻辑包可以表CZ个确切的逻辑意义。比如登录包Q它里面可以含有用户名字段和密码字Dc尽它看上M是一D늼冲区数据Q但q个~冲区里的各个区间是代表一定的逻辑意义的?br>  2、物理包Q指的是使用recv(recvfrom)或wsarecv(wsarecvfrom)从网l底层接收到的数据包Q这h到的一个数据包Q能不能表示一个完整的逻辑意义Q要取决于它是通过UDPcȝ“数据报协?#8221;发的包还是通过TCPcȝ“协?#8221;发的包?/p>

  我们知道QTCP是流协议Q?#8220;协?#8221;?#8220;数据报协?#8221;的不同点在于Q?#8220;数据报协?#8221;中的一个网l包本n是一个完整的逻辑包,也就是说Q在应用层用sendto发送了一个逻辑包之后,在接收端通过recvfrom接收到的是刚才使用sendto发送的那个逻辑包,q个包不会被分开发送,也不会与其它的包攑֜一起发送。但对于TCP而言QTCP会根据网l状况和neagle法Q或者将一个逻辑包单独发送,或者将一个逻辑包分成若q次发送,或者会若q个逻辑包合在一起发送出厅R正因ؓTCP在逻辑包处理方面的q种_合性,要求我们在作ZTCP的应用时Q一般都要编写相应的拼包、解包代码?/p>

  因此Q基于TCP的上层应用,一般都要定义自q包格式。TCP的封包定义中Q除了具体的数据内容所代表的逻辑意义之外Q第一步就是要定以何U方式表C当前包的开始和l束。通常情况下,表示一个TCP逻辑包的开始和l束有两U方式:
  1、以Ҏ的开始和l束标志表示Q比如FF00表示开始,00FF表示l束?br>  2、直接以包长度来表示。比如可以用W一个字节表C包总长度,如果觉得q样的话包比较小Q也可以用两个字节表C包长度?/p>

  下面要l出的代码是以第2U方式定义的数据包,包长度以每个包的前两个字节表示。我结合着代码l出相关的解释和说明?/p>

  函数中用到的变量说明Q?/p>

  CLIENT_BUFFER_SIZEQ缓冲区的长度,定义为:Const int CLIENT_BUFFER_SIZE=4096?br>  m_ClientDataBufQ数据整理缓冲区Q每ơ收到的数据Q都会先被复制到q个~冲区的末尾Q然后由下面的整理函数对q个~冲行整理。它的定义是Qchar m_ClientDataBuf[2* CLIENT_BUFFER_SIZE]?br>  m_DataBufByteCountQ数据整理缓冲区中当前剩余的未整理字节数?br>  GetPacketLen(const char*)Q函敎ͼ可以Ҏ传入的缓冲区首址按照应用层协议取出当前逻辑包的长度?br>  GetGamePacket(const char*, int)Q函敎ͼ可以Ҏ传入的缓冲区生成相应的游戏逻辑数据包?br>  AddToExeList(PBaseGamePacket)Q函敎ͼ指定的游戏逻辑数据包加入待处理的游戏逻辑数据包队列中Q等待逻辑处理U程对其q行处理?br>  DATA_POSQ指的是除了包长度、包cd{这些标志型字段之外Q真正的数据包内容的起始位置?/p>

Bool SplitFun(const char* pData,const int &len)
{
    PBaseGamePacket pGamePacket=NULL;
    __int64 startPos=0, prePos=0, i=0;
    int packetLen=0;

  //先将本次收到的数据复制到整理~冲区尾?br>    startPos = m_DataBufByteCount; 
    memcpy( m_ClientDataBuf+startPos, pData, len );
    m_DataBufByteCount += len;   

    //当整理缓冲区内的字节数少于DATA_POS字节Ӟ取不到长度信息则退?br> //注意Q退出时q不|m_DataBufByteCount?
    if (m_DataBufByteCount < DATA_POS+1)
        return false;

    //Ҏ正常逻辑Q下面的情况不可能出玎ͼ为稳妥v见,q是加上
    if (m_DataBufByteCount >  2*CLIENT_BUFFER_SIZE)
    {
        //讄m_DataBufByteCount?Q意味着丢弃~冲Z的现有数?br>        m_DataBufByteCount = 0;

  //可以考虑开N误格式数据包的处理接口,处理逻辑交给上层
  //OnPacketError()
        return false;
    }

     //q原起始指针
     startPos = 0;

     //只有当m_ClientDataBuf中的字节个数大于最包长度时才能执行此语句
    packetLen = GetPacketLen( pIOCPClient->m_ClientDataBuf );

    //当逻辑层的包长度不合法Ӟ则直接丢弃该?br>    if ((packetLen < DATA_POS+1) || (packetLen > 2*CLIENT_BUFFER_SIZE))
    {
        m_DataBufByteCount = 0;

  //OnPacketError()
        return false;
    }

    //保留整理~冲区的末尾指针
    __int64 oldlen = m_DataBufByteCount;

    while ((packetLen <= m_DataBufByteCount) && (m_DataBufByteCount>0))
    {
        //调用拼包逻辑Q获取该~冲区数据对应的数据?br>        pGamePacket = GetGamePacket(m_ClientDataBuf+startPos, packetLen);

        if (pGamePacket!=NULL)
        {
            //数据包加入执行队列
            AddToExeList(pGamePacket);
        }

        pGamePacket = NULL;
 
  //整理~冲区的剩余字节数和新逻辑包的起始位置q行调整
        m_DataBufByteCount -= packetLen;
        startPos += packetLen;

        //D留~冲区的字节数少于一个正常包大小Ӟ只向前复制该包随后退?br>        if (m_DataBufByteCount < DATA_POS+1)
        {
            for(i=startPos; i<startPos+m_DataBufByteCount; ++i)
                m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];

            return true;
        }

        packetLen = GetPacketLen(m_ClientDataBuf + startPos );

         //当逻辑层的包长度不合法Ӟ丢弃该包及缓冲区以后的包
        if ((packetLen<DATA_POS+1) || (packetLen>2*CLIENT_BUFFER_SIZE))
        {
            m_DataBufByteCount = 0;

      //OnPacketError()
            return false;
        }

         if (startPos+packetLen>=oldlen)
        {
            for(i=startPos; i<startPos+m_DataBufByteCount; ++i)
                m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];          

            return true;
        }
     }//取所有完整的?/p>

     return true;
}

  以上便是数据接收模块的处理函敎ͼ下面是几点简要说明:

  1、用于拼包整理的~冲?m_ClientDataBuf)应该比recv中指定的接收~冲?pData)长度(CLIENT_BUFFER_SIZE)要大Q通常前者是后者的2?2*CLIENT_BUFFER_SIZE)或更大?/p>

  2、ؓ避免因ؓ剩余数据前移而导致的额外开销Q徏议m_ClientDataBuf使用环Ş~冲区实现?/p>

  3、ؓ了避免出现无法拼装的包,我们U定每次发送的逻辑包,其单个逻辑包最大长度不可以过CLIENT_BUFFER_SIZE?倍。因为我们的整理~冲区只?*CLIENT_BUFFER_SIZEq么长,更长的数据,我们无法整理。这p求在协议的设计上以及最l的发送函数的处理上要加上q样的异常处理机制?/p>


  4、对于数据包q短或过长的包,我们通常的情冉||m_DataBufByteCount?Q即舍弃当前包的处理。如果此处不讄m_DataBufByteCount?也可Q但该客L只要发了一ơ格式错误的包,则其后发过来的包则也将q带着产生格式错误Q如果设|m_DataBufByteCount?Q则可以比较好的避免后的包受此包的格式错误影响。更好的作法是,在此处开放一个封包格式异常的处理接口(OnPacketError)Q由上层逻辑军_对这U异常如何处|。比如上层逻辑可以对封包格式方面出现的异常q行计数Q如果错误的ơ数过一定的|则可以断开该客L的连接?/p>

  5、徏议不要在recv或wsarecv的函数后Q就紧接着作以上的处理。当recv收到一D|据后Q生成一个结构体或对?它主要含有data和len两个内容Q前者是数据~冲区,后者是数据长度)Q将q样的一个结构体或对象放C个队列中由后面的U程对其使用SplitFun函数q行整理。这P可以最大限度地提高|络数据的接攉度Q不臛_为数据整理的原因而在此处费旉?/p>

  代码中,我已l作了比较详l的注释Q可以作为拼包函数的参考,代码是从偶的应用中提取、修改而来Q本w只为演CZ用,所以未作调试,应用旉要你自己d善。如有疑问,可以我的blog上留a提出?/p>


本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/clever101/archive/2008/10/12/3061679.aspx



2010-02-18 02:27 发表评论
]]>
常用的setsockopthttp://www.shnenglu.com/jack-wang/archive/2010/02/13/107802.htmlSat, 13 Feb 2010 07:39:00 GMThttp://www.shnenglu.com/jack-wang/archive/2010/02/13/107802.htmlhttp://www.shnenglu.com/jack-wang/comments/107802.htmlhttp://www.shnenglu.com/jack-wang/archive/2010/02/13/107802.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/107802.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/107802.html

2010-02-13 15:39 发表评论
]]>
使用VC9+PlatformSDK~写WinSockE序Ӟ#include WinSock2.h文gDcd重定义问题(麻G隔壁的,微Y又想搞什么花Phttp://www.shnenglu.com/jack-wang/archive/2010/02/13/107800.htmlSat, 13 Feb 2010 07:14:00 GMThttp://www.shnenglu.com/jack-wang/archive/2010/02/13/107800.htmlhttp://www.shnenglu.com/jack-wang/comments/107800.htmlhttp://www.shnenglu.com/jack-wang/archive/2010/02/13/107800.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/107800.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/107800.html 
 

~写WinSockE序Ӟ如果不包含WinSock2.h文g很多pȝcd无法识别。可是如果包含了WinSock2.h文g则报N多系l类型重定义的错误?br>例如 Q?br>      mswsock.h(69) : error C2065: 'SOCKET' : undeclared identifier
      winsock2.h(99) : error C2011: 'fd_set' : 'struct' type redefinition

 
      多亏了网上诸多网友的帖子l了我提C,问题解决了。跪谢了?br>      

       Windows|络~程臛_需要两个头文gQwinsock2.h和windows.hQ而在WinSock2.0之前q存在一个老版本的winsock.h。正是这三个头文件的包含序Q导致了上述问题的出现?/p>

先看看winsock2.h的内容,在文件开头有如下宏定义:

#ifndef _WINSOCK2API_
#define _WINSOCK2API_
#define _WINSOCKAPI_   /* Prevent inclusion of winsock.h in windows.h */

_WINSOCK2API_很容易理解,q是最常见的防止头文g重复包含的保护措施。_WINSOCKAPI_的定义则是ؓ了阻止对老文件winsock.h的包含,x_如果用户先包含了winsock2.h׃允许再包含winsock.h了,否则会导致类型重复定义。这是怎样做到的呢Q很单,因ؓwinsock.h的头部同样存在如下的保护措施Q?/p>

#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_

再回q头来看winsock2.hQ在上述内容之后紧跟着如下宏指令:

/*
* Pull in WINDOWS.H if necessary
*/
#ifndef _INC_WINDOWS
#include <windows.h>
#endif /* _INC_WINDOWS */

其作用是如果用户没有包含windows.hQ_INC_WINDOWS在windows.h中定义)p动包含它Q以定义WinSock2.0所需的类型和帔R{?/p>

现在切换到windows.hQ查找winsockQ我们会惊奇的发C下内容:

#ifndef WIN32_LEAN_AND_MEAN
#include <cderr.h>
#include <dde.h>
#include <ddeml.h>
#include <dlgs.h>
#ifndef _MAC
#include <lzexpand.h>
#include <mmsystem.h>
#include <nb30.h>
#include <rpc.h>
#endif
#include <shellapi.h>
#ifndef _MAC
#include <winperf.h>

#if(_WIN32_WINNT >= 0x0400)
#include <winsock2.h>
#include <mswsock.h>
#else
#include <winsock.h>
#endif /* _WIN32_WINNT >= 0x0400 */

#endif
// q里省略掉一部分内容
#endif /* WIN32_LEAN_AND_MEAN */

看到没?windows.h会反向包含winsock2.h或者winsock.hQ相互间的包含便是万恶之源!

下面具体分析一下问题是怎么发生的?/p>

错误情Ş1Q?/strong>我们在自q工程中先包含winsock2.h再包含windows.hQ如果WIN32_LEAN_AND_MEAN未定义且_WIN32_WINNT大于或等?x400Q那么windows.h会在winsock2.h开头被自动引入Q而windows.h又会自动引入mswsock.hQ此Ӟmswsock.h里所用的socketcdq尚未定义,因此会出现类型未定义错误?/p>

错误情Ş2Q?/strong>先包含windows.h再包含winsock2.hQ如果WIN32_LEAN_AND_MEAN未定义且_WIN32_WINNT未定义或者其版本号小?x400Q那么windows.h会自动导入旧有的winsock.hQ这样再当winsock2.h被包含时便会引v重定义?/p>

    q里要说明的是,宏WIN32_LEAN_AND_MEAN的作用是减小win32头文件尺总加快~译速度Q一般由AppWizard在stdafx.h中自动定义。_WIN32_WINNT的作用是开启高版本操作pȝ下的Ҏ函数Q比如要使用可等待定时器QWaitableTimerQ,得要求_WIN32_WINNT的值大于或{于0x400。因此,如果你没有遇CqC个问题,很可能是你没有在q些条g下进行网l编E?/p>

    问题q没有结束,要知道除了VC自带windows库文件外QMS的Platform SDK也含有这些头文g。我们很可能发现在之前能够好好编译的E序在改变了windows头文件包含\径后又出了问题。原因很单,Platform SDK中的windows.h与VC自带的文件存在差异,其相同位|的代码如下Q?/p>

#ifndef WIN32_LEAN_AND_MEAN
#include <cderr.h>
#include <dde.h>
#include <ddeml.h>
#include <dlgs.h>
#ifndef _MAC
#include <lzexpand.h>
#include <mmsystem.h>
#include <nb30.h>
#include <rpc.h>
#endif
#include <shellapi.h>
#ifndef _MAC
#include <winperf.h>
#include <winsock.h> // q里直接包含winsock.h
#endif
#ifndef NOCRYPT
#include <wincrypt.h>
#include <winefs.h>
#include <winscard.h>
#endif

#ifndef NOGDI
#ifndef _MAC
#include <winspool.h>
#ifdef INC_OLE1
#include <ole.h>
#else
#include <ole2.h>
#endif /* !INC_OLE1 */
#endif /* !MAC */
#include <commdlg.h>
#endif /* !NOGDI */
#endif /* WIN32_LEAN_AND_MEAN */

唉,我们不禁要问MSZ么要搞这么多花样Q更让h气愤的是Q既然代码不一Pwindows.h里却没有M一个宏定义能够帮助E序辨别当前使用的文件是VC自带的还是PSDK里的?/p>

    后来Q我写了一个头文g专门处理winsock2.h的包含问题,名ؓwinsock2i.hQ只需在要使用WinSock2.0的源文g里第一个包含此文g卛_Q不q由于前面提到的问题Q当使用PSDKӞ需要手工定义一下USING_WIN_PSDKQ源码如下:

//
// winsock2i.h - Include winsock2.h safely.
//
// Copyleft 02/24/2005 by freefalcon
//
//
// When WIN32_LEAN_AND_MEAN is not defined and _WIN32_WINNT is LESS THAN 0x400,
// if we include winsock2.h AFTER windows.h or winsock.h, we get some compiling
// errors as following:
//   winsock2.h(99) : error C2011: 'fd_set' : 'struct' type redefinition
//
// When WIN32_LEAN_AND_MEAN is not defined and _WIN32_WINNT is NOT LESS THAN 0x400,
// if we include winsock2.h BEFORE windows.h, we get some other compiling errors:
//   mswsock.h(69) : error C2065: 'SOCKET' : undeclared identifier
//
// So, this file is used to help us to include winsock2.h safely, it should be
// placed before any other header files.
//

#ifndef _WINSOCK2API_

// Prevent inclusion of winsock.h
#ifdef _WINSOCKAPI_
#error Header winsock.h is included unexpectedly.
#endif

// NOTE: If you use Windows Platform SDK, you should enable following definition:
// #define USING_WIN_PSDK

#if !defined(WIN32_LEAN_AND_MEAN) && (_WIN32_WINNT >= 0x0400) && !defined(USING_WIN_PSDK)
#include <windows.h>
#else
#include <winsock2.





2010-02-13 15:14 发表评论
]]>
隧道技术及其应用(架设服务器集的理想选择Q?/title><link>http://www.shnenglu.com/jack-wang/archive/2010/02/08/107501.html</link><dc:creator>王</dc:creator><author>王</author><pubDate>Mon, 08 Feb 2010 09:13:00 GMT</pubDate><guid>http://www.shnenglu.com/jack-wang/archive/2010/02/08/107501.html</guid><wfw:comment>http://www.shnenglu.com/jack-wang/comments/107501.html</wfw:comment><comments>http://www.shnenglu.com/jack-wang/archive/2010/02/08/107501.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/jack-wang/comments/commentRss/107501.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/jack-wang/services/trackbacks/107501.html</trackback:ping><description><![CDATA[<h1>隧道技?/h1> <div style="FONT-SIZE: 10pt" id=lemmaContent>  隧道技术及其应?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   隧道技术(TunnelingQ是一U通过使用互联|络的基设施在网l之间传递数据的方式。用隧道传递的数据Q或负蝲Q可以是不同协议的数据或包。隧道协议将其它协议的数据或包重新装然后通过隧道发送。新的头提供\׃息,以便通过互联|传递被装的负载数据?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   q里所说的隧道cM于点到点的连接。这U方式能够来自许多信息源的|络业务在同一个基设施中通过不同的隧道进行传输。隧道技术用点对点通信协议代替了交换连接,通过路由|络来连接数据地址。隧道技术允许授权移动用h已授权的用户在Q何时间、Q何地点访问企业网l?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   通过隧道的徏立,可实玎ͼ<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   * 数据流强制送到特定的地址<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   * 隐藏U有的网l地址<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   * 在IP|上传递非IP数据?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   * 提供数据安全支持<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   q来出现了一些新的隧道技术,q在不同的系l中得到q用和拓展?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   隧道技?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   为创建隧道,隧道的客h和服务器双方必须使用相同的隧道协议。隧道技术可分别以第2层或W?层隧道协议ؓ基础。第2层隧道协议对应于OSI模型的数据链路层Q用作ؓ数据交换单位。PPTPQ点对点隧道协议Q、L2TPQ第二层隧道协议Q和L2FQ第2层{发协议)都属于第2层隧道协议,是将用户数据装在点对点协议QPPPQ中通过互联|发送。第3层隧道协议对应于OSI模型的网l层Q用包作ؓ数据交换单位。IPIPQIP over IPQ以及IPSec隧道模式属于W?层隧道协议,是将IP包封装在附加的IP包头中,通过IP|络传送。无论哪U隧道协议都是由传输的蝲体、不同的装格式以及用户数据包组成的。它们的本质区别在于Q用L数据包是被封装在哪种数据包中在隧道中传输?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   点对炚w道协?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   PPTPQPoint to Point Tunneling ProtocolQ提供PPTP客户机和PPTP服务器之间的加密通信。PPTP客户机是指运行了该协议的PC机,如启动该协议的Windows95/98QPPTP服务器是指运行该协议的服务器Q如启动该协议的WindowsNT服务器。PPTP是PPP协议的一U扩展。它提供了一U在互联|上建立多协议的安全虚拟专用|(VPNQ的通信方式。远端用戯够透过M支持PPTP的ISP讉K公司的专用网?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   通过PPTPQ客户可采用拨号方式接入公用IP|。拨L户首先按常规方式拨到ISP的接入服务器QNASQ,建立PPPq接Q在此基上,用户q行二次拨号建立到PPTP服务器的q接Q该q接UCؓPPTP隧道Q实质上是基于IP协议的另一个PPPq接Q其中的IP包可以封装多U协议数据,包括TCPQIP、IPX和NetBEUI。PPTP采用了基于RSA公司RC4的数据加密方法,保证了虚拟连接通道的安全。对于直接连C联网的用户则不需要PPP的拨可接,可以直接与PPTP服务器徏立虚拟通道。PPTP把徏立隧道的d权交l了用户Q但用户需要在其PCZ配置PPTPQ这样做既增加了用户的工作量Q又会给|络带来隐患。另外,PPTP只支持IP作ؓ传输协议?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   W二层{发协?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   L2F(Layer Two Forwarding protocol )是由Cisco公司提出的可以在多种介质Q如ATM、中、IP|上建立多协议的安全虚拟专用|的通信。远端用戯通过M拨号方式接入公用IP|,首先按常规方式拨到ISP的接入服务器QNASQ,建立PPPq接QNASҎ用户名等信息Q徏立直达HGW服务器的W二重连接。在q种情况下,隧道的配|和建立对用h完全透明的。其体系l构见图1?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   W二层隧道协?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   L2TPQLayer Two Tunneling ProtocolQ结合了L2F和PPTP的优点,允许用户从客L或访问服务器端徏立VPNq接。L2TP是把链\层的PPP帧装入公用网l设施,如IP、ATM、中中进行隧道传输的装协议。其体系l构见图1?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   Cisco、Ascend、Microsoft和RedBack公司的专家们在修改了十几个版本后Q终于在1999q?月公布了L2TP的标准RFC2661。目前用h可问InternetӞ必须使用IP协议Qƈ且其动态得到的IP地址也是合法的。L2TP的好处在于支持多U协议,用户可以保留原有的IPX、Appletalk{协议或公司原有的IP地址。L2TPq解决了多个PPP链\的捆l问题,PPP链\捆绑要求其成员均指向同一个NASQL2TP则允许在物理上连接到不同NAS的PPP链\Q在逻辑上的l点为同一个物理设备。L2TP扩展了PPPq接Q在传统的方式中用户通过模拟电话U或ISDN/ADSL与网l访问服务器建立一个第2层的q接Qƈ在其上运行PPPQ第2层连接的l点和PPP会话的终点均讑֜同一个设备上(如NAS)。L2TP作ؓPPP的扩充提供了更强大的功能Q包括允许第2层连接的l点和PPP会话的终点分别设在不同的讑֤上?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   L2TP主要由LAC(L2TP Access Concentrator)和LNS(L2TP Network Server)构成。LAC支持客户端的L2TPQ发起呼叫,接收呼叫和徏立隧道;LNS是所有隧道的l点。在传统的PPPq接中,用户拨号q接的终ҎLACQ而L2TP能把PPP协议的终点g伸到LNS?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   L2TP的徏立过E如??br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   1.用户通过公用电话|或ISDN拨号呼叫本地接入服务器LACQLAC接受呼叫q进行基本的识别q程Q这一q程可以采用几种标准Q如域名、呼叫线路识?CLID)或拨号ID业务(DNIS){?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   2.当用戯认为合法企业用hQ就建立一个通向LNS的拨号VPN隧道?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   3.企业内部的安全服务器如TACACS+、RADIUSҎL戯行验证?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   4.LNS与远E用户交换PPP信息Q分配IP地址。LNS可采用企业专用地址(未注册的IP地址)或服务提供商提供的地址I间分配IP地址。因为内部源IP地址与目的地IP地址实际上都通过服务提供商的IP|络在PPP信息包内传送,企业专用地址Ҏ供者的|络是透明的?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   5.端到端的数据从拨L户传到LNS?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   在实际应用中QLAC拨LLPPP帧封装后Q传送到LNSQ后者去掉封装包_取出PPP帧,再去掉PPP帧头Q最后获得网l层数据包?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   L2TP方式l服务提供商和用户带来了许多方便。用户不需要在PC板上安装专门的客L软gQ企业网可以使用未注册的IP地址Qƈ在本地管理认证数据库Q从而降低了应用成本和培训维护费用?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   与PPTP和L2F相比QL2TP的优点在于提供了差错和流量控ӞL2TP使用UDP装和传送PPP帧。面向无q接的UDP无法保证|络数据的可靠传输,L2TP使用NrQ下一个希望接受的信息序列P和NsQ当前发送的数据包序列号Q字D进行流量和差错控制。双斚w过序列h定数据包的序和缓冲区Q一旦丢失数据,Ҏ序列号可以进行重发?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   作ؓPPP的扩展协议,L2TP支持标准的安全特性CHAP和PAPQ可以进行用戯n份认证。L2TP定义了控制包的加密传输,每个被徏立的隧道分别生成一个独一无二的随机钥匙,以便对付ƺ骗性的dQ但是它对传输中的数据ƈ不加密?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   通用路由装<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   通用路由装QGREQGeneric Routing EncapsulationQ在RFC1701/RFC1702中定义,它规定了怎样用一U网l层协议d装另一U网l层协议的方法。GRE的隧道由两端的源IP地址和目的IP地址来定义,它允许用户用IP装IP、IPX、AppleTalkQƈ支持全部的\由协议,如RIP、OSPF、IGRP、EIGRP。通过GREQ用户可以利用公用IP|络q接IPX|络和AppleTalk|络Q还可以使用保留地址q行|络互联Q或对公|隐藏企业网的IP地址?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   GRE的包头包含了协议cdQ用于标明乘客协议的cdQ;校验和包括了GRE的包头和完整的乘客协议与数据Q密钥(用于接收端验证接收的数据Q;序列P用于接收端数据包的排序和差错控制Q和路由信息Q用于本数据包的路由Q?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   GRE只提供了数据包的装Q它没有防止|络侦听和攻ȝ加密功能。所以在实际环境中它常和IPsec一起用,由IPsec为用h据的加密Q给用户提供更好的安全服务?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   IP安全协议<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   IP安全协议QIPSecQIP SecurityQ实际上是一套协议包而不是一个独立的协议Q这一点对于我们认识IPSec是很重要的。从1995q开始IPSec的研I以来,IETF IPSec工作l在它的主页上发布了几十个Internet草案文献?2个RFC文g。其中,比较重要的有RFC2409 IKEQ互q网密钥交换Q、RFC2401 IPSec协议、RFC2402 AH验证包头、RFC2406 ESP加密数据{文件?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   IPSec安全体系包括3个基本协议:AH协议为IP包提供信息源验证和完整性保证;ESP协议提供加密机制Q密钥管理协?ISAKMP)提供双方交流时的׃n安全信息。ESP和AH协议都有相关的一pd支持文gQ规定了加密和认证的法。最后,解释域(DOIQ通过一pd命o、算法、属性和参数q接所有的IPSecl文件?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   隧道技术应?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   <a href="http://www.shnenglu.com/view/480950.htm" target=_blank><u><font color=#0000ff>虚拟专用|络</font></u></a><br> <div style="FONT-SIZE: 10pt" class=spctrl><u><font color=#0000ff></font></u></div>   VPN是Internet技术迅速发展的产物Q其单的定义是,在公用数据网上徏立属于自q专用数据|。也是说不再用长途专U徏立专用数据网Q而是充分利用完善的公用数据网建立自己的专用网。它的优ҎQ既可连到公|所能达到的M地点Qn受其保密性、安全性和可管理性,又降低网l的使用成本?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   VPN依靠Internet服务提供商(ISPQ和其他的网l服务提供商QNSPQ在公用|中建立自己的专?#8220;隧道”Q不同的信息来源Q可分别使用不同?#8220;隧道”q行传输?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   新出台的标准ISE CHEIP6版保证用h据的安全加密。由于用户对企业|传输个人数据很敏感Q因此集成度更高?a href="http://www.shnenglu.com/view/105152.htm" target=_blank><u><font color=#0000ff>VPN技?/font></u></a>不久会行h?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   Linux 中的IP隧道<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   Z在TCP/IP|络中传输其他协议的数据包,Linux采用了一UIP隧道技术。在已经使用多年的桥接技术中是通过在源协议数据包上再套上一个IP协议帽来实现?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   利用IP隧道传送的协议包也包括IP数据包,Linux的IPIP包封指的是q种情况?a href="http://www.shnenglu.com/view/65888.htm" target=_blank><u><font color=#0000ff>UdIP</font></u></a>QMobile-IPQ和IP多点q播QIP-MulticastQ是两个行的例子。目前,IP隧道技术在VPN中也昄出极大的力?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   UdIP是在全球Internet上提供移动功能的一U服务,它允许节点在切换链\时仍可保持正在进行的通信。它提供了一UIP路由机制QɿUd节点以一个永久的IP地址q接CQ何链路上。与特定L路由技术和数据链\层方案不同,UdIPq要解决安全性和可靠性问题,q与传输媒介无关。移动IP的可扩展性其可以在整个互联|上应用?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   GPRS隧道协议<br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   随着隧道技术的发展Q各U业务已l开始根据本业务的特点制定相应的隧道协议。GPRSQGeneral Packet Radio ServiceQ中的隧道协议GTPQGPRS Tunnel ProtocolQ就是一例?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   GPRS是GSM提供的分l交换和分组传输方式的新的承载业务,可以应用在PLMNQPublic Land Mobile NetworkQ内部或应用在GPRS|与外部互联分组数据|(IP、X.25Q之间的分组数据传送,GPRS能提供到现有数据业务的无~连接。它在GSM|络中增加了两个节点Q服务GPRS支持节点QSGSN─serving GPRS support nodeQ和|关GPRS支持节点QGGSN─Gateway GPRS support nodeQ?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   SGSN是GPRS骨干|与无线接入|之间的接口Q它分l交换到正确的基站子pȝQBSSQ。其d包括提供对移动台的加密、认证、会话(sessionQ管理、移动性管理和逻辑链\理。它也提供到HLR{数据库的连接?br> <div style="FONT-SIZE: 10pt" class=spctrl></div>   通过GPRS隧道协议可ؓ多种协议的数据分l通过GPRS骨干|提供隧道。GTPҎ所q蝲的协议需求,利用TCP或UDP协议来分别提供可靠的q接Q如支持X.25的分l传输)和无q接服务Q如IP分组Q?/div> <img src ="http://www.shnenglu.com/jack-wang/aggbug/107501.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/jack-wang/" target="_blank">王</a> 2010-02-08 17:13 <a href="http://www.shnenglu.com/jack-wang/archive/2010/02/08/107501.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SocketE序开发,发送端写入数据成功Q接收端收不到数据的现象分析http://www.shnenglu.com/jack-wang/archive/2010/01/28/106660.htmlThu, 28 Jan 2010 10:47:00 GMThttp://www.shnenglu.com/jack-wang/archive/2010/01/28/106660.htmlhttp://www.shnenglu.com/jack-wang/comments/106660.htmlhttp://www.shnenglu.com/jack-wang/archive/2010/01/28/106660.html#Feedback1http://www.shnenglu.com/jack-wang/comments/commentRss/106660.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/106660.htmlProblem:

  • 1 client 1 server, connected with non-block tcp socket. Linux 2.6.*+.
  • Client 写入大概 3k 数据?socket?
  • Write()正确q回实际写入字节数?
  • Server 什么也收不到?

Causes:

  • 发送端 MTUE大于\由器上的MTU讄
  • 通知发送端需要拆包的ICMP在某处被杀掉了
  • 发送端不停的重发包

讄了DF标志的ip包当遇到路由器的MTU比包的时候,不会被\由器拆包。而\由器发送icmp消息到发送端Q通知它应该拆包?/p>

但icmp消息被防火墙拦截下来?/p>

环境和现象:
q个例子中,MTU在client和server都是1500.

dump出来的包如下:

客户端看到的:
发送了2个包Q后1个包成功Q第1个过大而不停的被发?

id 57558, offset 0, flags [DF], proto: TCP (6), length: 1500) 10.54.40.43.43145 > 10.29.14.74.http: ., cksum 0×5096 (incorrect (-> 0×5c4e), 0:1448(1448) ack 1 win 46

17:23:06.933580 IP (tos 0×0, ttl 64, id 57559, offset 0, flags [DF], proto: TCP (6), length: 730) 10.54.40.43.43145 > 10.29.14.74.http: P, cksum 0×4d94 (incorrect (-> 0×3933), 1448:2126(678) ack 1 win 46

17:23:07.167049 IP (tos 0×0, ttl 64, id 57560, offset 0, flags [DF], proto: TCP (6), length: 1500) 10.54.40.43.43145 > 10.29.14.74.http: ., cksum 0×5096 (incorrect (-> 0×5b5b), 0:1448(1448) ack 1 win 46

17:23:07.634922 IP (tos 0×0, ttl 64, id 57561, offset 0, flags [DF], proto: TCP (6), length: 1500) 10.54.40.43.43145 > 10.29.14.74.http: ., cksum 0×5096 (incorrect (-> 0×5987), 0:1448(1448) ack 1 win 46

接受端看到的:
只有730大小的包接受成功

17:23:08.605622 IP (tos 0×0, ttl 59, id 57559, offset 0, flags [DF], proto: TCP (6), length: 730) 202.108.3.204.43145 > 10.29.14.74.http: P, cksum 0×9d5b (correct), 1448:2126(678) ack 1 win 46

解决Ҏ:
调整发送端机器的配|?QQ?个)

在网l层?
Decrease mtu on network adapter:

ifconfig eth* mtu 1400

操作pȝ配置:
Clear the default ‘MTU discovery’ flag with sysctl:

net.ipv4.ip_no_pmtu_disc = 1

或在应用E序?
Set socket option ‘IP_MTU_DISCOVER’ with setsockopt(2) to clear ‘DF’ flag of IP package.

Reference:

  1. DF flag of IP package Header
  2. Internet Control Message Protocol
  3. IP fragmentation
  4. MTU or Maximum transmission unit
  5. IP programming
  6. Path MTU Discovery
  7. sysctl

ThanksQ?/strong>

esx kobe steve

来自Q?a >http://blog.developers.api.sina.com.cn/?p=672
原文Q?a >http://drdr-xp-tech.blogspot.com/2009/04/black-hole-socket-problem.html



2010-01-28 18:47 发表评论
]]>
asio异步方式http://www.shnenglu.com/jack-wang/archive/2009/08/19/93756.htmlTue, 18 Aug 2009 16:36:00 GMThttp://www.shnenglu.com/jack-wang/archive/2009/08/19/93756.htmlhttp://www.shnenglu.com/jack-wang/comments/93756.htmlhttp://www.shnenglu.com/jack-wang/archive/2009/08/19/93756.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/93756.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/93756.html异步方式 和同步方式不同,从来不花旉ȝ那些龟速的IO操作Q只是向pȝ说一声要做什么,然后可以做其它事去了?br />如果pȝ完成了操作, pȝ׃通过我之前给它的回调对象来通知我?
在ASIO库中Q异步方式的函数或方法名U前面都有“async_?前缀Q函数参数里会要求放一个回调函敎ͼ或仿函数Q?br />异步操作执行 后不有没有完成都会立即q回Q这时可以做一些其它事Q直到回调函敎ͼ或仿函数Q被调用Q说明异步操作已l完成?br />在ASIO中很多回调函数都只接受一个boost::system::error_code参数Q在实际使用时肯定是不够的,所以一?使用仿函数携带一堆相x据作为回调,或者用boost::bind来绑定一堆数据?
另外要注意的是,只有io_servicecȝrun()Ҏq行之后回调对象才会被调用,否则即ɾpȝ已经完成了异步操作也不会有Q 务动作?br />好了Q就介绍到这里,下面是我带来的异步方式TCP Helloworld服务器端:
1.#include
2.#include
3.#include
4.#include
5.#include
6.
7.using namespace boost::asio;
8.using boost::system::error_code;
9.using ip::tcp;
10.
11.struct CHelloWorld_Service{
12. CHelloWorld_Service(io_service &iosev)
13. :m_iosev(iosev),m_acceptor(iosev, tcp::endpoint(tcp::v4(), 1000))
14. {
15. }
16.
17. void start()
18. {
19. // 开始等待连接(非阻塞)
20. boost::shared_ptr psocket(new tcp::socket(m_iosev));
21. // 触发的事件只有error_code参数Q所以用boost::bind把socketl定q去
22. m_acceptor.async_accept(*psocket,
23. boost::bind(&CHelloWorld_Service::accept_handler, this, psocket, _1)
24. );
25. }
26.
 27. // 有客Lq接时accept_handler触发
28. void accept_handler(boost::shared_ptr psocket, error_code ec)
29. {
30. if(ec) return;
31. // l箋{待q接
32. start();
 33. // 昄q程IP
 34. std::cout << psocket->remote_endpoint().address() << std::endl;
35. // 发送信?非阻?
36. boost::shared_ptr pstr(new std::string("hello async world!"));
37. psocket->async_write_some(buffer(*pstr),
38. boost::bind(&CHelloWorld_Service::write_handler, this, pstr, _1, _2)
39. );
 40. }
 41.
42. // 异步写操作完成后write_handler触发
43. void write_handler(boost::shared_ptr pstr,
44. error_code ec, size_t bytes_transferred)
45. {
46. if(ec)
47. std::cout<< "发送失?" << std::endl;
48. else
49. std::cout<< *pstr << " 已发? << std::endl;
50. }
51.
52.private:
53. io_service &m_iosev;
54. ip::tcp::acceptor m_acceptor;
55.};
56.
57.int main(int argc, char* argv[])
58.{
59. io_service iosev;
60. CHelloWorld_Service sev(iosev);
 61. // 开始等待连?
62. sev.start();
63. iosev.run();
64.
65. return 0;
66.}
在这个例子中Q首先调用sev.start()开 始接受客Lq接?br />׃async_accept?用后立即q回Qstart()??也就马上完成了?
sev.start()?瞬间q回后iosev.run()开 始执行,iosev.run()Ҏ是一个@环,负责分发异步回调事gQ?br />?有所有异步操作全部完成才会返回?br />q里有个问题Q就是要保证start()Ҏ中m_acceptor.async_accept?作所用的tcp::socket对象在整个异步操作期间保持有?br />(?然系l底层异步操作了一半突然发现tcp::socket没了Q不是拿人家开涮嘛-_-!!!)Q?br />而且客户端连接进来后q个tcp::socket对象q?有用呢?br />q里的解军_法是使用一个带计数的智能指针boost::shared_ptrQƈ把这个指针作为参数绑定到回调函数上?br />一旦有客户q接Q我们在start()里给的回调函数accept_handler׃?调用Q?br />首先调用start()l箋异步{待?它客L的连接,然后使用l定q来的tcp::socket对象与当前客L通信?br />发送数据也使用了异步方?async_write_some)Q?同样要保证在整个异步发送期间缓冲区的有效性,
所以也用boost::bindl定了boost::shared_ptr?br />对于客户端也一P在connect和read_someҎ前加一个async_前缀Q然后加入回调即可,大家自己l习写一写?


2009-08-19 00:36 发表评论
]]>
asio同步方式http://www.shnenglu.com/jack-wang/archive/2009/08/19/93755.htmlTue, 18 Aug 2009 16:29:00 GMThttp://www.shnenglu.com/jack-wang/archive/2009/08/19/93755.htmlhttp://www.shnenglu.com/jack-wang/comments/93755.htmlhttp://www.shnenglu.com/jack-wang/archive/2009/08/19/93755.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/93755.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/93755.html Boost.Asio是一个跨q_的网l及底层IO的C++~程库,它用现代C++手法实现了统一的异步调用模型?br>头文?#include
名空?using namespace boost::asio;
ASIO库能够用TCP、UDP、ICMP、串口来发?接收数据Q?br>下面先介lTCP协议的读写操作对于读写方式,ASIO支持同步和异步两U方式,
首先d的是同步方式Q?br>下面请同步方式自我介l一下:大家好!我是同步方式Q我的主要特点就是执着Q?br>所有的操作都要完成或出错才会返回,不过偶的执着被大家称之ؓdQ实在是郁闷~~Q场下一片嘘壎ͼQ?br>其实q样 也是有好处的Q比如逻辑清晰Q编E比较容易?br>在服务器端,我会做个socket交给acceptor对象Q让它一直等客户端连q来Q连上以后再通过q个socket与客L通信Q?
而所有的通信都是以阻塞方式进行的Q读完或写完才会q回。在客户端也一P
q时我会拿着socket去连接服务器Q当然也是连上或出错了才q回Q最后也是以d的方式和服务器通信?br>有h认ؓ同步方式没有异步方式高效Q其实这是片面的理解?br>在单U程的情况下可能实如此Q我不能利用耗时的网l操作这D|间做别的?情,不是好的l筹Ҏ?br>不过q个问题可以通过多线E来避免Q比如在服务器端让其中一个线E负责等待客Lq接Q连接进来后把socket交给另外的线E去 和客L通信Q这样与一个客L通信的同时也能接受其它客L的连接,ȝE也完全被解放了出来?br>我的介绍有q里Q谢谢大Ӟ好,感谢同步方式的自我介l,
现在攑և同步方式的演CZ?L鼓掌!):
服务器端
1.#include
2.#include
3.
4.using namespace boost::asio;
 5.
 6.int main(int argc, char* argv[])
7.{
8. // 所有asioc都需要io_service对象
9. io_service iosev;
10. ip::tcp::acceptor acceptor(iosev,
11. ip::tcp::endpoint(ip::tcp::v4(), 1000));
12. for(;;)
13. {
14. // socket对象
15. ip::tcp::socket socket(iosev);
 16. // {待直到客户端连接进?
17. acceptor.accept(socket);
18. // 昄q接q来的客L
19. std::cout << socket.remote_endpoint().address() << std::endl;
20. // 向客L发送hello world!
21. boost::system::error_code ec;
22. socket.write_some(buffer("hello world!"), ec);
23.
24. // 如果出错Q打印出错信?
25. if(ec)
26. {
27. std::cout <<
28. boost::system::system_error(ec).what() << std::endl;
29. break;
30. }
31. // 与当前客户交互完成后循环l箋{待下一客户q接
32. }
33. return 0;
34.}

客户?
1.#include
2.#include
3.
4.using namespace boost::asio;
5.
6.int main(int argc, char* argv[])
7.{
8. // 所有asioc都需要io_service对象
9. io_service iosev;
10. // socket对象
11. ip::tcp::socket socket(iosev);
12. // q接端点Q这里用了本机q接Q可以修改IP地址试q程q接
13. ip::tcp::endpoint ep(ip::address_v4::from_string("127.0.0.1"), 1000);
14. // q接服务?
15. boost::system::error_code ec;
16. socket.connect(ep,ec);
17. // 如果出错Q打印出错信?
18. if(ec)
19. {
20. std::cout << boost::system::system_error(ec).what() << std::endl;
21. return -1; 22. }
23. // 接收数据
24. char buf[100];
25. size_t len=socket.read_some(buffer(buf), ec);
26. std::cout.write(buf, len);
27.
28. return 0;
29.}

从演CZ码可以得?
•ASIO的TCP协议通过boost::asio::ip?I间下的tcpc进行通信?
•IP地址Qaddress,address_v4,address_v6Q?端口号和协议版本l成一个端点(tcp:: endpointQ?br>用于在服务器端生成tcp::acceptor?象,q在指定端口上等待连接;或者在客户端连接到指定地址的服务器上?
•socket?服务器与客户端通信的桥梁,q接成功后所有的d都是通过socket?象实现的Q当socket?构后Q连接自动断 开?
•ASIOd所用的~冲区用buffer?数生成,q个函数生成的是一个ASIO内部使用的缓冲区c,它能把数l、指针(同时指定?)、std::vector、std::string、boost::array包装成缓冲区cR?br> •ASIO中的函数、类Ҏ都接受一个boost::system::error_codec?型的数据Q用于提供出错码?br>它可以{换成bool试是否出错Qƈ通过boost::system::system_errorc?获得详细的出错信息?br>另外Q也可以不向ASIO的函数或Ҏ提供 boost::system::error_codeQ这时如果出错的话就会直 接抛出异常,异常cd是boost::system:: system_error(它是从std::runtime_errorl承??

2009-08-19 00:29 发表评论
]]>
boost::asio 客户?/title><link>http://www.shnenglu.com/jack-wang/archive/2009/08/19/93754.html</link><dc:creator>王</dc:creator><author>王</author><pubDate>Tue, 18 Aug 2009 16:24:00 GMT</pubDate><guid>http://www.shnenglu.com/jack-wang/archive/2009/08/19/93754.html</guid><wfw:comment>http://www.shnenglu.com/jack-wang/comments/93754.html</wfw:comment><comments>http://www.shnenglu.com/jack-wang/archive/2009/08/19/93754.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/jack-wang/comments/commentRss/93754.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/jack-wang/services/trackbacks/93754.html</trackback:ping><description><![CDATA[<div>#pragma once</div><div>#include <iostream></div><div>#include <boost/asio.hpp></div><div>#include <boost/bind.hpp></div><div>#include <boost/shared_ptr.hpp></div><div></div><div></div><div>using namespace boost::asio::ip;</div><div>using namespace boost::asio;</div><div></div><div>class Client</div><div>{</div><div>public:</div><div><span style="white-space:pre"> </span>//boost::shared_ptr<Client><span style="white-space:pre"> </span>ClientPtr;</div><div></div><div>public:</div><div><span style="white-space:pre"> </span>Client(boost::asio::io_service& io_service, tcp::endpoint& endpoint);</div><div></div><div><span style="white-space:pre"> </span>~Client();</div><div></div><div>private:</div><div><span style="white-space:pre"> </span>void handle_connect(const boost::system::error_code& error);</div><div></div><div><span style="white-space:pre"> </span>void handle_read(const boost::system::error_code& error);</div><div></div><div><span style="white-space:pre"> </span>void handle_write(const boost::system::error_code& error);</div><div></div><div>private:</div><div><span style="white-space:pre"> </span>tcp::socket socket_; </div><div><span style="white-space:pre"> </span>char getBuffer[1024];</div><div>};<br /><br /><br /><div>#include "stdafx.h"</div><div>#include "Client.h"</div><div></div><div></div><div>Client::Client(boost::asio::io_service& io_service, tcp::endpoint& endpoint):</div><div>socket_(io_service)</div><div>{</div><div><span style="white-space:pre"> </span>socket_.async_connect(endpoint, boost::bind(&Client::handle_connect, this, boost::asio::placeholders::error));</div><div><span style="white-space:pre"> </span>::memset(getBuffer, '\0', 1024);</div><div>}</div><div></div><div>Client::~Client()</div><div>{</div><div></div><div>}</div><div></div><div>void Client::handle_connect(const boost::system::error_code& error)</div><div>{</div><div><span style="white-space:pre"> </span>if (!error)</div><div><span style="white-space:pre"> </span>{</div><div><span style="white-space:pre"> </span>// 一q上Q就向服务端发送信?nbsp;</div><div><span style="white-space:pre"> </span>boost::asio::async_write(socket_, boost::asio::buffer("hello,server!"),</div><div><span style="white-space:pre"> </span>boost::bind(&Client::handle_write, this, boost::asio::placeholders::error));</div><div></div><div><span style="white-space:pre"> </span>// boost::asio::async_read(...)d的字节长度不能大于数据流的长度,否则׃q入 </div><div><span style="white-space:pre"> </span>// ioservice.run()U程{待Qread后面的就不执行了?nbsp;</div><div><span style="white-space:pre"> </span>//boost::asio::async_read(socket, </div><div><span style="white-space:pre"> </span>// boost::asio::buffer(getBuffer,1024),</div><div><span style="white-space:pre"> </span>// boost::bind(&client::handle_read,this,boost::asio::placeholders::error) </div><div><span style="white-space:pre"> </span>// ); </div><div><span style="white-space:pre"> </span>socket_.async_read_some(boost::asio::buffer(getBuffer, 1024), </div><div><span style="white-space:pre"> </span>boost::bind(&Client::handle_read, this, boost::asio::placeholders::error));</div><div><span style="white-space:pre"> </span>}</div><div><span style="white-space:pre"> </span>else</div><div><span style="white-space:pre"> </span>{</div><div><span style="white-space:pre"> </span>socket_.close();</div><div><span style="white-space:pre"> </span>}</div><div>}</div><div></div><div>void Client::handle_read(const boost::system::error_code& error)</div><div>{</div><div><span style="white-space:pre"> </span>if (!error)</div><div><span style="white-space:pre"> </span>{</div><div><span style="white-space:pre"> </span>std::cout << getBuffer << std::endl;</div><div></div><div><span style="white-space:pre"> </span>//boost::asio::async_read(socket, </div><div><span style="white-space:pre"> </span>// boost::asio::buffer(getBuffer,1024), </div><div><span style="white-space:pre"> </span>// boost::bind(&client::handle_read,this,boost::asio::placeholders::error) </div><div><span style="white-space:pre"> </span>// ); </div><div><span style="white-space:pre"> </span>//q样可以实现@环读取了Q相当于whileQ?Q?nbsp;</div><div><span style="white-space:pre"> </span>//当然Q到了这里,做过|络的朋友就应该相当熟悉了,一些逻辑可以自行扩展了 </div><div><span style="white-space:pre"> </span>//惛_聊天室的朋友可以用多U程来实?nbsp;</div><div><span style="white-space:pre"> </span>socket_.async_read_some(boost::asio::buffer(getBuffer, 1024),</div><div><span style="white-space:pre"> </span>boost::bind(&Client::handle_read, this, boost::asio::placeholders::error));</div><div><span style="white-space:pre"> </span>}</div><div><span style="white-space:pre"> </span>else</div><div><span style="white-space:pre"> </span>{</div><div><span style="white-space:pre"> </span>socket_.close();</div><div><span style="white-space:pre"> </span>}</div><div>}</div><div></div><div>void Client::handle_write(const boost::system::error_code& error)</div><div>{</div><div></div><div>}<br /><br /><br />////////////////////////////////////////////////////////////////////////////////////////////////////////////////////<br /><div>#include "stdafx.h"</div><div>#include "Client.h"</div><div></div><div></div><div>using namespace boost::asio::ip;</div><div>using namespace boost::asio;</div><div></div><div>int _tmain(int argc, _TCHAR* argv[])</div><div>{</div><div><span style="white-space:pre"> </span>io_service ioservice;</div><div></div><div><span style="white-space:pre"> </span>tcp::endpoint endpoint(address_v4::from_string("127.0.0.1"), 8100);</div><div><span style="white-space:pre"> </span>//ClientPtr client_ptr(new Client(io_service, endpoint));</div><div><span style="white-space:pre"> </span>Client client(ioservice, endpoint);</div><div></div><div><span style="white-space:pre"> </span>ioservice.run();</div><div></div><div><span style="white-space:pre"> </span>return 0;</div><div>}</div><div></div></div></div><img src ="http://www.shnenglu.com/jack-wang/aggbug/93754.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/jack-wang/" target="_blank">王</a> 2009-08-19 00:24 <a href="http://www.shnenglu.com/jack-wang/archive/2009/08/19/93754.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>boost::asio 服务器端http://www.shnenglu.com/jack-wang/archive/2009/08/19/93753.htmlTue, 18 Aug 2009 16:21:00 GMThttp://www.shnenglu.com/jack-wang/archive/2009/08/19/93753.htmlhttp://www.shnenglu.com/jack-wang/comments/93753.htmlhttp://www.shnenglu.com/jack-wang/archive/2009/08/19/93753.html#Feedback0http://www.shnenglu.com/jack-wang/comments/commentRss/93753.htmlhttp://www.shnenglu.com/jack-wang/services/trackbacks/93753.html#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include "Session.h"
using namespace boost::asio::ip;
using namespace boost::asio;
class Acceptor 
{
public:
typedef boost::shared_ptr<Session> SessionPtr;
public: 
Acceptor(io_service& ioservice, tcp::endpoint& endpoint);
~Acceptor();
private: 
void handle_accept(const boost::system::error_code& error, SessionPtr session);
private: 
io_service& ioservice_;
tcp::acceptor acceptor_; 
};


#include "stdafx.h"
#include "Acceptor.h"
Acceptor::Acceptor(boost::asio::io_service& ioservice, tcp::endpoint& endpoint) : 
ioservice_(ioservice), 
acceptor_(ioservice, endpoint)
{
SessionPtr new_session(new Session(ioservice));
acceptor_.async_accept(new_session->socket(),
boost::bind(&Acceptor::handle_accept, this, boost::asio::placeholders::error, new_session));
}
Acceptor::~Acceptor()
{
}
void Acceptor::handle_accept(const boost::system::error_code& error, SessionPtr session)
{
if (!error)
{
std::cout << "get a new client!" << std::endl; //实现Ҏ个客L的数据处?nbsp;
session->start();
SessionPtr new_session(new Session(ioservice_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&Acceptor::handle_accept, this, boost::asio::placeholders::error, new_session));
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>
#define max_len 1024
using namespace boost::asio::ip;
class Session : public boost::enable_shared_from_this<Session>
{
public:
Session(boost::asio::io_service& ioservice);
~Session();
tcp::socket& socket();
void start();
private:
void handle_write(const boost::system::error_code& error);
void handle_read(const boost::system::error_code& error);
private:
tcp::socket m_socket;
char data_[max_len];
};


2009-08-19 00:21 发表评论
]]>
|络游戏服务器设?/title><link>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70968.html</link><dc:creator>王</dc:creator><author>王</author><pubDate>Thu, 01 Jan 2009 19:54:00 GMT</pubDate><guid>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70968.html</guid><wfw:comment>http://www.shnenglu.com/jack-wang/comments/70968.html</wfw:comment><comments>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70968.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/jack-wang/comments/commentRss/70968.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/jack-wang/services/trackbacks/70968.html</trackback:ping><description><![CDATA[<h1><img height=14 alt=转蝲 src="http://blog.csdn.net/images/turnship.gif" width=15 border=0> <a ><font color=#3468a4>|络游戏服务器设?/font></a><cite class=fav_csdnstylebykimi><font face=Tahoma color=#3468a4 size=2>收藏</font></cite><span id=Post.ascx_ViewPost_PreviousAndNextEntriesUp></h1> <div id="yi4ewue" class=blogstory>原文Q?br><a >http://blog.csdn.net/staryy/archive/2008/11/29/3410388.aspx</a><br></span> <script>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script> <a ><font color=#006bad>http://www.yq8.cn/html/15/215-47957.html</font></a> <div id="qg8sisu" class=xspace-itemmessage id=xspace-showmessage> <p><font face=Tahoma size=2> </p> <div class="mgoc8y4" id=show> <h1 class=xspace-title>|络游戏服务器设?/h1> <p class=xspace-smalltxt><a ><font color=#3399cc>上一?/font></a> / <a ><font color=#3399cc>下一?/font></a>  2008-02-22 17:34:18 / 个h分类Q?a ><font color=#3399cc>回收?/font></a> </p> </div> </font> <p><font face=Tahoma size=2></font> </p> <p><font face=Tahoma size=2> 谈这个话题之前,首先要让大家知道Q什么是<a onclick="javascript:tagshow(event, '%B7%FE%CE%F1%C6%F7');" href="javascript:;" target=_self><u><strong><font color=#0066cc>服务?/font></strong></u></a>。在<a onclick="javascript:tagshow(event, '%CD%F8%C2%E7%D3%CE%CF%B7');" href="javascript:;" target=_self><u><strong><a onclick="javascript:tagshow(event, '%CD%F8%C2%E7');" href="javascript:;" target=_self><u><strong><font color=#0066cc>|络</font></strong></u></a>游戏</strong></u></a>中,服务器所扮演的角色是同步Q广播和服务器主动的一些行为,比如说天气,NPC AI之类的,之所以现在的很多|络游戏服务器都需要负担一些游戏逻辑上的q算是因Zؓ了防止客L?a onclick="javascript:tagshow(event, '%D7%F7%B1%D7');" href="javascript:;" target=_self><u><strong><font color=#0066cc>作弊</font></strong></u></a>行ؓ。了解到q一点,那么本系列的文章分Z部分来谈谈网l游戏服务器?a onclick="javascript:tagshow(event, '%C9%E8%BC%C6');" href="javascript:;" target=_self><u><strong><font color=#0066cc>设计</font></strong></u></a>Q一部分是讲如何做好服务器的|络q接Q同步,q播以及NPC的设|,另一部分则将着重谈谈哪些逻辑攑֜服务器比较合适,q且用什么样的结构来安排q些逻辑?br><br><br><strong>服务器的|络q接</strong><br><br>  大多数的|络游戏的服务器都会选择非阻塞selectq种l构Qؓ什么呢Q因为网l游戏的服务器需要处理的q接非常之多Qƈ且大部分会选择在Linux/Unix下运行,那么为每个用户开一个线E实际上是很不划的Q一斚w因ؓ在Linux/Unix下的U程是用q程q么一个概忉|拟出来的Q比较消?a onclick="javascript:tagshow(event, '%CF%B5%CD%B3');" href="javascript:;" target=_self><u><strong><font color=#0066cc>pȝ</font></strong></u></a>资源Q另外除了I/O之外Q每个线E基本上没有什么多余的需要ƈ行的<a onclick="javascript:tagshow(event, '%C8%CE%CE%F1');" href="javascript:;" target=_self><u><strong><font color=#0066cc>d</font></strong></u></a>Q而且|络游戏是互交性非常强的,所以线E间的同步会成ؓ很麻烦的问题。由此一来,对于q种含有大量|络q接的单U程服务器,用阻塞显然是不现实的。对于网l连接,需要用一个结构来储存Q其中需要包含一个向客户端写消息的缓Ԍq需要一个从客户端读消息的缓Ԍ具体的大根据具体的消息l构来定了。另外对于同步,需要一些时间校对的|q需要一些各U不同的值来记录当前状态,下面l出一个初步的q接的结构:<br><br></font><font face=Fixedsys size=2>typedef connection_s {<br><br>    user_t *ob; /* 指向处理服务器端逻辑的结?*/<br><br>    int fd; /* socketq接 */<br><br>    struct sockaddr_in addr; /* q接的地址信息 */<br><br>    char text[MAX_TEXT]; /* 接收的消息缓?*/<br><br>    int text_end; /* 接收消息~冲的尾指针 */<br><br>    int text_start; /* 接收消息~冲的头指针 */<br><br>    int last_time; /* 上一条消息是什么时候接收到?*/<br><br>    struct timeval latency; /* 客户端本地时间和服务器本地时间的差?*/<br><br>    struct timeval last_confirm_time; /* 上一ơ验证的旉 */<br><br>    short is_confirmed; /* 该连接是否通过验证q?*/<br><br>    int ping_num; /* 该客L到服务器端的ping?*/<br><br>    int ping_ticker; /* 多少个IO周期处理更新一ơping?*/<br><br>    int message_length; /* 发送缓冲消息长?*/<br><br>    char message_buf[MAX_TEXT]; /* 发送缓冲区 */<br><br>    int iflags; /* 该连接的状?*/<br><br>} connection_t;</font><font face=Tahoma><br><br><font size=2>  服务器@环的处理所有连接,是一个死循环q程Q每ơ@环都用select查是否有新连接到达,然后循环所有连接,看哪个连接可以写或者可以读Q就处理该连接的d。由于所有的处理都是非阻塞的Q所以所有的Socket IO都可以用一个线E来完成?br><br>  ׃|络传输的关p,每次recv()到的数据可能不止包含一条消息,或者不C条消息,那么怎么处理呢?所以对于接收消息缓冲用了两个指针,每次接收都从text_start开始读P因ؓ里面D留的可能是上次接收到的多余的半条消息,然后text_end指向消息~冲的结。这L两个指针可以很方便的处理这U情况,另外有一点值得注意的是Q?a onclick="javascript:tagshow(event, '%BD%E2%CE%F6');" href="javascript:;" target=_self><u><strong><font color=#0066cc>解析</font></strong></u></a>消息的过E是一个@环的q程Q可能一ơ接收到两条以上的消息在消息~冲里面Q这个时候就应该执行到消息缓冲里面只有一条都不到的消息ؓ止,大体<a onclick="javascript:tagshow(event, '%C1%F7%B3%CC');" href="javascript:;" target=_self><u><strong><font color=#0066cc>程</font></strong></u></a>如下Q?br><br></font></font><font face=Fixedsys size=2>while ( text_end – text_start > 一条完整的消息长度 )<br><br>{<br><br>    从text_start处开始处?<br><br>    text_start += 该消息长?<br><br>}<br><br>memcpy ( text, text + text_start, text_end – text_start );</font><font face=Tahoma><br><br><font size=2>  对于消息的处理,q里首先需要知道你的游戏d有哪些消息,所有的消息都有哪些Q才能设计出比较合理的消息头。一般来_消息大概可分Z角消息,场景消息Q同步消息和界面消息四个部分。其中主角消息包括客L所控制的角色的所有动作,包括走\Q跑步,战斗之类的。场景消息包括天气变化,一定的旉在场景里出现一些东西等{之cȝQ这cL息的特点是所有消息的发v者都是服务器Q广播对象则是场景里的所有玩家。而同步消息则是针对发起对象是某个玩家Q经q服务器q播l所有看得见他的玩家Q该消息也是包括所有的动作Q和主角消息不同的是该种消息是服务器q播l客L的,而主角消息一般是客户端主动发l服务器的。最后是界面消息Q界面消息包括是服务器发l客L的聊天消息和各种<a onclick="javascript:tagshow(event, '%CA%F4%D0%D4');" href="javascript:;" target=_self><u><strong><font color=#0066cc>属?/font></strong></u></a>及状态信息?br><br>  下面来谈谈消息的l成。一般来_一个消息由消息头和消息体两部分l成Q其中消息头的长度是不变的,而消息体的长度是可变的,在消息体中需要保存消息体的长度。由于要l每条消息一个很明显的区分,所以需要定义一个消息头Ҏ的标志,然后需要消息的cd以及消息ID。消息头大体l构如下Q?br><br></font></font><font face=Fixedsys size=2>type struct message_s {<br><br>    unsigned short message_sign;<br><br>    unsigned char message_type;<br><br>    unsigned short message_id<br><br>    unsigned char message_len<br><br>}message_t;<br><br></font><font face=Tahoma><br><font size=2><strong>服务器的q播</strong><br><br>  服务器的q播的重点就在于如何计算出广播的对象。很昄Q在一张很大的地图里面Q某个玩家在最东边的一个动作,一个在最西边的玩家是应该看不到的Q那么怎么来计广播的对象呢?最单的<a onclick="javascript:tagshow(event, '%B0%EC%B7%A8');" href="javascript:;" target=_self><u><strong><font color=#0066cc>办法</font></strong></u></a>Q就是把地图分块Q分成大合适的块Q然后每ơ只象周围几个小块的玩家q行q播。那么究竟切到多大比较合适呢Q一般来_切得块大了,<a onclick="javascript:tagshow(event, '%C4%DA%B4%E6');" href="javascript:;" target=_self><u><strong><font color=#0066cc>内存</font></strong></u></a>的消耗会增大Q切得块了QCPU的消耗会增大Q原因会在后面提刎ͼ。个得切成一屏左右的块比较合适,每次q播q播周围九个块的玩Ӟ׃q播的操作非帔RJ,那么遍利周围九块的操作就会变得相当的频繁Q所以如果块分得了Q那么遍利的范围׃扩大QCPU的资源会很快的被吃完?br><br>  切好块以后,怎么让玩家在各个块之间走来走dQ让我们来想惛_切换一ơ块的时候要做哪些工作。首先,要算Z个块的周围九块的玩家有哪些是现在当前块没有的Q把自己的信息广播给那些玩家Q同时也要算Z个块周围九块里面有哪些物件是现在没有的,把那些物件的信息q播l自己,然后把下个块的周围九快里没有的,而现在的块周围九块里面有的物件的消失信息q播l自己,同时也把自己消失的消息广播给那些物g。这个操作不仅烦琐而且会吃掉不CPU资源Q那么有什么办法可以很快的出q些物g呢?一个个做比较?昄看v来就不是个好办法Q这里可以参照二l矩늢撞检的一些思\Q以自己周围九块Z个矩阵,目标块周围九块ؓ另一个矩阵,这两个矩阵是否撞Q如果两个矩늛交,那么没相交的那些块怎么。这里可以把怺的块的坐标{换成内部坐标Q然后再q行q算?br><br>  对于q播q有另外一U?a onclick="javascript:tagshow(event, '%BD%E2%BE%F6');" href="javascript:;" target=_self><u><strong><font color=#0066cc>解决</font></strong></u></a>ҎQ实施v来不如切块来的简单,q种Ҏ需要客L来协助进行运。首先在服务器端的连接结构里面需要增加一个广播对象的队列Q该队列在客L登陆服务器的时候由服务器传l客LQ然后客L自己来维护这个队列,当有出客L视野的时候,由客Ld要求服务器给那个物g发送消q消息。而对于有人总进视野的情况,则比较麻烦了?br><br>  首先需要客L在每ơ给服务器发送update position的消息的时候,服务器都l该q接出一个视野范_然后在需要广播的时候,循环整张地图上的玩家Q找到坐标在其视野范围内的玩家。用这U方法的好处在于不存在{换块的时候需要一ơ性广播大量的消息Q缺点就是在计算q播对象的时候需要遍历整个地图上的玩Ӟ如果当一个地图上的玩家多得比较离q时候,该操作就会比较的慢?br><br><br><strong>服务器的同步</strong><br><br>  同步在网l游戏中是非帔R要的Q它保证了每个玩家在屏幕上看到的东西大体是一L。其实呢Q解军_步问题的最单的Ҏ是把每个玩家的动作都向其他玩家q播一遍,q里其实存在两个问题:1Q向哪些玩家q播Q广播哪些消息?Q如果网lgq怎么办。事实上呢,W一个问题是个非常简单的问题Q不q之所以我提出q个问题来,是提醒大家在设计自己的消息结构的时候,需要把q个因素考虑q去。而对于第二个问题Q则是一个挺ȝ的问题,大家可以来看q么个例子:<br><br>  比如有一个玩家A向服务器发了?a onclick="javascript:tagshow(event, '%D6%B8%C1%EE');" href="javascript:;" target=_self><u><strong><font color=#0066cc>指o</font></strong></u></a>Q说我现在在P1点,要去P2炏V指令发出的旉是T0Q服务器收到指o的时间是T1Q然后向周围的玩家广播这条消息,消息的内Ҏ“玩家A从P1到P2”有一个在A附近的玩家BQ收到服务器的这则广播的消息的时间是T2Q然后开始在客户端上dQA从P1到P2炏V这个时候就存在一个不同步的问题,玩家A和玩家B的屏q上昄的画面相差了T2-T1的时间。这个时候怎么办呢Q?br><br><br><br>  有个解决ҎQ我l它取名?预测拉扯Q虽然有些怪异了点Q不q基本上大家也能从字面上来理解它的意思。要解决q个问题Q首先要定义一个值叫Q预误差。然后需要在服务器端每个玩家q接的类里面加一属性,叫latencyQ然后在玩家登陆的时候,对客L的时间和服务器的旉q行比较Q得出来的差g存在latency里面。还是上面的那个例子Q服务器q播消息的时候,根据要q播对象的latencyQ计出一个客L的CurrentTimeQ然后在消息头里面包含这个CurrentTimeQ然后再q行q播。ƈ且同时在玩家A的客L本地建立一个队列,保存该条消息Q只到获得服务器验证׃未被验证的消息队列里面将该消息删除,如果验证p|Q则会被拉扯回P1炏V然后当玩家B收到了服务器发过来的消息“玩家A从P1到P2”q个时候就查消息里面服务器发出的时间和本地旉做比较,如果大于定义的预误差,q出在T2q个旉Q玩家A的屏q上走到的地点P3Q然后把玩家B屏幕上的玩家A直接拉扯到P3Q再l箋C去,q样p保证同步。更q一步,Z保证客户端运行v来更加smoothQ我q不推荐直接把玩家拉扯过去,而是出P3偏后的一点P4Q然后用(P4-P1)/T(P4-P3)来算Z个很快的速度SQ然后让玩家A用速度S快速移动到P4Q这L处理Ҏ是比较合理的Q这U解x案的原Ş在国际上被称为(Full plesiochronousQ,当然Q该原Ş被我改了很多来适应|络游戏的同步,所以而变成所谓的Q预拉扯?br><br>  另外一个解x案,我给它取名叫 验证同步Q听名字也知道,大体的意思就是每条指令在l过服务器验证通过了以后再执行动作。具体的思\如下Q首先也需要在每个玩家q接cd里面定义一个latencyQ然后在客户端响应玩安标行走的同时Q客Lq不会先行走动,而是发一条走路的指ol服务器Q然后等待服务器的验证。服务器接受到这条消息以后,q行逻辑层的验证Q然后计出需要广播的范围Q包括玩家A在内Q根据各个客L不同的latency生成不同的消息头Q开始广播,q个时候这个玩家的走\信息是完全同步的了。这个方法的优点是能保证各个客户端之间绝对的同步Q缺Ҏ当网lgq比较大的时候,玩家的客L的行Z变得比较不流畅,l玩家带来很不爽的感觉。该U解x案的原Ş在国际上被称为(Hierarchical master-slave synchronizationQ,80q代以后被广泛应用于|络的各个领域?br><br>  最后一U解x案是一U理惛_的解x案,在国际上被称为Mutual synchronizationQ是一U对未来|络的前景的良好预测出来的解x案。这里之所以要提这个方案,q不是说我们已经完全的实Cq种ҎQ而只是在|络游戏领域的某些方面应用到q种Ҏ的某些思想。我对该U方案取名ؓQ半服务器同步。大体的设计思\如下Q?br><br>  首先客户端需要在登陆世界的时候徏立很多张q播列表Q这些列表在客户端后台和服务器要q行不及时同步,之所以要建立多张列表Q是因ؓ要广播的cd是不止一U的Q比如说有local message,有remote message,q有global message {等Q这些列表都需要在客户端登陆的时候根据服务器发过来的消息建立好。在建立列表的同Ӟq需要获得每个列表中q播对象的latencyQƈ且要l护一张完整的用户状态列表在后台Q也是不及时的和服务器进行同步,Ҏ本地的用L态表Q可以做C部分决策由客L自己来决定,当客L发送这部分决策的时候,则直接将最l决{发送到各个q播列表里面的客LQƈ对其旉q行校对Q保证每个客L在收到的消息的时间是和根据本地时间进行校对过的。那么再采用预测拉扯中提到过的计提前量Q提高速度行走q去的方法,会使同步变得非常的smooth。该Ҏ的优Ҏ不通过服务器,客户端自׃间进行同步,大大的降低了׃|络延迟而带来的误差Qƈ且由于大部分决策都可以由客户端来做,也大大的降低了服务器的资源。由此带来的弊端是׃消息和决{权都放在客L本地Q所以给外挂提供了很大的可乘之机?/font></font></p> <p><font face=Tahoma size=2> 下面我想来谈谈关于服务器上NPC的设计以及NPC{一些方面涉及到的问题。首先,我们需要知道什么是NPCQNPC需要做什么。NPC的全U是QNon-Player CharacterQ,很显Ӟ他是一个characterQ但不是玩家Q那么从q点上可以知道,NPC的某些行为是和玩家类似的Q他可以行走Q可以战斗,可以呼吸Q这点将在后面的NPC里面提到Q,另外一点和玩家物g不同的是QNPC可以复生Q即NPC被打M后在一定时间内可以重新出来Q。其实还有最重要的一点,是玩家物g的所有决{都是玩家做出来的,而NPC的决{则是由计算机做出来的,所以在对NPC做何U决{的时候,需要所谓的NPC来进行决{?br><br>  下面我将分两个部分来谈谈NPCQ首先是NPCQ其ơ是服务器如何对NPCq行l织。之所以要先谈NPC是因为只有当我们了解清楚我们需要NPC做什么之后,才好开始设计服务器来对NPCq行l织?br><br><br><strong>NPC</strong><br><br>  NPC分ؓ两种Q一U是被动触发的事Ӟ一U是d触发的事件。对于被动触发的事gQ处理v来相Ҏ说简单一些,可以׃件本w来呼叫NPCw上的函敎ͼ比如说NPC的死亡,实际上是在NPC的HP于一定值的时候,来主动呼叫NPCw上的OnDie() 函数Q这U由事g来触发NPC行ؓ的NPCQ我UCؓ被动触发。这U类型的触发往往分ؓ两种Q?br><br>一U是由别的物件导致的NPC的属性变化,然后属性变化的同时会导致NPC产生一些行为。由此一来,NPC物g里面臛_包含以下几种函数Q?br><br></font><font face=Fixedsys size=2>class NPC {<br><br>public:<br><br>    // 是谁在什么地方导致了我哪属性改变了多少?br><br>    OnChangeAttribute(object_t *who, int which, int how, int where);<br><br>Private:<br><br>    OnDie();<br><br>    OnEscape();<br><br>    OnFollow();<br><br>    OnSleep();<br><br>    // 一pd的事件?br><br>}<br></font><font face=Tahoma><br><font size=2>  q是一个基本的NPC的结构,q种被动的触发NPC的事Ӟ我称它ؓNPC的反。但是,q样的结构只能让NPC被动的接收一些信息来做出决策Q这LNPC是愚蠢的。那么,怎么栯一个NPC能够d的做Z些决{呢Q这里有一U方法:呼吸。那么怎么栯NPC有呼吸呢Q?br><br>  一U很单的ҎQ用一个计时器Q定时的触发所有NPC的呼吸,q样可以让一个NPC有呼吸v来。这L话会有一个问题,当NPC太多的时候,上一ơNPC的呼吸还没有呼吸完,下一ơ呼吸又来了Q那么怎么解决q个问题呢。这里有一U方法,让NPC异步的进行呼吸,x个NPC的呼吸周期是ҎNPC出生的时间来定的Q这个时候计时器需要做的就是隔一D|间检查一下,哪些NPC到时间该呼吸了,来触发q些NPC的呼吸?br><br>  上面提到的是pȝ如何来触发NPC的呼吸,那么NPC本n的呼吔R率该如何讑֮呢?q个好象现实中的h一P睡觉的时候和q行Ȁ烈运动的时候,呼吸频率是不一L。同PNPC在战斗的时候,和^常的时候,呼吸频率也不一栗那么就需要一个Breath_Ticker来设|NPC当前的呼吔R率?br><br>  那么在NPC的呼怺仉面,我们怎么h讄NPC的智能呢Q大体可以概括ؓ查环境和做出决策两个部分。首先,需要对当前环境q行数字上的l计Q比如说是否在战斗中Q战斗有几个敌hQ自qHPq剩多少Q以及附q有没有敌h{等之类的统计。统计出来的数据传入本n的决{模块,决策模块则根据NPC自n的性格取向来做Z些决{,比如说野蛮型的NPC会在HP比较的时候仍然猛扑猛打,又比如说智慧型的NPC则会在HP比较的时候选择逃跑。等{之cȝ?br><br>  xQ一个可以呼吸,反射的NPC的结构已l基本构成了Q那么接下来我们来谈谈pȝ如何l织让一个NPC出现在世界里面?br><br><br><strong>NPC的组l?/strong><br><br>  q里有两U方案可供选择Q其一QNPC的位|信息保存在场景里面Q蝲入场景的时候蝲入NPC。其二,NPC的位|信息保存在NPCw上Q有专门的事件让所有的NPC登陆场景。这两种Ҏ有什么区别呢Q又各有什么好坏呢Q?br><br>  前一U方法好处在于场景蝲入的时候同时蝲入了NPCQ场景就可以对NPCq行理Q不需要多余的处理Q而弊端则在于在刷新的时候是同步h的,也就是说一个场景里面的NPC可能会在同一旉内长出来。而对于第二种Ҏ呢,设计h会稍微麻烦一些,需要一个统一的机制让NPC登陆到场景,q需要一些比较麻烦的设计Q但是这U方案可以实现NPC异步的刷斎ͼ是目前网l游戏普遍采用的ҎQ下面我们就来着重谈谈这U方法的实现Q?br><br>  首先我们要引入一?#8220;灵魂”的概念,即一个NPC在死后,消失的只是他的肉体,他的灵魂仍然在世界中存在着Q没有呼吸,在死亡的附近漂QQ等着到时间投胎,投胎的时候把之前的所有属性清Ӟ重新在场景上构徏其肉体。那么,我们怎么来设计这样一个结构呢Q首先把一个场景里面要出现的NPC制作成图量表Q给每个NPC一个独一无二的标识符Q在载入场景之后Q根据图量表来蝲入属于该场景的NPC。在NPC的OnDie() 事g里面不直接把该物件destroy 掉,而是关闭NPC的呼吸,然后打开一个重生的计时器,最后把该物件设|ؓinvisable。这L设计Q可以实现NPC的异步刷斎ͼ在节省服务器资源的同时也让玩家觉得更加的真实?br><br>Q这一章节已经牉|C些服务器脚本相关的东西,所以下一章节谈谈服务器脚本相关的一些设计)<br><br>补充的谈谈启发式搜烦Qheuristic searchingQ在NPC中的应用?br><br>  其主要思\是在q度优先搜烦的同Ӟ下一层的所有节点经q一个启发函数进行过滤,一定范围内~小搜烦范围。众所周知的寻路A*法是典型的启发式搜烦的应用,其原理是一开始设计一个Judge(point_t* point)函数Q来获得pointq个一点的代hQ然后每ơ搜索的时候把下一步可能到辄所有点都经qJudge()函数评h一下,获取两到三个代h比较的点,l箋搜烦Q那些没被选上的点׃会在l箋搜烦下去了,q样带来的后果的是可能求出来的不是最优\径,q也是ؓ什么A*法在寻路的时候会走到障碍物前面再l过去,而不是预先就走斜U来l过该障物。如果要d最优化的\径的话,是不能用A*法的,而是要用动态规划的ҎQ其消耗是q大于A*的?br><br>  那么Q除了在寻\之外Q还有哪些地方可以应用到启发式搜索呢Q其实说得大一点,NPC的Q何决{都可以用启发式搜烦来做Q比如说逃跑吧,如果是一?D的网l游戏,有八个方向,NPC选择哪个方向逃跑呢?可以设|一个Judge(int direction)来给定每个点的代P在Judge里面上该点的敌人的强弱Q或者该敌h的敏捷如何等{,最后选择代h最的地方逃跑。下面,我们来谈谈对于几种NPC常见的智能的启发式搜索法的设计:<br><br>Target select Q选择目标Q:<br><br>  首先获得地图上离该NPC附近的敌人列表。设计Judge() 函数Q根据敌人的强弱Q敌人的q近Q算Z仗然后选择代h最的敌hq行dd?br><br>EscapeQ逃跑Q:<br><br>  在呼怺仉面检查自qHPQ如果HP低于某个值的时候,或者如果你是远E兵U,而敌w的话,则触发逃跑函数Q在逃跑函数里面也是对周围的所有的敌hl织成列表,然后设计Judge() 函数Q先选择出对你构成威胁最大的敌hQ该Judge() 函数需要判断敌人的速度Q战斗力强弱Q最后得Z个主要敌人,然后针对该主要敌行\径的Judge() 的函数的设计Q搜索的范围只可能是和主要敌人相反的方向Q然后再Ҏ该几个方向的敌h的强弱来计算代hQ做出最后的选择?br><br>Random walkQ随路)Q?br><br>  q个我ƈ不推荐用A*法Q因为NPC一旦多hQ那么这个对CPU的消耗是很恐怖的Q而且NPC大多不需要长距离的寻路,只需要在附近走走卛_Q那么,在附近随机的给几个点,然后让NPC走过去,如果到障碍物就停下来,q样几乎无Q何负担?br><br>Follow TargetQ追随目标)Q?br><br>  q里有两U方法,一U方法NPC看上L较愚蠢,一U方法看上去NPC比较聪明Q第一U方法就是让NPC跟着目标的\点走卛_Q几乎没有资源消耗。而后一U则是让NPC在跟随的时候,在呼怺仉面判断对方的当前位置Q然后走直线Q碰上障物了用A*l过去,该种设计会消耗一定量的系l资源,所以不推荐NPC大量的追随目标,如果需要大量的NPCq随目标的话Q还有一个比较简单的ҎQ让NPC和目标同步移动,卌他们的速度l一Q移动的时候走同样的\点,当然Q这U设计只适合NPC所跟随的目标不是追杀的关p,只是跟随着玩家走而已了?/font></font></p> <p><font size=2> 在这一章节Q我惌谈关于服务器端的脚本的相兌计。因为在上一章节里面Q谈NPC相关的时候已l接触到一些脚本相关的东东了。还是先来谈谈脚本的作用吧?br>  在基于编译的服务器端E序中,是无法在E序的运行过E中构徏一些东西的Q那么这个时候就需要脚本语a的支持了Q由于脚本语a涉及到逻辑判断Q所以光提供一些函数接口是没用的,q需要提供一些简单的语法和文法解析的功能。其实说到底QQ何的事g都可以看成两个部分:W一是对自nQ或者别的物件的数值的改变Q另外一个就是将该事件以文字或者图形的方式q播出去。那么,q里牉|C个很重要的话题,是Ҏ一物gq行d。恩Q谈到这Q我惛_本章节分Z个部分来谈,首先是服务器如何来管理动态创建出来的物gQ服务器内存理Q,W二是如何对某一物gq行dQ第三则是脚本语a的组l和解释。其实之所以到W四章再来谈服务器的内存理是因为在前几章谈q个的话Q大家对其没有一个感性的认识Q可能不知道服务器的内存理I竟有什么用?br><br><strong>4.1、服务器内存理</strong><br>  对于服务器内存管理我们将采用内存池的ҎQ也UCؓ静态内存管理。其概念为在服务器初始化的时候,甌一块非常大的内存,UCؓ内存池(<span id="4iamew4" class=English>Memory pool</span>Q,同时也申请一块内存I间Q称为垃圑֛收站Q?span class=English>Garbage recollecting station</span>Q。其大体思\如下Q当E序需要申请内存的时候,首先查垃圑֛收站是否为空Q如果不为空的话Q则从垃圑֛收站中找一块可用的内存地址Q在内存池中Ҏ地址扑ֈ相应的空_分配l程序用Q如果垃圑֛收站是空的话Q则直接从内存池的当前指针位|申请一块内存;当程序释攄间的时候,l那块内存打上已l释放掉的标讎ͼ然后把那块内存的地址攑օ垃圾回收站?br>  下面具体谈谈该方法的详细设计Q首先,我们采用类g操作pȝ的段式pȝ来管理内存,q样的好处是可以充分的利用内存池Q其~点是管理v来比较麻烦。嗯Q下面来具体看看我们怎么h定义和D늚l构Q?br><br></font><font face=Fixedsys>  <span id="mwygyu4" class=ColorCode>typedef struct m_segment_s<br>  {<br>    struct m_segment_s *next;</span> <font color=#808080><span id="4kqw4e4" class=ColorCatchword>/* 双线链表 + 静态内存可以达到随问和序讉K的目的,<br>                   真正的想怎么讉KQ就怎么讉K?*/</span></font><br>    <span id="68au4qk" class=ColorCode>struct m_segment_s *pre; int flags;</span>  <span id="ieu84yc" class=ColorCatchword><font color=#808080>// 该段的一些标记?/font><br></span>    <span id="4usk844" class=ColorCode>int start;</span>              <span id="cmsw846" class=ColorCatchword><font color=#808080>// 相对于该늚首地址?/font><br></span>    <span id="e4wa6wg" class=ColorCode>int size;</span>               <font color=#808080><span id="k6ycakw" class=ColorCatchword>// 长度?/span></font><br>    <span id="qs4484u" class=ColorCode>struct m_page_s *my_owner;</span>      <font color=#808080><span id="aoiu8qy" class=ColorCatchword>// 我是属于哪一늚?/span></font><br>    <span id="s4oigmw" class=ColorCode>char *data;</span>              <font color=#808080><span id="i4se4m4" class=ColorCatchword>// 内容指针?/span></font><br>  <span id="cq8oksi" class=ColorCode>}m_segment_t;<br><br>  typedef struct m_page_s<br>  {<br>    unsigned int flags;</span>   <span id="qoasq68" class=ColorCatchword><font color=#808080>/* 使用标记Q是否完全用,是否q有IZ */</font><br></span>    <span id="y8coe4i" class=ColorCode>int size;</span>        <font color=#808080><span id="cqcowqy" class=ColorCatchword>/* 该页的大,一般都是统一的,最后一除?*/</span></font><br>    <span id="wkq8ag8" class=ColorCode>int end;</span>         <span id="444qm4s" class=ColorCatchword><font color=#808080>/* 使用C么地方了 */</font><br></span>    <span id="4ak4oys" class=ColorCode>int my_index;</span>      <span id="48k4m4c" class=ColorCatchword><font color=#808080>/* 提供随机讉K的烦?*/</font><br></span>    <span id="86u6cyy" class=ColorCode>m_segment_t *segments;</span>  <span id="4e4i44m" class=ColorCatchword><font color=#808080>// 内D늚头指针?/font><br></span> <span id="so8yo8g" class=ColorCode> }m_page_t;<br></span></font><font face=Arial size=2><br>  那么内存池和垃圾回收站怎么构徏呢?下面也给Z些构建相关的?a onclick="javascript:tagshow(event, '%B4%FA%C2%EB');" href="javascript:;" target=_self><u><strong><font color=#0066cc>代码</font></strong></u></a>Q?br><br></font><font face=Fixedsys>  <span id="6k44ume" class=ColorCode>static m_page_t *all_pages;<br></span>  <span id="4w4swag" class=ColorCatchword><font color=#808080>// total_size是d要申L内存敎ͼnum_pages是d打算创徏多少个页面?/font><br></span>  <span id="uwcsyii" class=ColorCode>void initialize_memory_pool( int total_size, int num_pages )<br>  {<br>    int i, page_size, last_size;</span>    <span id="4si44i4" class=ColorCatchword><font color=#808080>// 出每个面的大?/font><br></span>    <span id="uyms844" class=ColorCode>page_size = total_size / num_pages;</span> <span id="ayui84g" class=ColorCatchword><font color=#808080>// 分配_的页面?/font><br></span>    <span id="4yscaka" class=ColorCode>all_pages = (m_page_t*) calloc( num_pages, sizeof(m_page_t*) );<br>    for ( i = 0; i < num_pages; i ++ )<br>    {<br></span>      <span id="kegc8ci" class=ColorCatchword><font color=#808080>// 初始化每个页面的D|针?/font><br></span>      <span id="m44ck4i" class=ColorCode>all_pages[i].m_segment_t = (m_segment_t*) malloc( page_size );<br></span>      <span id="iwege4g" class=ColorCatchword><font color=#808080>// 初始化该面的标记?/font><br></span>      <span id="eoa844k" class=ColorCode>all_pages[i].flags |= NEVER_USED;<br></span>      <span id="4wg4aqg" class=ColorCatchword><font color=#808080>// 除了最后一个页面,其他的大都是page_size 大小?/font><br></span>      <span id="ac8g8qi" class=ColorCode>all_pages[i].size = page_size;<br></span>      <span id="c8ywies" class=ColorCatchword><font color=#808080>// 初始化随问的索引?/font><br></span>      <span id="sue44es" class=ColorCode>all_pages[i].my_index = i;<br></span>      <span id="8iu8q4m" class=ColorCatchword><font color=#808080>// ׃没有用过Q所以大都?</font><br></span>      <span id="i44ec4i" class=ColorCode>all_pages[i].end = 0;<br>    }<br></span><br>    <font color=#808080><span id="a4wqkks" class=ColorCatchword>// 讄最后一个页面的大小?/span></font><br>    <span id="8864g4k" class=ColorCode>if ( (last_size = total_size % num_pages) != 0 )<br>      all_pages[i].size = last_size;<br>  }<br></span></font><font face=Arial size=2><br>  下面看看垃圾回收站怎么设计Q?br><br></font><font face=Fixedsys>  <span id="qqcy8io" class=ColorCode>int **garbage_station;<br>  void init_garbage_station( int num_pages, int page_size )<br>  {<br>    int i;<br>    garbage_station = (int**) calloc( num_pages, sizeof( int* ) );<br>    for ( i = 0; i < num_pages; i ++)<br>    {<br></span>      <span id="uw484ic" class=ColorCatchword><font color=#808080>// q里用unsigned short的高8位来储存首相对地址Q低8位来储存长度?/font><br></span>      </font><span id="w4iei84" class=ColorCode><font face=Fixedsys>garbage_station[i] = (int*) calloc( page_size, sizeof( unsigned short ));<br>      memset( garbage_station[i], 0, sizeof( garbage_station[i] ));<br>    }<br>  }</font><font face=Arial><br></font></span><font face=Arial size=2><br>  也许q样的脓代码会让大家觉得很不明白Q嗯Q我的代码水q确实不怎么P那么下面我来用文字方式来叙说一下大体的概念吧。对于段式内存理Q首先分成N个页面,q个是固定的Q而对于每个页面内的段则是动态的Q段的大事先是不知道的Q那么我们需要回收的不仅仅是面的内存,q包括段的内存,那么我们需要一个二l数l来保存是哪个页面的那块D늚地址被释放了。然后对于申请内存的时候,则首先检查需要申请内存的大小Q如果不够一个页面大的话,则在垃圾回收站里面寻扑֏用的D늩间分配,如果找不刎ͼ则申请一个新的页面空间?br>  q样用内存池的方法来理整个游戏世界的内存可以有效的减少内存片Q一定程度的提高游戏q行的稳定性和效率?br><br><strong>4.2、游戏中物g的寻址<br></strong>  W一个问题,我们Z么要dQ加入了脚本语言的概念之后,游戏中的一些逻辑物gQ比如说NPCQ某个ITEM之类的都是由脚本语言在游戏运行的q程中动态生成的Q那么我们通过什么样的方法来对这些物件进行烦引呢Q说得简单一点,是如何扑ֈ他们呢?有个很简单的ҎQ全部遍历一ơ。当Ӟq是个简单而有效的ҎQ但是效率上的消耗是M一台服务器都吃不消的,特别是在游戏的规模比较大之后?br>  那么Q我们怎么来在游戏世界中很快的Lq些物g呢?我想在谈q个之前Q说一下Hash Tableq个数据l构Q它叫哈希表Q也有h叫它散列表,其工作原理是不是序讉KQ也不是随机讉KQ而是通过一个散列函数对其keyq行计算Q算出在内存中这个key对应的value的地址Q而对其进行访问。好处是不管面对多大的数据,只需要一ơ计就能找到其地址Q非常的快捷Q那么弊端是什么呢Q当两个key通过散列函数计算出来的地址是同一个地址的时候,ȝ来了,会生碰撞,其的解决Ҏ非常的麻烦,q里׃详细谈其解决Ҏ了,否则估计再写个四Q五章也未必谈得清楚Q不q如果大家对其感兴趣的话Q欢q讨论?br>  嗯,我们用散列表来Ҏ戏中的物件进行烦引,具体怎么做呢Q首先,在内存池中申请一块两倍大于游戏中物gL的内存,Z么是两倍大呢?防止散列表碰撞。然后我们选用物g的名UC为散列表的烦引keyQ然后就可以开始设计散列函C。下面来看个例子Q?br><br></font></font><font face=Fixedsys size=2>  <span id="4844yi4" class=ColorCode>static int T[] =<br>  {<br>    1, 87, 49, 12, 176, 178, 102, 166, 121, 193, 6, 84, 249, 230, 44, 163,<br>    14, 197, 213, 181, 161, 85, 218, 80, 64, 239, 24, 226, 236, 142, 38, 200,<br>    110, 177, 104, 103, 141, 253, 255, 50, 77, 101, 81, 18, 45, 96, 31, 222,<br>    25, 107, 190, 70, 86, 237, 240, 34, 72, 242, 20, 214, 244, 227, 149, 235,<br>    97, 234, 57, 22, 60, 250, 82, 175, 208, 5, 127, 199, 111, 62, 135, 248,<br>    174, 169, 211, 58, 66, 154, 106, 195, 245, 171, 17, 187, 182, 179, 0, 243,<br>    132, 56, 148, 75, 128, 133, 158, 100, 130, 126, 91, 13, 153, 246, 216, 219,<br>    119, 68, 223, 78, 83, 88, 201, 99, 122, 11, 92, 32, 136, 114, 52, 10,<br>    138, 30, 48, 183, 156, 35, 61, 26, 143, 74, 251, 94, 129, 162, 63, 152,<br>    170, 7, 115, 167, 241, 206, 3, 150, 55, 59, 151, 220, 90, 53, 23, 131,<br>    125, 173, 15, 238, 79, 95, 89, 16, 105, 137, 225, 224, 217, 160, 37, 123,<br>    118, 73, 2, 157, 46, 116, 9, 145, 134, 228, 207, 212, 202, 215, 69, 229,<br>    27, 188, 67, 124, 168, 252, 42, 4, 29, 108, 21, 247, 19, 205, 39, 203,<br>    233, 40, 186, 147, 198, 192, 155, 33, 164, 191, 98, 204, 165, 180, 117, 76,<br>    140, 36, 210, 172, 41, 54, 159, 8, 185, 232, 113, 196, 231, 47, 146, 120,<br>    51, 65, 28, 144, 254, 221, 93, 189, 194, 139, 112, 43, 71, 109, 184, 209,<br>  };<br><br></span>  <span id="u4844s4" class=ColorCatchword><font color=#808080>// s是需要进行烦引的字符串指针,maxn是字W串可能的最大长度,q回值是相对地址?/font><br></span>  </font><span id="kcessme" class=ColorCode><font face=Fixedsys size=2>inline int whashstr(char *s, int maxn)<br>  {<br>    register unsigned char oh, h;<br>    register unsigned char *p;<br>    register int i;<br><br>    if (!*s)<br>      return 0;<br>    p = (unsigned char *) s;<br>    oh = T[*p]; h = (*(p++) + 1) & 0xff;<br>    for (i = maxn - 1; *p && --i >= 0; )<br>    {<br>      oh = T[oh ^ *p]; h = T[h ^ *(p++)];<br>    }<br>    return (oh << 8) + h;<br>  }</font><font face=Arial size=2><br></font></span><font face=Arial><font face=Arial size=2><br>  具体的算法就不说了,上面的那一大段东西不要问我Z么,q个法的出处是CACM 33-6中的一个叫Peter K.Pearson的鬼子写的论文中介绍的算法,据说速度非常的快。有了这个散列函敎ͼ我们可以通过它来对世界里面的L物gq行非常快的d了?br><br><strong>4.3、脚本语a解释</strong><br>  在设计脚本语a之前Q我们首先需要明白,我们的脚本语a要实C么样的功能?否则随心所Ʋ的做下dZC的解释器之类的也说不定。我们要实现的功能只是简单的逻辑判断和@环,其他所有的功能都可以由事先提供好的函数来完成。嗯Q这h们就可以列出一张工作量的表单:设计物g在底层的保存l构Q提供脚本和底层间的讉K接口Q设计支持逻辑判断和@环的解释器?br>  下面先来谈谈物g在底层的保存l构。具体到每种不同属性的物gQ需要采用不同的l构Q当Ӟ如果你愿意的话,你可以所有的物g都采同同Ll构Q然后在l构里面设计一个散列表来保存各U不同的属性。但qƈ不是一个好ҎQ过分的依赖散列表会让你的游戏的逻辑变得J杂不清。所以,量的区分每U不同的物g采用不同的结构来设计。但是有一点值得注意的是Q不是什么结构,有一些东西是l一的,是我们所说的物g_那么我们怎么来设计这样一个物件头呢?<br><br></font></font><font face=Fixedsys size=2>  </font><span id="q8w4ik4" class=ColorCode><font face=Fixedsys size=2>typedef struct object_head_s<br>  {<br>    char* name;<br>    char* prog;<br>  }object_head_t;</font><font face=Arial size=2><br></font></span><font face=Arial><font face=Arial size=2><br>  其中name是在散列表中q个物g的烦引号Qprog则是脚本解释器需要解释的E序内容。下面我们就以NPCZ来设计一个结构:<br><br></font></font><font face=Fixedsys size=2>  <span id="4w4ymow" class=ColorCode>typedef struct npc_s<br>  {<br>    object_head_t header;</span>    <span id="um8ws4i" class=ColorCatchword><font color=#808080>// 物g?/font><br></span>    <span id="cewqe8q" class=ColorCode>int hp;</span>           <span id="8io8a66" class=ColorCatchword><font color=#808080>// NPC的hp倹{?/font><br></span>    <span id="4gs444s" class=ColorCode>int level;</span>          <span id="yy44q4i" class=ColorCatchword><font color=#808080>// NPC的等U?/font><br></span>    <span id="4k6kg84" class=ColorCode>struct position_s position;</span> <span id="c4c6ew8" class=ColorCatchword><font color=#808080>// 当前的位|信息?/font><br></span>    <span id="qquo4eu" class=ColorCode>unsigned int personality;</span>  <font color=#808080><span id="umq8mwm" class=ColorCatchword>// NPC的个性,一个unsigned int可以保存24U个性?/span></font><br>  <span id="oic8uue" class=ColorCode>}npc_t;</span></font><font face=Arial><font face=Arial size=2><br><br>  OKQ结构设计完成,那么我们怎么来设计脚本解释器呢?q里有两U法Q一U是用虚拟机的模式来解析脚本语言Q另外一中则是用cM汇编语言的那U结构来设计Q设|一些条件蟩转和循环可以实现逻辑判断和@环了Q比如:<br><br></font></font><font face=Fixedsys size=2>  <span id="84644qg" class=ColorCode>set name, "路h?;<br>  CHOOSE: random_choose_personality;</span>  <span id="ys8w4mu" class=ColorCatchword><font color=#808080>// 随机选择NPC的个?/font><br></span>  <span id="e4c88gu" class=ColorCode>compare hp, 100;</span>           <span id="8o44ewy" class=ColorCatchword><font color=#808080>// 比较气血Q比较出的值可以放在一个固定的变量里面</font><br></span>  <span id="g644k4q" class=ColorCode>ifless LESS;</span>             <span id="84u44gm" class=ColorCatchword><font color=#808080>// hp < 100的话Q则q回?/font><br></span>  <span id="4q8icw4" class=ColorCode>jump CHOOSE;</span>             <font color=#808080><span id="aayu88g" class=ColorCatchword>// 否则l箋选择Q只到选到一个hp < 100的?/span></font><br> </font><span id="4g4m44k" class=ColorCode><font face=Fixedsys size=2> LESS: return success;</font><font face=Arial size=2><br></font></span><font face=Arial><font face=Arial size=2><br>  q种脚本l构q似CPU的指令的l构Q一条一条指令按照顺序执行,对于脚本E序员(<span id="mm4gqk4" class=English>Script. Programmer</span>Q也可以培养他们汇编能力的说?br>  那么怎么来模仿这U结构呢Q我们拿CPU的指令做参照Q首先得讄一些寄存器QCPU的寄存器的大和数量是受g影响的,但我们是用内存来模拟寄存器,所以想要多大,可以有多大。然后提供一些指令,包括四则q算Q寻址Q判断,循环{等。接下来针对不同的脚本用不同的解析方法,比如说对NPCqNPC固定的脚本,对ITEMqITEM固定的脚本,解析完以后就把结果生成底层该物g的结构用于用?br>  而如果要用虚拟机来实现脚本语a的话呢,则会工E变得无比之巨大Q强烈不推荐使用Q不q如果你惛_一个通用的网l游戏底层的话,则可以考虑设计一个虚拟机。虚拟机大体的解释过E就是进行两ơ编译,W一ơ对关键字进行编译,W二ơ生成汇~语aQ然后虚拟机在根据编译生成的汇编语言q行逐行解释Q如果大家对q个感兴的话,可以?a class=cLink target=_blank><u><font color=#0066cc>www.mudos.org</font></u></a>?a onclick="javascript:tagshow(event, '%CF%C2%D4%D8');" href="javascript:;" target=_self><u><strong><font color=#0066cc>下蝲</font></strong></u></a>一份MudOS的原码来研究研究?/font></font></p> </div> </div> <img src ="http://www.shnenglu.com/jack-wang/aggbug/70968.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/jack-wang/" target="_blank">王</a> 2009-01-02 03:54 <a href="http://www.shnenglu.com/jack-wang/archive/2009/01/02/70968.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>高效|游服务器实现探?/title><link>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70966.html</link><dc:creator>王</dc:creator><author>王</author><pubDate>Thu, 01 Jan 2009 19:49:00 GMT</pubDate><guid>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70966.html</guid><wfw:comment>http://www.shnenglu.com/jack-wang/comments/70966.html</wfw:comment><comments>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70966.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.shnenglu.com/jack-wang/comments/commentRss/70966.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/jack-wang/services/trackbacks/70966.html</trackback:ping><description><![CDATA[<p>随着多核处理器的普及,如何充分利用多核q行工作成为高性能E序设计的一个重炏V本pd文章围l高性能|游服务器的实现,探讨q方面的技术?br><br>|游服务器的特点?<br><br>h大量客户端连?数百x千个),每个客户端都以一定的速率不断发送和接收数据;<br>服务器端的数据流量通常在几个至几十个Mbps之间;<br>数据需要实时处?<br>数据包具有时序关p?往往需要按照严格的先后序予以处理?br><br>|游服务器实际上代表了一cd型的新兴数据处理服务器。这里只是ؓ了讨论方便而限定于|游服务?但是所讨论的原理和技术应该是普适的?br><br>同步多线E技术肯定是无法满要求的。由于每个客L都在持箋和服务器交换数据,pȝ无法有效管理太多的U程;即使用U程池技?所能服务的客户q接也是很有限的。至于数据处理的实时性和数据的时序都无法֏?br><br>异步技术有好几U方?q里只讨论IOCP和轮询模式。IOCP是微软推动的技术。对非常大量的连?数千x?很有效。但是由于用了多线E?q些U程需要把所需d的数据通过׃n的FIFO与主U程解?否则无法保持时序)。这造成频繁的线E切?无法满大数据量的实时处理要求。另?׃|卡只有一?׃个网l地址而言),多线Eƈ不能增加d的速率。在另外一些时序要求不那么严格的场?q些U程可以各自独立完成所有的处理d,只需要在U程内部保持数据的时序。这是向同步多U程技术退化了?br><br>轮询是常用的模式。程序员把需要处理的Socketq接注册C个数据结构中,然后提交l系l检查它们的d状态。系l返回可供操作的Socketq接列表供程序员逐个处理。如果有数据可读p入ƈ处理,如果可写则把相应的数据写出去。ؓ了提高效率和E序l构的清晰v?Socket服务器通常单独使用一个线E?q且通过FIFO数据l构和主U程解耦?br><br>在单核处理器?上面q种轮询的模式是没有问题的。但是在多核q_?用于解耦的FIFO会变成q发瓉。这是因Zl的实现技术必dFIFO加锁。虽然网l线E和ȝE分别跑在不同的怸,理论上可以物理同时地q行(如果分别操作不同的数据项),但是同步锁却q其中的一个线E必ȝ待另外一个线E退Z界段,即另外一个核I闲着?br><br>q时候就需要一U支持ƈ发的数据l构,下面UC为ConcurrentFIFO?br><br>public interface ConcurrentFIFO {<br>    public Object remove();<br>    public void put(Object o);<br>}<br><br>putҎ把一个数据对象推qFIFO,而removeҎ从FIFO删除q返回一个数据对象。通过_ֿ设计,ConcurrentFIFO的实现是U程安全?两个U程可以安全而同时地讉KFIFO。这样在多核q_上就能达到极高的性能?br><br>通用的ConcurrentFIFO是非帔R于实现的。基本的技术是使用原子的CAS操作来实现。CAS即CompareAndSet。现代处理器基本上都能支持这一cL令。但是这U数据结构的实现的一个很大的障碍是垃圾回收。在多线Eƈ发运行的情况?被原子替换下来的数据无法得知其是否是其它U程所需要的,也就无法军_是否回收q块内存。除非有垃圾回收?否则ConcurrentFIFO是很隑֮现的?鼓吹手工理内存效率最高的朋友们请瞪大眼睛看清?<br><br>其实,即是对于有垃圾回收和内建线E支持的Java语言,要想构造一个支持ƈ发的数据l构,也是极端困难的。java.util.concurrent包是l过q发领域的专?Doug Lea,同时也是早期lig++的主要作?以及DLmalloc的作者。我后面讨论内存理的时候还要提C)_ֿ~写,q且由javaC֌的许多专家仔l评审测试之后才发布的?/p> <p>现在来讨Zơ提到的q发FIFO,其实现需要一些特D的技巧。我上次说要实现单线E读单线E写的FIFO,但是q里我们先来讨论一般的q发FIFO?br><br>我们知道,传统的生产者——消费者问?通常是用一个共享的~冲区来交换数据?生者和消费者各自有对应的指?在生产或者消费的时候相应地Ud。如果达C~冲区的边界则回l。如果生产者指针追上消费者指?则表明缓冲区满了;如果消费者指针追上生产者指?则表明缓冲区IZ。问题在?Z防止在缓冲区满的时候插入数?或者在~冲区空的时候删除数?生者或者消费者的每一ơ插入或者删除数据操?都必d时访问这两个指针,q就带来了不必要的同步?br><br>在单核处理器?׃n~冲区方式非帔R?q且h固定的空间开销(有时候你需要保守地估计一个比较大的数?。但是在多核处理器上(或者SMPpȝ?,如果要实现ƈ发的FIFO,必L弃这U方式。用单链表而不是共享缓冲区可以避开q个问题,q是W一个技巧?br><br>W二个技巧关pd链表的用方向。一般用链?其插入或者删除节点的位置是Q意的。但是把链表作ؓFIFO使用,则只能也只需要在两端操作。需要注意的是这时候必MNTAIL插入新的节点,而从头部HEAD删除节点。否则从N删除节点之后,无从得知新的N在哪?除非从头部遍历。这样做的好处是,插入或者删除都只涉及到一个节炏V插入的时?只要让新创徏的节点包含所需要插入的数据,q且其后l?下一个节?为NULL;再让当前N的节点的后从NULL变成q个新节?q个新节点也变成了新的N节点(q里的操作顺序很关键)。删除的时?则检查当前头部节点的后NEXT是否NULL。若?表明FIFO是空?否则,取NEXT所包含的数据来使用(是的,是NEXT而不是当前头部节Ҏ包含的数?参看下一个技巧和不变?,q把该数据从NEXT中删?而NEXT也成为新的头部节炏V?没有配图,各位误己想象一?<br><br>最后一个技?Z隔离对头部和N的访?我们需要一个空节点N(不包含数据的有效节点),其下一个节点ؓNULL;q且引入HEAD和TAIL。在开始的时?HEAD和TAIL都等于N。插入和删除数据的过E上面已l讲q了,q里讲一下不变式?br><br>W一个不变式:头部节点LI的(不包含数?。在FIFO初始化的时候这是成立的。之后的插入操作不改变头部节?因此对不变式没有影响。而对于删除操?则每一个新头部节点的数据都已经在它成ؓ新的头部节点的时候被删除(取用)了?br><br>W二个不变式:插入和删除操作没有数据冲H?也就是说,插入U程和删除线E不会同时读写同一Ҏ?不是节点)。我们只需要考虑FIFO为空,即相当于刚刚完成初始化之后的情况。对于空节点N,插入操作改变其后l?删除操作则检查其后。只要插入线E保证先让新节点包含数据再把新节Ҏ入链?也就是不能先插入I?再往节点中填入数?,那么删除U程׃会拿到空的节炏V我们看?唯一可能发生争用的地方就是N的后l指?插入U程只要在更新N的后l指针之前准备好其它相关数据和设|即可?br><br>q意味着,如果能够做到:1)一个线E对数据的更新能够被另外一个线E即ȝ?2)Ҏ据的L者写(更新和读取N的后l指?都是原子?3)指o没有被ؕ序执行。那么在单线E读单线E写的情况下,甚至不需要用锁可以安全地完成q发FIFO;如果有多个生产者线E?则增加一个生产者锁;如果有多个消费者线E?则可以增加一个消费者锁。也是?可以有四U组合?br><br>但是实际情况q非如此。对?)是容易满的,因ؓC通用处理器上32位数据的L者写通常都是原子的。对?),则取决于pȝ的内存模?在强内存模型如C/C++中是满?在弱内存模型如Java中则不然。但是主要的问题q在?)。由于指令的乱序执行,W二个不变式所需要的保证很可能被破坏,即代码实是那样写的。因此锁是必不可的,因ؓ加锁的同时还会插入内存屏障?br><br>q样看来,上次说的SRSWq发FIFO没有特别的意义了。干脆就用两个锁分别对应生者和消费?而ƈ不限制生产者或者消费者的数量:T_LOCK和H_LOCK。在插入新徏节点到链表尾部的时候用T_LOCK,而在对头部操作的时候用H_LOCK?br><br><br>具体的代码这里先不给了。这里的法不是我发明的,而是来自Maged M. Michael ?nbsp;Michael L. Scott的Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms。请参考其双锁法的伪码?br></p> <p>现在来讨论游戏消息的传送。在一个网游的q营成本?带宽费用应该是很大的一块。因此如何高效编码以及收发消息就成ؓ节省q营成本的关键。这里面能做很多文章?br><br>首先是一个基本的判断:随着处理器的计算能力不断提高,以及多核的日益普?在消息的~码以及收发环节,CPU资源不会成为瓶颈。相对的,应该千方百计考虑如何在保证游戏正常运行的前提?降低不必要的通信开销。也是?可以Ҏ戏中的消息进行一些比较复杂的~码?br><br>那么游戏中都有哪些消?我们知道聊天和语x息优先比较?而且可以通过专门的服务器来处理。真正比较关键、能够媄响玩家的游戏体验?是那些状态变更、动作、玩家之间或者玩家和服务?NPC之间的实时交互的消息。尤其是,q些消息的传送有严格的时序要求。如果一个玩家先看到自己的角色被砍死,然后才看到对方发出来的攻d?甚至Ҏ没有看到Ҏ有什么动??她肯定会愤愤不^。因?消息pȝ必须保证每一条消息的及时传?q且不能打ؕ它们之间的顺序?br><br>q意味着,每一条消息必L明确的边界。也是?收到一条消息之?接收方必能够明这条消息有多少个字节。这是一条显而易见的要求。但是大概是Z惯?在实践中它常常变为消息编码中的长度字Dc?br><br>q无疑是一U浪贏V很多消息的长度是固定的,仅仅靠检查其消息cd可以了解其边界。变长消息的处理后面会讨论。我q里q不是说要把具体的游戏逻辑与网l代码؜在一赗通过使用元数据就可以有效的把|络代码跟具体的游戏逻辑有效隔离开来。关于元数据的用后面也会详加探讨。今天时间不多了,下面讨论消息cd的编码作为结束?br><br>通常一个字节会被用来编码消息的cd,以方便接收方的解码。但是我们知?游戏中ƈ不是每种cd的消息的传送频率都是一L。事实上,我们知道哪些消息会被大量发?哪些消息的频率会低很?而另外一些消?一天也不会有几条。明乎此,可以采用非对称的编码方式来~码消息的类型。这是Huffman~码。对于占据了l大部分通信量的状态变更消息而言,即每条消息节省下半个字?也是非常划算的。以我的l验,一台普通PC可以作ؓ服务器支?000人同时在U的实时动作cL?消息通量是每U?0000?如果一个服务集有5台处理器,那么q当于节省?00kbps的带宽。这q仅仅是从消息类型编码方面榨取的。当?Huffman~码的解码是比较ȝ?效率也会低一些。但是正如前面所指出?q部分的q行开销q不会造成性能瓉?br></p> <img src ="http://www.shnenglu.com/jack-wang/aggbug/70966.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/jack-wang/" target="_blank">王</a> 2009-01-02 03:49 <a href="http://www.shnenglu.com/jack-wang/archive/2009/01/02/70966.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>|游服务器通信架构的设?/title><link>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70963.html</link><dc:creator>王</dc:creator><author>王</author><pubDate>Thu, 01 Jan 2009 18:23:00 GMT</pubDate><guid>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70963.html</guid><wfw:comment>http://www.shnenglu.com/jack-wang/comments/70963.html</wfw:comment><comments>http://www.shnenglu.com/jack-wang/archive/2009/01/02/70963.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.shnenglu.com/jack-wang/comments/commentRss/70963.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/jack-wang/services/trackbacks/70963.html</trackback:ping><description><![CDATA[随着|游从业者的规模和需求不断扩大,来多的朋友进入了|游开发这个领域,使得市场中网游开发技术相关的需求量q猛增长。目前,|游行业比较紧缺的是h较深技术功底的“专家?#8221;开发者,q主要包括两个方面:服务器端设计人员以及客户端设计h员。对于网l游戏而言Q由于其主要的游戏逻辑计算是在服务器端完成的,数据同步与广播信息的传递也是通过服务器完成的Q所以,是否拥有一个有l验的服务器端设计h员已l成ZƄ品能否成功的关键之一。鉴于此Q本文将试图q游服务器设计的一pd问题展开讨论和ȝQ笔者将l合自己的开发经验和体会Q将其中各方面内定w一呈现。希望能够对以下三类人员有所帮助Q?br>  有一定网l编E基、准备进入网游行业作服务器端设计的h员;<br>  正在从事|游服务器设计的人员Q?br>  |游目的技术负责h?br>  <br>  ׃|游服务器的设计牉|到太多内容,比如Q网l通信斚w、h工智能、数据库设计{等Q所以本文将重点从网l通信斚w的内容展开。谈到网l通信Q就不能不涉及如下五个问题:<br>[attach]1264[/attach]<br><br>[attach]1265[/attach]<br><br>[attach]1266[/attach]<br><br>[attach]1267[/attach]<br>1?常见的网游服务通信器架构概q?br>2?|游服务器设计的基本原则<br>3?|游服务器通信架构设计所需的基本技?br>4?|游服务器通信架构的测?br>5?|游服务器通信架构设计的常见问?br><br>下面我们׃W一个问题说P<br><br>常见的网游服务器通信架构概述<br>  目前Q国内的|游市场中大体存在两U类型的|游游戏QMMORPGQ如Q魔兽世界)和休闲网游(如:QQ休闲游戏和联众游戏,而如泡堂一cȝ游戏与QQ休闲游戏有很多相同点Q因此也归ؓ此类Q。由于二者在游戏风格上的截然不同Q导致了他们在通信架构设计思\上的较大差别。下面笔者将分别描述q两U网游的通信架构?br><br>1QMMORPGcȝ游的通信架构<br>  |游的通信架构Q通常是根据几个方面来定的:游戏的功能组成、游戏的预计上线人数以及游戏的可扩展性?br>  目前比较通用的MMORPG游戏程是这LQ?br><br>a. 玩家到游戏官方网站注册用户名和密码?br>b. 注册完成后,玩家选择在某一个区ȀzL戏̎受?br>c. 玩家在游戏客L中登录进入已l被Ȁzȝ游戏分区Q徏立游戏角色进行游戏?br><br>  通常Q在q样的模式下Q玩家的角色数据是不能跨Z用的Q即Q在A区徏立的游戏角色在B区是无法使用的,各区之间的数据保持各自独立性。我们将q样独立的A区或B区称Z个独立的服务器组Q一个独立的服务器组是一个相对完整的游戏世界。而网游服务器的通信架构设计Q则包括了基于服务器l之上的整个游戏世界的通信架构Q以及在一个服务器l之内的服务器通信架构?br><br>  我们先来看看单独的服务器l内部的通信是如何设计的?br>  一个服务器l内的各服务器组成,要依据游戏功能进行划分。不同的游戏内容{划会对服务器的l成造成不同的媄响。一般地Q我们可以将一个组内的服务器简单地分成两类Q场景相关的Q如Q行走、战斗等Q以及场景不相关的(如:公会聊天、不受区域限制的贸易{)。ؓ了保证游戏的畅性,可以这两类不同的功能分别交׃同的服务器去各自完成。另外,对于那些在服务器q行中进行的比较耗时的计,一般也会将其单独提炼出来,交由单独的线E或单独的进E去完成?br><br>  各个|游目会根据游戏特点的不同Q而灵z选择自己的服务器l成Ҏ。经常可以见到的一U方案是Q场景服务器、非场景服务器、服务器理器、AI服务器以及数据库代理服务器?br>  以上各服务器的主要功能是Q?br><br>  场景服务器:它负责完成主要的游戏逻辑Q这些逻辑包括Q角色在游戏场景中的q入与退出、角色的行走与跑动、角色战斗(包括打怪)、Q务的认领{。场景服务器设计的好坏是整个游戏世界服务器性能差异的主要体玎ͼ它的设计隑ֺ不仅仅在于通信模型斚wQ更主要的是整个服务器的体系架构和同步机制的设计?br><br>  非场景服务器Q它主要负责完成与游戏场景不相关的游戏逻辑Q这些逻辑不依靠游戏的地图pȝ也能正常q行Q比如公会聊天或世界聊天Q之所以把它从场景服务器中独立出来Q是Z节省场景服务器的CPU和带宽资源,让场景服务器能够可能快地处理那些对游戏畅性媄响较大的游戏逻辑?br><br>  服务器管理器Qؓ了实C多的场景服务器之间以及场景服务器与非场景服务器之间的数据同步Q我们必d立一个统一的管理者,q个理者就是服务器l中的服务器理器。它的Q务主要是在各服务器之间作数据同步Q比如玩家上下线信息的同步。其最主要的功能还是完成场景切换时的数据同步。当玩家需要从一个场景A切换到另一个场景BӞ服务器管理器负责玩家的数据从场景A转移到场景BQƈ通过协议通知q两个场景数据同步的开始与l束。所以,Z实现q些内容J杂的数据同步Q务,服务器管理器通常会与所有的场景服务器和非场景服务器保持socketq接?br><br>  AIQh工智能)服务器:׃怪物的h工智能计非常消耗系l资源,所以我们把它独立成单独的服务器。AI服务器的主要作用是负责计怪物的AIQƈ计结果返回给场景服务器,也就是说QAI服务器是单独为场景服务器服务的,它完成从场景服务器交q来的计Q务,q将计算l果q回l场景服务器。所以,从网l通信斚w来说QAI服务器只与众多场景服务器保持socketq接?br><br>  数据库代理服务器Q在|游的数据库d斚wQ通常有两U作法,一U是在应用服务器中直接加q数据库讉K的代码进行数据库讉KQ还有一U方式是数据库d独立出来Q单独作成数据库代理Q由它统一q行数据库访问ƈq回讉Kl果?br><br>  其中Q非场景服务器在不同的游戏项目中可能会被设计成不同的功能Q比如以l队、公会或全频道聊天ؓ特色的游戏,很可能ؓ了满玩家的聊天需求而设立单独的聊天服务器;而如果是以物品N易(如拍卖等Qؓ特色的游戏,很可能ؓ了满x卖的需求而单独设立拍卖服务器。到底是不是有必要将某一Ҏ戏功能独立处理成一个服务器Q要视该功能Ҏ戏的d景逻辑Q指行走、战斗等玩家日常游戏行ؓQ的影响E度而定。如果该功能对主场景逻辑的媄响比较大Q可能对d景逻辑的运行造成比较严重的性能和效率损失,那么应考虑其从主场景逻辑中剥,但能否剥还有另一个前提:此功能是否与游戏场景Q即地图坐标pȝQ相兟뀂如果此功能与场景相兛_实影响Cd景逻辑的执行效率,则可能需要在场景服务器上讄专门的线E来处理而不是将它独立成一个单独的服务器?br><br>  以上是一个服务器l内的各服务器组成情况介l,那么Q各服务器之间是如何通信的呢Q它的基本通信构架有哪些呢Q?br>  MMORPG的单l服务器架构通常可以分ؓ两种Q第一U是带网关的服务器架构;W二U是不带|关的服务器架构。两U方案各有利弊?br><br>  带|关的服务器架构而言Q由于它对外只向玩家提供唯一的一个通信端口Q所以在玩家一侧会有比较流畅的游戏体验Q这通常也是那些大规模无缝地图|游所采用的方案,但这U方案的~点是服务器l内的通信架构设计相对复杂、调试不方便、网关的通信压力q大、对|关的通信模型设计要求较高{。第二种Ҏ会同时向玩家开攑֤个游戏服务器端口Q除了游戏场景服务器的通信端口外,同时q可能提供诸如聊天服务器{的通信端口。这U方案的主要~点是在q行场景服务器的切换Ӟ玩家客户端的表现中通常会有一个诸如场景调入的界面出现Q媄响了游戏的流畅感。基于这U方案的游戏在客L的界面处理方面,比较典型的表现是Q当要进行场景切换时Q只能通过相应?#8220;传送功?#8221;传送到另外的场景去Q或者需要进入新的场景时Q客L会有比较长时间的{待q入新场景的{待界面(Loading界面)?br><br>  从技术角度而言Q笔者更們֐于将独立的服务器l设计成带网关的模型Q虽然这加大了服务器的设计难度,但却增强了游戏的畅感和安全性,q种pq是值得的?br>  W者在下面附上了带|关的MMORPG通信架构图,希望能给业内的朋友们一Ҏ益的启_? <img src ="http://www.shnenglu.com/jack-wang/aggbug/70963.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/jack-wang/" target="_blank">王</a> 2009-01-02 02:23 <a href="http://www.shnenglu.com/jack-wang/archive/2009/01/02/70963.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <p>лǵվܻԴȤ</p> <a href="http://www.shnenglu.com/" title="精品视频久久久久">精品视频久久久久</a> <div class="friend-links"> </div> </div> </footer> <a href="http://www.duansisi.cn" target="_blank">þ޾Ʒ벥</a>| <a href="http://www.85062.com.cn" target="_blank">wwwþ</a>| <a href="http://www.lae.net.cn" target="_blank">þþþavר</a>| <a href="http://www.qdaigo.com.cn" target="_blank">Сڵþþþþ</a>| <a href="http://www.88177.com.cn" target="_blank">þݹֻƬ</a>| <a href="http://www.wooblog.cn" target="_blank">þǿdŮ</a>| <a href="http://www.941ad.cn" target="_blank">޹Ʒþþò</a>| <a href="http://www.apramomall.cn" target="_blank">þþƷ˹ҹ</a>| <a href="http://www.ikdianying.cn" target="_blank">޹Ʒľþþ</a>| <a href="http://www.tasd.org.cn" target="_blank">þþƷavպ </a>| <a href="http://www.i35idc.cn" target="_blank">ҹþƷþþþ</a>| <a href="http://www.awxsp.cn" target="_blank">޹Ʒþ66</a>| <a href="http://www.jisuvpn.cn" target="_blank">˾þۺij</a>| <a href="http://www.gaohuirong.cn" target="_blank">ƷþþþþĻ</a>| <a href="http://www.zzjinshan.cn" target="_blank">ھƷ˾þþþվ</a>| <a href="http://www.qqhaobofangqi.cn" target="_blank">һþaþþƷvrۺ</a>| <a href="http://www.lslscy.cn" target="_blank">ۺϾþü</a>| <a href="http://www.uztw.cn" target="_blank">Сڵþþþþ</a>| <a href="http://www.bukrrlg.cn" target="_blank">Ժձһձþ </a>| <a href="http://www.zuiaimama.cn" target="_blank">þþƷһ</a>| <a href="http://www.dgcry.cn" target="_blank">˾Ʒþþþ7777</a>| <a href="http://www.taobaoke.net.cn" target="_blank">91ƷۺϾþ㽶</a>| <a href="http://www.pchenshimin.com.cn" target="_blank">ҰAVþһ</a>| <a href="http://www.babaishu.cn" target="_blank">Ůþþþþ</a>| <a href="http://www.woai858.cn" target="_blank">㽶þþþ</a>| <a href="http://www.7cfw.cn" target="_blank">91Ʒþþþþio</a>| <a href="http://www.car321.cn" target="_blank">97Ʒ˾þþô߽</a>| <a href="http://www.yteid.cn" target="_blank">޹Ʒ۲ӰԺþ</a>| <a href="http://www.snailwr.cn" target="_blank">ɫۺϾþþƷĻҳ</a>| <a href="http://www.51s8s.cn" target="_blank">þþþþҹӰԺ </a>| <a href="http://www.syggzy.org.cn" target="_blank">ij˾þþþӰԺѹۿ</a>| <a href="http://www.taobaomaiba.cn" target="_blank">ȾþùƷ</a>| <a href="http://www.rljps.cn" target="_blank">þþžƷ</a>| <a href="http://www.zhengchaoyue.cn" target="_blank">ھƷþ޻</a>| <a href="http://www.gll-gx.cn" target="_blank">99þþƷ鶹</a>| <a href="http://www.hkgsjt.cn" target="_blank">þþþþùƷ</a>| <a href="http://www.suvvoza.cn" target="_blank">ҹҹݺݾþö </a>| <a href="http://www.88177.com.cn" target="_blank">þþþAVƬ</a>| <a href="http://www.wanghaochuju.cn" target="_blank">þþƷƷ޾Ʒ</a>| <a href="http://www.662z.cn" target="_blank">þþƷҹҹŷ</a>| <a href="http://www.gmve.cn" target="_blank">þݺҹҹ2020һ</a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>