本帖最后由 GuestCode 于 2009-11-04 14:16:57 编辑

解决方案 »

  1.   

    楼主帮我解答下问题关于IOCP的几个疑问:自定义协议发送,每次都先接收数据头结构1。当接到头的时候,计算接下来数据大小,然后就投放一个WSArecv,这时候满足这个WSArecv,的事件是什么?马上能把缓冲里面的数据拿出来?还是要等下个包来才会getqueuexxx返回,我觉得合理的话应该是前者。可是对于核心编程书理解是这样的:当有一个I/O完成时,完成队列添加一项,这时候如果有线程在getqueuexxx,就会返回,并把那一项删除掉。对应上面描述情形,把第一次getqueuexxx返回取走数据头的时候,完成队列已经没这项了,这样投递一个WSArecv的话,getqueuexxx应该不会返回。
    这样的解说和选择事件模式一致——当有事件来时,如果总数据有100K,只取30K,那剩下的70K不会有事件通知!2。由于wsarecv存在不能一次把给定的数据发送完成的可能,需要在发送完成的时候做判断再发送剩下的。。那如果在发送完成之前需要发送另一个数据包的话,不是乱套了?第一个数据包给这个数据包隔开了。这时候是不是需要建立一个发送队列?只有确定前面一个包完成时才能发送另一个包?
      

  2.   

    LS的。完成端口只是通知你该干什么事,具体的接/发操作还是由你自己来的。比如,
    收:发起一个完成例程的事件,当收到数据后,完成端口的线成get到这个状态,然后进行操作
    发:放入一个事件,完成端口get这个事件状态后,进行发送的代码执行。这个所有的中间,有个KEY,就相当是你的参数地址。
      

  3.   

    5楼:
    1、如果第一次GetXXX返回,Socket有数据并大于你投递的缓存的时候,会填满你投递的缓存,剩下的会等你再次投递WSARecv,WSARecv在异步情况下,应该不会不会立即获取数据,理论上应该由GetXXX返回。这种情况只有单投递接收的时候才会产生你题的这个问题,而对于多投递接收,没有发现这种情况。关于你题的这个我仅能从理论上回答,你可以测试单投递,一次发超过投递缓存大小的数据,在GetXXX之后看看WSARecv返回是否已经获得缓冲数据。2、WSARecv最大只能填充投递的缓存,剩下由Socket约8K的缓存存放。一般情况下,需要你设置投递缓存小于Socket缓存,如果大于的,应该分包。或者你设置Socket缓存为0,并多次投递WSARecv保证不发生饥渴。对于粘包分包等处理,不是Iocp所能完成的,也不是Socket能完成的,需要你有一套完整的协议。
    另外,不知道你的协议为什么要先发数据头,这样的效率反而降低了Iocp的性能,我们通常做法是一次发完一个完整的数据包(包头+数据+包尾)。
      

  4.   


    如果你不先发包头的话。假设这样一个情况。对方发送一个超过你接收buffer大小的包过来,那你怎么处理。
      

  5.   

    你可能还不明白我的说法,对于每个Socket,多绑几次接收的事件不会增多。
    只是返回的Key的可能是最后一个绑定的Key,前面绑定的应该失效了吧。不会说,我先后绑两个Key,同一个事件,会触发两次GetXXX返回吧?权威说法期待中...
      

  6.   


    恩,我知道这时候缓冲区肯定有数据,,问题是出在getqueuexxx这函数什么情况下会返回核心编程》上提到内核有一个完成队列,这个队列有通知元素的时候,getqueuexxx就返回,但你第一次getqueuexxx返回取到包头后,同时也把这个队列的通知元素取走了。这样你再想wsarecv数据体的时候,
    getqueuexxx由于队列没通知元素就没能取成功。具体你可以找那本书来看下,我觉得说得也有道理,不过也可能我理解错误
      

  7.   

    而且LZ的问题我也有点想不明白:既然你都accept到一个用户了,直接把这个用户的结构(套接字,缓冲区,内存指针)绑在一起,这样在有数据的时候不是直接就get到用户了吗?这还找什么??用户掉线直接删掉就好了。
      

  8.   

    哈哈,已经说清楚了,你要有一套完整的协议。
    Socket和Iocp只管收发。
      

  9.   


    你完全没有理解掉线和Logout在游戏中的重要性
      

  10.   


    对方发送一个超过你接收buffer大小的包过来,那你怎么处理。 首先回答你这个问题,超过buffer,TCP是保证发的有多少,就能收到多少,比如缓冲区 8K,你发了10K,剩下2K待发,直到你前面8K收完为止。你收的函数就可能这样
    while( len==lenRecv )
       lenRecv+=recv();..你再想wsarecv数据体的时候不是有产生一个事件通知了吗?不就又get到数据了。完成端口是以完成例程为基础衍生的,而windows里的异步事件都是有一个事件由系统主动通知的。那本书上不是也说吗:
    你可以放入一个消息post
    也可以因为完成一个I/O操作而在队列增加。 完成端口怎么知道这个事件操作,就是因为有事件通知。不信你在WSArevc的时候,把overlapped参数设置为空,看看能不能get到东西
      

  11.   


    你不明白游戏是怎么处理的。
    套接字的四个关键字:服务器 IP port,客户端ip port
    如果是异常掉线的,你觉得客户端IP会一样吗?即使一样,port会一样吗?port有保护机制,你去研究下tcp断开的四次握手,为什么有个wait的状态。
      

  12.   


    请记住,游戏正在进行时,掉线是不能立即删除这个用户对象的,只能删除这个Socket的句柄对象。
    只有用户发送Logout方可删除,或掉线超时了也可认为是Logout了(部分棋牌游戏会将他转入托管状态,如果在游戏结束之前他在上线了还可以继续游戏)。你答非所问。
      

  13.   

    每个用户登录都有ID,这个ID不是这个用户IP和端口。只有这个ID才能关联业已存在的用户对象。
      

  14.   

    不仅是游戏,还有其它的系统需求,不能人为掉线就是Logout就去删除这个用户对象。除非掉线的时候你把这个用户当前的状态写入硬盘,待他再上线的时候你再读硬盘(这个不是明智的做法)。
      

  15.   

    和你讨论我很委屈。。Logout不同环境有不同的处理办法,你文中又没说这个环境。况且socket做为用户标志没什么不行的,这个就是唯一性。你担心的是掉线后这个唯一的是否。
      

  16.   


    除非你保证,掉线后再连接也是同一个Socket。
      

  17.   

    to vincent_1011
    我Q 2#4#2#1#0#6#7#6#4#
      

  18.   

    我还另一个问题怎么处理?假如你服务每次都是投递一个max_size=100的wsarecv而客户端wsasend了2个20bytes的包过来,如果服务器不及时的话,服务器会在一次wsarecv就把2个包40字节接收过来的,实际有这情况吗?
      

  19.   


    你说的是TCP吗?TCP的特性就是这样啊。流结构,,随意的合在一起。所以才有数据头的概念。
      

  20.   

    原来一楼是你啊。不回答,不等于没有理解能力。
    文中我已经很明确的说了:
    做过Tcp服务开发的人都知道,每个客户端连接都有一个Socket对象(下称HandleData)与之对应。做过游戏开发的人也肯定知道,在服务器端每个登录的用户有个对象(下称UserData)记录用户信息,UserData和HandleData也是一一对应关系。但两者很难合并为一体(同一个数据结构或类对象),UserData是在用户输入ID和密码Login之后被会创建(或池分配),用户发送数据Logout之后会被摧毁(或回收入池),而在Logout之前,由于网络等诸多原因,会有N次的重新连接的可能性,如果断线不超过规定时间的话UserData是不能摧毁的,那么UserData在其生命期内对应的HandleData就不是唯一的了。一般HandleData和UserData关联的做法是,根据Login的用户ID在目前的在线用户信息列表中查找UserData,找到则关联(未超时和Logout),找不到则创建后再关联,此后根据关联信息无需遍历很快就可以知道来自这个HandleData的数据就是这个UserData的(用户登录频率远比数据收发频率底得多,以最快方式把Socket的数据定位到用户对象上去是高效率的一个关键)后面说:
    尚未不知道有人提过这个方法,也不知道是否行得通,也希望大家互相探讨。
    ....
      

  21.   

    好长  就看了个开头  
    对这句我说一下 
    有自称内核编程的人说临界段同步不要需要进入内核态,用过临界段的人都知道,当竞争发生的时候,“后来”的线程总被挂起,那么线程挂起会进入什么态? 我不知道windows得临界段同步是否会进入内核态,但是,如果说不进入内核态,指的是没有竞争发生得时候。如果是要被挂起得线程,那是一定会进入内核态得,毕竟线程调度是内核态完成得。这里说得一定不包括自旋锁,或者一些非系统定义得线程(比如一些库里面会提供用户态模拟得线程调度). 
    另外竞争失败得线程要进入内核态带来得性能损耗一般是可以忽略得,毕竟,都竞争失败了,你不让他进内核态睡眠,你想让他继续占CPU?
      

  22.   

    是TCP,对,所以可以这样随意组合。。既然是这样的话,所以我就想只有先发数据头,再发数据体,(才能最小程度解决。因为数据头也可能一次不完整发成功,虽然机率更小)。能上个Q具体和你讨论比较好
      

  23.   


    那是一个反问方式的回答而已。刚好,请教你个个问题:
    InterlockedIncrement和InterlockedDecrement,InterlockedPopEntrySList,
    InterlockedPopEntrySList是如何处理竞争的?是使用类似Lockfree的死Loop等待,还是其它更好的线程挂起等待方式?在测试中总发现System进程的CPU使用很高,总感觉是上诉锁的问题,当然也不排除内核处理Icop机制和收发数据。
      

  24.   

    整包发送:包头+数据+包尾
    如果安全点需要CRC之类的验证。应为会有数据区的数值与(包头或包尾)一致。
    如果发现包头,根据包长度,验证包尾是否合法,合法则解析数据,并移动指针到包尾之后,如何还有数据再验证是否是包头...
    如果发现包头没有发现包尾,则需要保存,等待下个数据包并与之合并,再寻找包头和包尾...
      

  25.   


    恩,搞清楚了。那另外有一个写操作的问题。就是wsasend,有可能wsasend不完整。这情况难道只能建立一个队列,把要send的数据丢进这队列,然后在确定前一包全部发送成功,再出队下个包发送?这样效率会不会低了?但如果不是这样,还能怎样?
    这个就和上面问题对应,解决这个,上一个就不会出现。
      

  26.   

    InterlockedIncrement汇编:
    7C809776   mov         ecx,dword ptr [esp+4]
    7C80977A   mov         eax,1
    7C80977F   lock xadd   dword ptr [ecx],eax
    7C809783   inc         eax
    7C809784   ret         4
    EnterCriticalSection();
    7C921005  mov         ecx,dword ptr fs:[18h] 
    7C92100C  mov         edx,dword ptr [esp+4] 
    7C921010  cmp         dword ptr [edx+14h],0 
    7C921014  jne         7C921065 
    7C921016  lock inc    dword ptr [edx+4] 
    7C92101A  jne         7C921035 
    7C92101C  mov         eax,dword ptr [ecx+24h] 
    7C92101F  mov         dword ptr [edx+0Ch],eax 
    7C921022  mov         dword ptr [edx+8],1 
    7C921029  xor         eax,eax 
    7C92102B  ret         4  LeaveCriticalSection();
    7C9210ED  mov         edx,dword ptr [esp+4] 
    7C9210F1  xor         eax,eax 
    7C9210F3  dec         dword ptr [edx+8] 
    7C9210F6  jne         7C92111E 
    7C9210F8  mov         dword ptr [edx+0Ch],eax 
    7C9210FB  lock dec    dword ptr [edx+4] 
    7C9210FF  jge         7C921104 
    7C921101  ret         4 2者用的方式一样,VC08 debug无优化模式lock解释:
    摘:
    LOCK总线封锁信号,三态输出,低电平有效。LOCK有效时表示CPU不允许其它总线主控者占用总线。这个信号由软件设置,当前指令前加上LOCK前缀时,则在执行这条指令期间LOCK保持有效,阻止其它主控者使用总线。 摘
    从 P6 处理器开始,如果指令访问的内存区域已经存在于处理器的内部缓存中,则“lock” 前缀并不将引线 LOCK 的电位拉低,而是锁住本处理器的内部缓存,然后依靠缓存一致性协议保证操作的原子性。4.2) IA32 CPU调用有lock前缀的指令,或者如xchg这样的指令,会导致其它的CPU也触发一定的动作来同步自己的Cache。
    CPU的#lock引脚链接到北桥芯片(North Bridge)的#lock引脚,当带lock前缀的执行执行时,北桥芯片会拉起#lock
    电平,从而锁住总线,直到该指令执行完毕再放开。 而总线加锁会自动invalidate所有CPU对 _该指令涉及的内存_
    的Cache,因此barrier就能保证所有CPU的Cache一致性。4.3) 接着解释。
    lock前缀(或cpuid、xchg等指令)使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。
    IA32在每个CPU内部实现了Snoopying(BUS-Watching)技术,监视着总线上是否发生了写内存操作(由某个CPU或DMA控
    制器发出的),只要发生了,就invalidate相关的Cache line。 因此,只要lock前缀导致本CPU写内存,就必将导致
    所有CPU去invalidate其相关的Cache line。 
    http://www.unixresources.net/linux/clf/linuxK/archive/00/00/65/37/653778.html
    http://www.bitscn.com/linux/kernel/200806/144491_2.html 
    蓝色字取自看雪。看懂看不懂就看自己RP了。
      

  27.   

    lock解释:这段都采自看雪http://bbs.pediy.com/showthread.php?t=84326
      

  28.   

    说明EnterCriticalSection和interlock都是采取类似的锁总线方式,没有进行内核的切换。因此只取得了效率,而没有更多的操纵。如果是内核对象,你就可以设置更多的比如超时等这样的细节控制。至于EnterCriticalSection为什么会竞争内核忘记了当时还做了个笔记。
      

  29.   

    “导致所有CPU去invalidate其相关的Cache line”
    可以说竞争的时候,后来者的操作是无效的?那么后来者如何才能再次操作,直到他获得成功为止?或者说,lock前缀是让所有CPU串行化,不会在同一个时间内操作?主要是这个问题。
      

  30.   


    这个很简单啊。当被锁住的时候(从 P6 处理器开始,如果指令访问的内存区域已经存在于处理器的内部缓存中,则“lock” 前缀并不将引线 LOCK 的电位拉低,而是锁住本处理器的内部缓存,然后依靠缓存一致性协议保证操作的原子性。 ),线程必然要不断的检测是否符合条件。 而这个繁忙度就取决于你线程的优先级别
      

  31.   

    所以,lockinter的指令虽然快,但也有不公平的地方随着处理器个数的不断增加,竞争也在加剧,自然导致更长的等待时间。    释放自旋锁时的重置操作将无效化所有其它正在忙等待的处理器的缓存,那么在处理器拓扑结构中临近自旋锁拥有者的处理器可能会更快地刷新缓存,因而增大获得自旋锁的机率。 网管联盟www.bitsCN.com    由于每个申请自旋锁的处理器均在全局变量 slock 上忙等待,系统总线将因为处理器间的缓存同步而导致繁重的流量,从而降低了系统整体的性能。 蓝色字摘自http://www.bitscn.com/linux/kernel/200806/144491_2.html说明一切还是以人脑的策略优先。。
      

  32.   


    EnterCriticalSection 发生竞争后跳转到的地方,应该就是处理竞争进入内核态的。
      

  33.   


    看来,使用InterLocked还要考虑很多了。
    要看服务器的CPu数量了。曾经有人极力推荐内核链栈:Singly-Linked list
    也就是:InterlockedPopEntrySList
    我在2CPU的机子上测试过,比临界端块N多。我也查了很多资料,但说如何解决竞争等待问题,还是不很明白。
    现在似乎明白了。
      

  34.   


    由于每个申请自旋锁的处理器均在全局变量 slock 上忙等待
    ”这就是明白的关键了。我总以为可以串行化CPU时钟周期或者令CPU时钟停止直到获得控制权,从而达到等待目的呢。
      

  35.   

    EnterCriticalSection 我估计不会用自旋锁把  用户态的程序动不动就自旋 效率太低了把  
    谢谢楼上贴汇编的 EnterCriticalSection应该是没竞争就只是锁了一下总线  有竞争就通过系统调用进内核态睡觉去  另外LEAVE的时候也有可能要进内核态唤醒别的线程
      

  36.   

    http://topic.csdn.net/u/20091029/23/663ad6e8-8182-430c-9aea-52e4097546b7.html代码客,帮我看看这个。
      

  37.   


    Windows核心编程第五版P212:
    当线程试图进入一个关键段,但这个关键段正被另一个线程占用的时候,函数会立即把调用线程切换到等待状态,意味着线程必须从用户模式切换到内核模式。
      

  38.   

    楼上几位对关键段讨论都是对的。很大几率上就是靠概率,没必要每回都要死死的进入内核状态。这也是很早前,linux2.4的版本在关键段的效率上不如Win的一个很大原因。 经过讨论头脑风暴,学习了。