Socket 的传输的内容大概分3种:
-
封装的结构体:结构体(结构清晰,发送数据占用内存小),例如
struct SOCKETDATA
{
DWORD password; //每个客户端都有一个密码,为了防止外挂
DWORD messageId; //发送内容的ID标识,每种ID对应着消息的一种操作
DWORD nowParkId; //连续的ID号,防止丢包,和重复的发同样的包的操作
Char* buffer; //内容
}此处也是对内容的简单的描述,真正的结构体可能更加复杂
-
char*(显而易见的,支持指令),例如
发送的字符串为"23,张三,10000",这样的内容的弊病在于很容易别解析,安全性低
- 超文本(支持脚本,类似xml,内存使用增加).可以结合1来综合运用,思路是很好的。
收发机制:封包,粘包。Send SendList, recv RecvList
向服务器一直发送数据的话,发送的内容不平均,有时内容较大,有时内容较小,一直连接对链路的负载也较大,所以我们在固定的时间间隔下发送数据,把要发送的数据存在一个链表中,集体发送,这样大大提高性能,发送的时间间隔根据游戏的情况而定,魔兽争霸为100ms,魔兽世界为200ms。
线程管理:
1、确保主线程不死
2、一段时间间隔看子线程是否死亡
3、Socket类封装:
ServerSocket
ClientSocket
NetCom (消息底层,可用socket或是DirectPlay等等)
NetMessage(消息宏和结构体)
NetManager(消息处理)
4、Socket安全
5、Socket其他功能:连包,粘包
时间问题:加时间戳,逻辑不通过时间判断
总结 Socket服务器端的设计:
服务器还是利用Socket的完成端口来实现,首先创建服务器的Socket。
然后创建Accept线程,当有客户端accept时,加入到客户端列表中,然后异步调用WSARecv。
根据cpu个数创建接受数据的线程,接收的数据存储到链表中。
处理接受到的数据时用一个单独的线程遍历其中的内容,用信号量控制。
#define MAXMESSAGESIZE 1024 enum SOCKETOPERATE { soRECV }; struct SOCKETDATA { WSAOVERLAPPED overlap; //重叠结构,用于异步请求的IO控制 WSABUF Buffer; //缓存,用于异步请求数据的保存 char sMessage[MAXMESSAGESIZE]; //真正的缓存 DWORD dwBytes; //异步请求发生时,产生的字节流量 DWORD Flages; SOCKETOPERATE OperationType; //异步请求的操作类型 void Clear(SOCKETOPERATE SO) { ZeroMemory(this, sizeof(SOCKETDATA)); Buffer.len = MAXMESSAGESIZE; Buffer.buf = sMessage; OperationType = SO; } }; struct CClientPeer { SOCKET ClientSocket; DWORD ID; DWORD PassWord; DWORD NowPackID; DWORD dwIP; u_short Port; CEasyList *gClientRecv; void InitList() { gClientRecv = new CEasyList; } }; class CServerSocket { public: CServerSocket(void); ~CServerSocket(void); bool Create(u_short Port); int SendBuffer(int ClientID, void* Buffer, int Size); int BroadCastBuff(void* Buffer, int Size); //广播 public: SOCKET mSocket; CEasyList mClients; HANDLE mCP; u_short mPort; }; SOCKET CreateServerSocket(int Port); DWORD WINAPI SocketProcMain(LPVOID lpParam); //Socket主线程函数,负责处理IO请求 DWORD WINAPI SocketProcAccept(LPVOID lpParam); //Socket线程函数,负责处理线程链接 CServerSocket::CServerSocket(void) { WSADATA wsaData; WSAStartup(0x0202,&wsaData); mSocket=INVALID_SOCKET; mCP = INVALID_HANDLE_VALUE; } CServerSocket::~CServerSocket(void) { closesocket(mSocket); WSACleanup(); } SOCKET CreateServerSocket(int Port) { int iErrCode; WSADATA wsaData; iErrCode = WSAStartup(0x0202, &wsaData); int iRes; SOCKET tempSocket = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addr; addr.sin_family = AF_INET; addr.sin_port = htons(Port); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); iRes = bind(tempSocket,(LPSOCKADDR)&addr,sizeof(addr)); if(iRes == SOCKET_ERROR) { closesocket(tempSocket); return INVALID_SOCKET; } iRes = listen(tempSocket, 2000); if(iRes == SOCKET_ERROR) { closesocket(tempSocket); return INVALID_SOCKET; } return tempSocket; } bool CServerSocket::Create( u_short Port ) { mSocket = CreateServerSocket(Port); if (mSocket == INVALID_SOCKET) { return false; } mPort = Port; mCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); for (int i = 0; i<systemInfo.dwNumberOfProcessors;i++) { CreateThread(NULL, NULL, SocketProcMain, this, NULL, NULL); } CreateThread(NULL, NULL, SocketProcAccept, this, NULL, NULL); Sleep(5); return true; } DWORD WINAPI SocketProcMain(LPVOID lpParam) { HANDLE CP = ((CServerSocket*)lpParam)->mCP; SOCKET serverSocket = ((CServerSocket*)lpParam)->mSocket; DWORD dwBytes; SOCKETDATA *lpSocketData = NULL; while (1) { //等待全局完成端口的玩曾队列 CClientPeer *pClient=NULL; GetQueuedCompletionStatus(CP, &dwBytes, (PULONG_PTR)&pClient, (LPOVERLAPPED*)&lpSocketData, INFINITE); if(dwBytes == 0xffffffff) { return 0; } //当数据类型为soRECV时, if(lpSocketData->OperationType == soRECV) { //当客端关闭连接时 if(dwBytes == 0) { closesocket(serverSocket); HeapFree(GetProcessHeap(), 0, lpSocketData); } else { char* s = new char[dwBytes+1]; strcpy(s, lpSocketData->sMessage); pClient->gClientRecv->Add(s); /*printf("Server:%d Client:%d :%s ", ((CServerSocket*)lpParam)->mPort, pClient->dwIP/0x1000000, lpSocketData->sMessage);*/ //重置Socket数据为soRECV lpSocketData->Clear(soRECV); WSARecv(pClient->ClientSocket, &lpSocketData->Buffer, 1, &lpSocketData->dwBytes, &lpSocketData->Flages, &lpSocketData->overlap, NULL); } } } } DWORD WINAPI SocketProcAccept(LPVOID lpParam) { SOCKET tmp; SOCKADDR_IN tempAddr; int dwAddrSize = sizeof(tempAddr); HANDLE CP = ((CServerSocket*)lpParam)->mCP; SOCKET serverSocket = ((CServerSocket*)lpParam)->mSocket; SOCKETDATA *lpSocketData; while(1) { tmp = accept(serverSocket, (sockaddr*)&tempAddr, &dwAddrSize); CClientPeer *pClient = NULL; pClient = (CClientPeer*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CClientPeer)); pClient->InitList(); pClient->ClientSocket = tmp; pClient->dwIP = tempAddr.sin_addr.s_addr; pClient->Port = tempAddr.sin_port; ((CServerSocket*)lpParam)->mClients.Add(pClient); //创建客户端Socket和全局完成端口的链接 CreateIoCompletionPort((HANDLE)tmp, CP, (DWORD)pClient, 0); //在主线程堆中开辟,Socket缓存数据 lpSocketData = (SOCKETDATA *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SOCKETDATA)); //将此数据初始化为soRecv lpSocketData->Clear(soRECV); //异步调用WSARecv,其中第二个参数是I/O请求成功时,数据保存的地址。 WSARecv(tmp, &lpSocketData->Buffer, 1, &lpSocketData->dwBytes, &lpSocketData->Flages, &lpSocketData->overlap, NULL); } } class CNetManager { public: CNetManager(void); ~CNetManager(void); bool CreateServer(); private: CServerSocket mServerSocket; }; DWORD WINAPI RecvProc(LPVOID pParam); bool CNetManager::CreateServer() { mServerSocket.Create(6565); CreateThread(NULL, NULL, RecvProc, &mServerSocket, NULL, NULL); return 1; } DWORD WINAPI RecvProc( LPVOID pParam ) { if(pParam == NULL) { return 0; } CServerSocket *pServer = (CServerSocket*)pParam; CClientPeer *pClient; while(1) { for (int i=0; i<pServer->mClients.Count();i++) { pClient = (CClientPeer*)pServer->mClients.Get(i); if (pClient) { for (int j=0; j<pClient->gClientRecv->Count();j++) { char* s = (char*)pClient->gClientRecv->Get(j); printf("%x (%d) %s ",pClient->dwIP,j,s); delete s; } pClient->gClientRecv->Clear(); } } } }