void* STNetEngine::MsgWorker( STNetConnect *pConnect ) { for ( ; !m_stop; ) { m_pNetServer->OnMsg( pConnect->m_host );//无返回值,避免框架逻辑依赖于客户实现 if ( !pConnect->m_bConnect ) break; if ( !pConnect->IsReadAble() ) break; } AtomDec(&pConnect->m_nReadCount,1); //确保NetServer::OnClose()一定在所有NetServer::OnMsg()完成之后 if ( !pConnect->m_bConnect ) NotifyOnClose(pConnect); return 0; }
void* NetEngine::MsgWorker( NetConnect *pConnect ) { for ( ; !m_stop; ) { pConnect->m_nReadCount = 1; m_pNetServer->OnMsg( pConnect->m_host );//无返回值,避免框架逻辑依赖于客户实现 if ( !pConnect->m_bConnect ) break; if ( pConnect->IsReadAble() ) continue; if ( 1 == AtomDec(&pConnect->m_nReadCount,1) ) break;//避免漏接收 } //触发OnClose(),确保NetServer::OnClose()一定在所有NetServer::OnMsg()完成之后 if ( !pConnect->m_bConnect ) NotifyOnClose(pConnect); pConnect->Release();//使用完毕释放共享对象 return 0; }
//关闭一个连接 void STNetEngine::CloseConnect( ConnectList::iterator it ) { /* 必须先删除再关闭,顺序不能换, 避免关闭后,erase前,正好有client连接进来, 系统立刻就把该连接分配给新client使用,造成新client在插入m_connectList时失败 */ STNetConnect *pConnect = it->second; m_connectList.erase( it );//之后不可能有MsgWorker()发生,因为OnData里面已经找不到连接了 AtomDec(&pConnect->m_useCount, 1);//m_connectList访问完成 pConnect->GetSocket()->Close(); pConnect->m_bConnect = false; if ( 0 == AtomAdd(&pConnect->m_nReadCount, 1) ) NotifyOnClose(pConnect); pConnect->Release();//连接断开释放共享对象 return; }
//关闭一个连接 void NetEngine::CloseConnect( ConnectList::iterator it ) { /* 必须先删除再关闭,顺序不能换, 避免关闭后,erase前,正好有client连接进来, 系统立刻就把该连接分配给新client使用,造成新client在插入m_connectList时失败 */ NetConnect *pConnect = it->second; m_connectList.erase( it );//之后不可能有MsgWorker()发生,因为OnData里面已经找不到连接了 /* pConnect->GetSocket()->Close(); 以上操作在V1.51版中,被从此处移动到CloseWorker()中 在m_pNetServer->OnCloseConnect()之后执行 A.首先推迟Close的目的 在OnCloseConnect()完成前,也就是业务层完成连接断开业务前 不让系统回收socket的句柄,再利用 以避免发生如下情况。 比如用户在业务层(NetServer派生类)中创建map<int,NetHost>类型host列表 在NetServer::OnConnect()时加入 在NetServer::OnClose())时删除 如果在这里就执行关闭socket(假设句柄为100) 业务层NetServer::OnClose将在之后得到通知, 如果这时又有新连接进来,则系统会从新使用100作为句柄分配给新连接。 由于是多线程并发,所以可能在NetServer::OnClose之前,先执行NetServer::OnConnect() 由于NetServer::OnClose还没有完成,100这个key依旧存在于用户创建的map中, 导致NetServer::OnConnect()中的插入操作失败 因此,用户需要准备一个wait_insert列队,在OnConnect()中insert失败时, 需要将对象保存到wait_insert列队,并终止OnConnect()业务逻辑 在OnClose中删除对象后,用对象的key到wait_insert列队中检查, 找到匹配的对象再insert,然后继续执行OnConnect的后续业务, OnConnect业务逻辑才算完成 1.代码上非常麻烦 2.破坏了功能内聚,OnConnect()与OnClose()逻辑被迫耦合在一起 B.再分析推迟Close有没有其它副作用 问题1:由于连接没有关闭,在server端主动close时,连接状态实际还是正常的, 如果client不停地发送数据,会不会导致OnMsg线程一直接收不完数据, 让OnClose没机会执行? 答:不会,因为m_bConnect标志被设置为false了,而OnMsg是在MsgWorker()中被循环调用, 每次循环都会检查m_bConnect标志,所以即使还有数据可接收,OnMsg也会被终止 */ // pConnect->GetSocket()->Close(); pConnect->m_bConnect = false; /* 执行业务NetServer::OnClose(); 避免与未完成MsgWorker并发,(MsgWorker内部循环调用OnMsg()),也就是避免与OnMsg并发 与MsgWorker的并发情况分析 情况1:MsgWorker已经return 那么AtomAdd返回0,执行NotifyOnClose(),不可能发生在OnMsg之前, 之后也不可能OnMsg,前面已经说明MsgWorker()不可能再发生 情况2:MsgWorker未返回,分2种情况 情况1:这里先AtomAdd 必然返回非0,因为没有发生过AtomDec 不执行OnClose 遗漏OnClose? 不会!那么看MsgWorker(),AtomAdd返回非0,所以AtomDec必然返回>1, MsgWorker()会再循环一次OnMsg(这次OnMsg是没有数据的,对用户没有影响 OnMsg读不到足够数据很正常), 然后退出循环,发现m_bConnect=false,于是NotifyOnClose()发出OnClose通知 OnClose通知没有被遗漏 情况2:MsgWorker先AtomDec 必然返回1,因为MsgWorker循环中首先置了1,而中间又没有AtomAdd发生 MsgWorker退出循环 发现m_bConnect=false,于是NotifyOnClose()发出OnClose通知 然后这里AtomAdd必然返回0,也NotifyOnClose()发出OnClose通知 重复通知? 不会,NotifyOnClose()保证了多线程并发调用下,只会通知1次 与OnData的并发情况分析 情况1:OnData先AtomAdd 保证有MsgWorker会执行 AtomAdd返回非0,放弃NotifyOnClose MsgWorker一定会NotifyOnClose 情况2:这里先AtomAdd OnData再AtomAdd时必然返回>0,OnData放弃MsgWorker 遗漏OnMsg?应该算做放弃数据,而不是遗漏 分3种断开情况 1.server发现心跳没有了,主动close,那就是网络原因,强制断开,无所谓数据丢失 2.client与server完成了所有业务,希望正常断开 那就应该按照通信行业连接安全断开的原则,让接收方主动Close 而不能发送方主动Close,所以不可能遗漏数据 如果发送放主动close,服务器无论如何设计,都没办法保证收到最后的这次数据 */ if ( 0 == AtomAdd(&pConnect->m_nReadCount, 1) ) NotifyOnClose(pConnect); pConnect->Release();//连接断开释放共享对象 return; }