bool Queue::Push( void *pObject ) { if ( NULL == pObject ) return false; if ( 0 >= m_nWriteAbleCount ) return false;//列队已满 if ( 0 >= AtomDec(&m_nWriteAbleCount,1) ) //已经有足够多的push操作向列队不同位置写入数据 { AtomAdd(&m_nWriteAbleCount, 1); return false; } uint32 pushPos = AtomAdd(&m_push, 1); pushPos = pushPos % m_nSize; /* 只有在NPop并发情况下,因Pop无序完成,第一个位置的Pop未完成, 后面的Pop就先完成提示有空位,导致这里检查第一个位置时,发现数据未被取走 因为该类只允许1对N,所以必然是单线程Push( void *pObject ),所以条件内m_push不需要并发控制 */ if ( !m_queue[pushPos].IsEmpty ) { m_push--; AtomAdd(&m_nWriteAbleCount,1); return false; } m_queue[pushPos].pObject = pObject; m_queue[pushPos].IsEmpty = false; AtomAdd(&m_nReadAbleCount,1); return true; }
void* Queue::Pop() { if ( 0 >= m_nReadAbleCount ) return NULL;//空列队 if ( 0 >= AtomDec(&m_nReadAbleCount,1)) //已经有足够多的pop操作读取列队不同位置的数据 { AtomAdd(&m_nReadAbleCount, 1); return NULL; } uint32 popPos = AtomAdd(&m_pop, 1); popPos = popPos % m_nSize; /* 只有在NPush并发情况下,因Push无序完成,第一个位置的Push未完成, 后面的Push就先完成提示有数据,导致这里检查第一个位置时,发现没有数据可读 因为该类只允许1对N,必然是单线程Pop(),所以条件内m_pop不需要并发控制 */ if ( m_queue[popPos].IsEmpty ) { m_pop--; AtomAdd(&m_nReadAbleCount,1); return NULL; } void *pObject = m_queue[popPos].pObject; m_queue[popPos].pObject = NULL; m_queue[popPos].IsEmpty = true; AtomAdd(&m_nWriteAbleCount,1); return pObject; }
void NetEngine::NotifyOnClose(NetConnect *pConnect) { if ( 0 == AtomAdd(&pConnect->m_nDoCloseWorkCount, 1) )//只有1个线程执行OnClose,且仅执行1次 { AtomAdd(&pConnect->m_useCount, 1);//业务层先获取访问 m_workThreads.Accept( Executor::Bind(&NetEngine::CloseWorker), this, pConnect); } }
connectState NetEngine::OnData( SOCKET sock, char *pData, unsigned short uSize ) { connectState cs = unconnect; AutoLock lock( &m_connectsMutex ); ConnectList::iterator itNetConnect = m_connectList.find(sock);//client列表里查找 if ( itNetConnect == m_connectList.end() ) return cs;//底层已经断开 NetConnect *pConnect = itNetConnect->second; pConnect->RefreshHeart(); AtomAdd(&pConnect->m_useCount, 1);//业务层先获取访问 lock.Unlock();//确保业务层占有对象后,HeartMonitor()才有机会检查pConnect的状态 try { cs = RecvData( pConnect, pData, uSize );//派生类实现 if ( unconnect == cs ) { pConnect->Release();//使用完毕释放共享对象 OnClose( sock ); return cs; } /* 避免并发MsgWorker,也就是避免并发读 与MsgWorker的并发情况分析 情况1:MsgWorker已经return 那么AtomAdd返回0,触发新的MsgWorker,未并发 情况2:MsgWorker未完成,分2种情况 情况1:这里先AtomAdd 必然返回非0,因为没有发生过AtomDec 放弃触发MsgWorker 遗漏OnMsg? 不会!那么看MsgWorker(),AtomAdd返回非0,所以AtomDec必然返回>1, MsgWorker()会再循环一次OnMsg 没有遗漏OnMsg,无并发 情况2:MsgWorker先AtomDec 必然返回1,因为MsgWorker循环中首先置了1,而中间又没有AtomAdd发生 MsgWorker退出循环 然后这里AtomAdd,必然返回0,触发新的MsgWorker,未并发 */ if ( 0 < AtomAdd(&pConnect->m_nReadCount, 1) ) { pConnect->Release();//使用完毕释放共享对象 return cs; } //执行业务NetServer::OnMsg(); m_workThreads.Accept( Executor::Bind(&NetEngine::MsgWorker), this, pConnect); }catch( ... ){} return cs; }
connectState STNetEngine::OnData( SOCKET sock, char *pData, unsigned short uSize ) { connectState cs = unconnect; AutoLock lock( &m_connectsMutex ); ConnectList::iterator itNetConnect = m_connectList.find(sock);//client列表里查找 if ( itNetConnect == m_connectList.end() ) return cs;//底层已经断开 STNetConnect *pConnect = itNetConnect->second; STNetHost accessHost = pConnect->m_host;//被引擎访问,局部变量离开时,析构函数自动释放访问 lock.Unlock(); pConnect->RefreshHeart(); try { cs = RecvData( pConnect, pData, uSize ); if ( unconnect == cs ) { OnClose( sock ); return cs; } if ( 0 != AtomAdd(&pConnect->m_nReadCount, 1) ) return cs; //执行业务STNetServer::OnMsg(); MsgWorker(pConnect); }catch( ... ){} return cs; }
//向某组连接广播消息(业务层接口) void NetEngine::BroadcastMsg( int *recvGroupIDs, int recvCount, char *msg, int msgsize, int *filterGroupIDs, int filterCount ) { ////////////////////////////////////////////////////////////////////////// //关闭无心跳的连接 ConnectList::iterator it; NetConnect *pConnect; vector<NetConnect*> recverList; //加锁将所有广播接收连接复制到一个队列中 AutoLock lock( &m_connectsMutex ); for ( it = m_connectList.begin(); m_nHeartTime > 0 && it != m_connectList.end(); it++ ) { pConnect = it->second; if ( !pConnect->IsInGroups(recvGroupIDs, recvCount) || pConnect->IsInGroups(filterGroupIDs, filterCount) ) continue; recverList.push_back(pConnect); AtomAdd(&pConnect->m_useCount, 1);//业务层先获取访问 } lock.Unlock(); //向队列中的连接开始广播 vector<NetConnect*>::iterator itv = recverList.begin(); for ( ; itv != recverList.end(); itv++ ) { pConnect = *itv; if ( pConnect->m_bConnect ) pConnect->SendData((const unsigned char*)msg,msgsize); pConnect->Release();//使用完毕释放共享对象 } }
NetHost& NetHost::operator=(const NetHost& obj) { if ( m_pConnect == obj.m_pConnect ) return *this; if ( NULL != m_pConnect ) m_pConnect->Release(); if ( NULL != obj.m_pConnect ) AtomAdd(&obj.m_pConnect->m_useCount, 1); m_pConnect = obj.m_pConnect; return *this; }
void STNetEngine::NotifyOnClose(STNetConnect *pConnect) { if ( 0 == AtomAdd(&pConnect->m_nDoCloseWorkCount, 1) )//只有1个线程执行OnClose,且仅执行1次 { SetServerClose(pConnect);//连接的服务断开 m_pNetServer->OnCloseConnect( pConnect->m_host ); } }
void NetConnect::SetData( HostData *pData, bool autoFree ) { m_autoFreeData = autoFree; //释放已设置的数据或引用计数 if ( NULL != m_pHostData ) { if ( m_pHostData->m_autoFree ) return;//自由模式,不能解除关联 NetHost unconnectHost; m_pHostData->SetHost(&unconnectHost);//关联到未连接的host mdk::AutoLock lock(&m_mutexData); m_pHostData->Release();//释放数据,并发GetData()分析,参考HostData::Release()内部注释 /* 与GetData并发 可能返回NULL,也可能返回新HostData,也可能返回无效的HostData 就算提前到罪愆执行,结果也一样 就算对整个SetData加lock控制,也不会有多大改善 */ m_pHostData = NULL;//解除host与data的绑定 } if ( NULL == pData ) return; if ( -1 == AtomAdd(&pData->m_refCount, 1) ) return; //有线程完成了Release()的释放检查,对象即将被释放,放弃关联 pData->m_autoFree = autoFree; /* autoFree = true HostData的生命周期由框架管理 框架保证了,地址的有效性, 直接复制NetHost指针,到HostData中,不增加引用计数. autoFree = false HostData的生命周期由用户自行管理,框架不管 则复制NetHost对象到HostData中,引用计数增加,表示有用户在访问NetHost对象, 确保在用户释放HostData之前,NetHost永远不会被框架释放。 */ if ( autoFree ) { /* 代理模式,不需要增加引用计数,减回来 前面+1检查能通过,旧值最小应该=1,否则说明+1之后,外部又发生了重复Release(),是不合理的,触发断言 */ if ( 1 > AtomDec(&pData->m_refCount, 1) ) { mdk::mdk_assert(false); return; } pData->m_pHost = &m_host;//代理模式,copy规则限制了,pData不会超出host生命周期,只要pData存在,m_pHost必然存在,不必copy } else //自主模式 { pData->m_hostRef = m_host;//确保m_pHost指向安全内存,将host做1份copy } m_pHostData = pData;//必须最后,确保GetData()并发时,返回的时是完整数据 return; }
//向某主机发送消息(业务层接口) void NetEngine::SendMsg( int hostID, char *msg, int msgsize ) { AutoLock lock( &m_connectsMutex ); ConnectList::iterator itNetConnect = m_connectList.find(hostID); if ( itNetConnect == m_connectList.end() ) return;//底层已经主动断开 NetConnect *pConnect = itNetConnect->second; AtomAdd(&pConnect->m_useCount, 1);//业务层先获取访问 lock.Unlock(); if ( pConnect->m_bConnect ) pConnect->SendData((const unsigned char*)msg,msgsize); pConnect->Release();//使用完毕释放共享对象 return; }
bool STNetEngine::OnConnect( SOCKET sock, bool isConnectServer ) { AtomAdd(&g_c, 1); STNetConnect *pConnect = new (m_pConnectPool->Alloc())STNetConnect(sock, isConnectServer, m_pNetMonitor, this, m_pConnectPool); if ( NULL == pConnect ) { closesocket(sock); return false; } //加入管理列表 pConnect->RefreshHeart(); AtomAdd(&pConnect->m_useCount, 1);//被m_connectList访问 AutoLock lock( &m_connectsMutex ); pair<ConnectList::iterator, bool> ret = m_connectList.insert( ConnectList::value_type(pConnect->GetSocket()->GetSocket(),pConnect) ); lock.Unlock(); //执行业务 STNetHost accessHost = pConnect->m_host;//被引擎访问,局部变量离开时,析构函数自动释放访问 m_pNetServer->OnConnect( pConnect->m_host ); /* 监听连接 必须等OnConnect完成,才可以开始监听连接上的IO事件 否则,可能业务层尚未完成连接初始化工作,就收到OnMsg通知, 导致业务层不知道该如何处理消息 */ bool bMonitor = true; if ( !m_pNetMonitor->AddMonitor(sock) ) return false; #ifdef WIN32 bMonitor = m_pNetMonitor->AddRecv( sock, (char*)(pConnect->PrepareBuffer(BUFBLOCK_SIZE)), BUFBLOCK_SIZE ); #else bMonitor = m_pNetMonitor->AddIO( sock, true, false ); #endif if ( !bMonitor ) CloseConnect(pConnect->GetSocket()->GetSocket()); return true; }
void STNetConnect::Release() { if ( 1 == AtomDec(&m_useCount, 1) ) { m_host.m_pConnect = NULL; if ( NULL == m_pMemoryPool ) { delete this; return; } this->~STNetConnect(); m_pMemoryPool->Free(this); AtomAdd(&g_r, 1); } }
//关闭一个连接 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; }
HostData* NetConnect::GetData() { if ( m_autoFreeData ) return m_pHostData; mdk::AutoLock lock(&m_mutexData); if ( NULL == m_pHostData ) return NULL; if ( !m_pHostData->m_autoFree ) { //自主模式,需要记录引用计数 if ( -1 == AtomAdd(&m_pHostData->m_refCount, 1) ) //取数据时,有线程执行了SetData(NULL)解除关联,绝对不会是Release()释放数据 { return NULL; } } return m_pHostData; }
bool NetEngine::OnConnect( SOCKET sock, bool isConnectServer ) { NetConnect *pConnect = new (m_pConnectPool->Alloc())NetConnect(sock, isConnectServer, m_pNetMonitor, this, m_pConnectPool); if ( NULL == pConnect ) { closesocket(sock); return false; } pConnect->GetSocket()->SetSockMode(); //加入管理列表 AutoLock lock( &m_connectsMutex ); pConnect->RefreshHeart(); pair<ConnectList::iterator, bool> ret = m_connectList.insert( ConnectList::value_type(pConnect->GetSocket()->GetSocket(),pConnect) ); AtomAdd(&pConnect->m_useCount, 1);//业务层先获取访问 lock.Unlock(); //执行业务 m_workThreads.Accept( Executor::Bind(&NetEngine::ConnectWorker), this, pConnect ); return true; }
//响应发送完成事件 connectState NetEngine::OnSend( SOCKET sock, unsigned short uSize ) { connectState cs = unconnect; AutoLock lock( &m_connectsMutex ); ConnectList::iterator itNetConnect = m_connectList.find(sock); if ( itNetConnect == m_connectList.end() )return cs;//底层已经主动断开 NetConnect *pConnect = itNetConnect->second; AtomAdd(&pConnect->m_useCount, 1);//业务层先获取访问 lock.Unlock();//确保业务层占有对象后,HeartMonitor()才有机会检查pConnect的状态 try { if ( pConnect->m_bConnect ) cs = SendData(pConnect, uSize); } catch(...) { } pConnect->Release();//使用完毕释放共享对象 return cs; }
bool Signal::Wait( unsigned long lMillSecond ) { bool bHasSingle = true; #ifdef WIN32 int nObjIndex = WaitForSingleObject( m_signal, lMillSecond ); if ( WAIT_TIMEOUT == nObjIndex || (WAIT_ABANDONED_0 <= nObjIndex && nObjIndex <= WAIT_ABANDONED_0 + 1) ) bHasSingle = false; #else AtomAdd(&m_waitCount, 1); unsigned long notimeout = -1; if ( notimeout == lMillSecond ) { sem_wait( &m_signal );//等待任务 } else { int nSecond = lMillSecond / 1000; int nNSecond = (lMillSecond - nSecond * 1000) * 1000; timespec timeout; timeout.tv_sec=time(NULL) + nSecond; timeout.tv_nsec=nNSecond; if ( 0 != sem_timedwait(&m_signal, &timeout) ) bHasSingle = false; } /* windows行为,在没有wait时,notify n次 之后再有多个wait,只能通过一个 linux行为,在没有wait时,notify n次 之后再有多个wait,会通过n个,第n+1个开始阻塞 简单说就是windows上第2~n个notify的信号丢失了 为了和windows行为一致,当等待线程为0时,将多余的信号丢弃 */ if ( 1 == AtomDec(&m_waitCount, 1) ) { sem_init( &m_signal, 1, 0 ); } #endif return bHasSingle; }
NetHost::NetHost(const NetHost& obj) { AtomAdd(&obj.m_pConnect->m_useCount, 1); m_pConnect = obj.m_pConnect; }
//开始发送流程 bool NetConnect::SendStart() { if ( 0 != AtomAdd(&m_nSendCount,1) ) return false;//只允许存在一个发送流程 return true; }
/** * 写入完成 * 标记写入操作写入数据的长度 * 必须与PrepareBuffer()成对调用 */ void IOBuffer::WriteFinished(unsigned short uLength) { m_pRecvBufferBlock->WriteFinished( uLength ); AtomAdd(&m_uDataSize, uLength); }
//关闭一个连接 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; }