共享内存 TCPSvrd LogSvrd 二、对象内存管理模型 TCPSvrd服务采用完成端口模式。主要处理单句柄数据和单操作数据。它们分别由CPERIOHandleData和CPERIOData两个对象来实现。下面是几个主要对象的结构图:
CPERIOHandleDataManager CPERIODataManager CObjectManager
CTCPSvrd CPERIOHandleDataManager Client CPERIODataManager
CPERIOHandleDataManager CPERIOHandleData CObjectID CBaseObject 对完成端口工作线程的代码如下: DWORD WINAPI ServerWorkerThread(LPVOID lParam) { CTCPSvrd* pTCPSvrd = (CTCPSvrd*)lParam; CPERIOHandleData* pHandleData = NULL; CPERIOData* pIOData = NULL; LPOVERLAPPED lpOverlapped; DWORD dwBytesTransfered = 0; DWORD dwRecvBytes =0; DWORD dwFlags = 0; int state = 0; while (TRUE) { int nRet = GetQueuedCompletionStatus(pTCPSvrd->m_hCompetionPort, &dwBytesTransfered, (DWORD*)&pHandleData, &lpOverlapped, INFINITE); if (FALSE == nRet && NULL == lpOverlapped) { printf("GetQueueCompletionStautus Error/n"); return 0; } if (FALSE == nRet && lpOverlapped != NULL) { printf("Client Except Exit/n"); pHandleData->SetOffLine(); continue; } if (dwBytesTransfered == 0) { printf("Client Normal Exit/n"); pHandleData->SetOffLine(); continue; } pIOData = CONTAINING_RECORD(lpOverlapped, CPERIOData, m_OVerlapped); switch(pIOData->m_nIOType) { case IORead: pTCPSvrd->RecvData(pHandleData, pIOData, dwBytesTransfered); break; case IOWrite: pTCPSvrd->SendData(pHandleData, pIOData, dwBytesTransfered); break; } } return 0; } 三、数据管理通道 两个进程间的数据是通过共享内存的方式传递的,本模块就是操作这个数据区域,从而实现数据的传递。实现如下: class CDataQueue { typedef struct tagQueueHead { int nTotalSize; int nWriteBegin; int nReadBegin; }QueueHead; public: CDataQueue(); CDataQueue(int nBufSize); ~CDataQueue(); public: void* operator new(size_t nSize); void operator delete(void *pMem); void Initialize(int nBufSize); public: static size_t GetQueueSize(size_t nBufSize); static CShareMemory* m_pAttackMem; public: int PushOneCode(const BYTE* pCode, unsigned short nSize); int PeekOneCode(BYTE* pCode, unsigned short& nSize); BOOL IsFull(); BOOL IsEmpty(); int GetUseLen(); int GetUsedLen(); public: QueueHead m_QueueHead; BYTE* m_pDataMem; }; 四、TCP封包拆包 一、为什么基于TCP的通讯程序需要进行封包和拆 TCP是个"流"协议,,谓流,就是没有界限的一串串数据.,一般通讯程序开发是需要定义一个个相互独立的数据包的,比如用于登陆的数据包,用于注销的数据包.由于TCP"流"的特性以及网络状况,在进行数据传输时会出现以下几种情况: 假设我们连续调用两次send分别发送两段数据data1和data2,在接收端有以下几种接收情况: A.先接收到data1,然后接收到data2。 B.先接收到data1的部分数据,然后接收到data2余下的部分以及data2的全部。 C.先接收到了data1的全部数据和data1的部分数据,然后接收到了data2的余下的数据。 D.一次性接收到了data1和data2的全部数据.。 对于A这种情况正是我们需要的,不再做讨论.对于B,C,D的情况就是大家经常说的"粘包",就需要我们把接收到的数据进行拆包,拆成一个个独立的数据包.为了拆包就必须在发送端进行封包. 另:对于UDP来说就不存在拆包的问题,因为UDP是个"数据包"协议,也就是两段数据间是有界限的,在接收端要么接收不到数据要么就是接收一个完整的一段数据,不会少接收也不会多接收。 二、为什么会出现B.C.D的情况. "粘包"可发生在发送端也可发生在接收端. 1.由Nagle算法造成的发送端的粘包,Nagle算法是一种改善网络传输效率的算法.简单的说,当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去.这是对Nagle算法一个简单的解释,详细的请看相关书籍.象C和D的情况就有可能是Nagle算法造成的. 2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据.当应用层由于某些原因不能及时的,TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。 对于完成端口,我们可以提交接收包头长度的数据的请求,当GetQueuedCompletionStatus返回时,我们判断接收的数据长度是否等于包头长度,若等于,则提交接收包体长度的数据的请求,若不等于则提交接收剩余数据的请求。下面相关的代码。 void CTCPSvrd::RecvData(CPERIOHandleData* pHandleData, CPERIOData* pIOData, DWORD dwBytesTransfered) { int nRet = 0; DWORD dwRecvBytes = 0; DWORD dwFlags = 0; if (pIOData->m_DataBuf.len == dwBytesTransfered ) { if( pIOData->m_bPackHead == TRUE ) { PACKAGE_HEAD *pPackageHead = (PACKAGE_HEAD *)(pIOData->m_Buffer); pIOData->m_bPackHead = FALSE; pIOData->m_DataBuf.len = pPackageHead->wDataLen; pIOData->m_dwRecvBytes += dwBytesTransfered; pIOData->m_DataBuf.buf = pIOData->m_Buffer + pIOData->m_dwRecvBytes; printf("recv head. ver = %d, com = %d, len = %d/n", pPackageHead->wVersion, pPackageHead->wCommand, pPackageHead->wDataLen); dwFlags = 0; pIOData->m_nIOType = IORead; nRet = WSARecv(pHandleData->GetClietnSocket(), &(pIOData->m_DataBuf), 1, &dwRecvBytes, &dwFlags, &pIOData->m_OVerlapped, NULL); } else///接收到的是包体 { pIOData->m_dwRecvBytes += dwBytesTransfered; char* p = pIOData->m_Buffer; p += sizeof(PACKAGE_HEAD); printf("recv Finished. nCount = %d, %s/n", pIOData->m_dwRecvBytes, p); //记录这次的句柄和数据ID PACKAGE_HEAD* pPackHead = (PACKAGE_HEAD*)pIOData->m_Buffer; pPackHead->wIOHandleID = pHandleData->GetObjectID(); pPackHead->wIODataID = pIOData->GetObjectID(); nRet = m_pC2SMsgBuf->PushOneCode((BYTE*)pIOData->m_Buffer, (unsigned short)pIOData->m_dwRecvBytes); printf("push one code Return value = %d/n", nRet); //进行下一次投递 RecvHeadData(pHandleData->GetClietnSocket()); } } else///接收的数据还不完整 { printf("recv continue/n"); pIOData->m_DataBuf.len -= dwBytesTransfered; pIOData->m_dwRecvBytes += dwBytesTransfered; pIOData->m_DataBuf.buf = pIOData->m_Buffer + pIOData->m_dwRecvBytes; dwFlags = 0; pIOData->m_nIOType = IORead; nRet = WSARecv(pHandleData->GetClietnSocket(), &(pIOData->m_DataBuf), 1, &dwRecvBytes, &dwFlags, &pIOData->m_OVerlapped, NULL); } } void CTCPSvrd::SendData(CPERIOHandleData* pHandleData, CPERIOData* pIOData, DWORD dwBytesTransfered) { int nRet = 0; DWORD dwSendBytes = 0; if (pIOData->m_DataBuf.len == dwBytesTransfered ) { if( pIOData->m_bPackHead == TRUE ) { PACKAGE_HEAD *pPackageHead = (PACKAGE_HEAD *)(pIOData->m_Buffer); memset(&pIOData->m_OVerlapped, 0, sizeof(OVERLAPPED)); pIOData->m_bPackHead = FALSE; pIOData->m_DataBuf.len = pPackageHead->wDataLen; pIOData->m_dwSendBytes += dwBytesTransfered; pIOData->m_DataBuf.buf = pIOData->m_Buffer + pIOData->m_dwSendBytes; printf("send head. ver = %d, com = %d, len = %d/n", pPackageHead->wVersion, pPackageHead->wCommand, pPackageHead->wDataLen); pIOData->m_nIOType = IOWrite; nRet = WSASend(pHandleData->GetClietnSocket(), &(pIOData->m_DataBuf), 1, &dwSendBytes, 0, &pIOData->m_OVerlapped, NULL); } else///发送的是包体 { pIOData->m_dwSendBytes += dwBytesTransfered; printf("Send Finished. nCount = %d/n", pIOData->m_dwSendBytes); //清空该缓冲区,供下一次全用 pIOData->ClearData(); } } else///发送的数据还不完整 { printf("send continue/n"); pIOData->m_DataBuf.len -= dwBytesTransfered; pIOData->m_dwSendBytes += dwBytesTransfered; pIOData->m_DataBuf.buf = pIOData->m_Buffer + pIOData->m_dwSendBytes; pIOData->m_nIOType = IOWrite; nRet = WSASend(pHandleData->GetClietnSocket(), &(pIOData->m_DataBuf), 1, &dwSendBytes, 0, &pIOData->m_OVerlapped, NULL); } } 上面仅实现了TCPSvrd,在后面的学习中我将会主要实现LogSvrd中服务器之间的通信。这是我近期学习Windows网络编程的一点总结。希望大家给出好的建议。
