本文共 4595 字,大约阅读时间需要 15 分钟。
连接
接收
发送
在《》中介绍了Windows下Socket编程的一些基本知识与服务端实现,现在介绍一下客户端的实现。相比于服务端,客户端流程相对简单些,主要就是:
连接服务端;
收发消息
此客户端实现,除发送接口外,其他的都使用IOCP(I/O Completion Port,I/O完成端口)接口WSAXXX
。IOCP是性能良好的I/O模型,可以支持大并发(通过完成端口,避免大量线程的创建),更适合在服务端使用。
等待服务端应答及退出事件,都是通过C++条件变量实现的(参见《》)
XuEvent g_evtQuit;XuEvent g_evtAck;
与服务端相似,在使用socket前,需要先通过initSocket
初始化,并在退出前做清理工作。
SOCKET openConnection(const string &strHost, unsigned short nPort, functionfunError, function funMsg) { string strError; SOCKADDR_IN sockAddr; SOCKET sockClient = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (INVALID_SOCKET == sockClient) { goto FUN_CLEANUP; } // to connect sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(nPort); inet_pton(AF_INET, strHost.c_str(), &(sockAddr.sin_addr)); if (SOCKET_ERROR == WSAConnect(sockClient, (SOCKADDR*)&sockAddr, sizeof(sockAddr), nullptr, nullptr, nullptr, nullptr)) { goto FUN_CLEANUP; } g_bStop = false; { thread thrRecv(receiveMsg, sockClient, funError, funMsg); thrRecv.detach(); thread thrAlive(heartBeat, sockClient, funError); thrAlive.detach(); } return sockClient;FUN_CLEANUP: int nError = WSAGetLastError(); if (nullptr != funError) { funError(strError, nError); } return INVALID_SOCKET;}
接收消息使用的完成端口WSARecv实现的,接受者通过通过事件等待,在有消息到达时系统会主动通知。
接收返回WSA_IO_PENDING
错误时,不是真正的错误,只是告诉上层当前未收到消息,需要等待,在有消息到达时会触发通知。
void receiveMsg(SOCKET sock, functionfunError, function funMsg) { int nPort = 0; string strAddr = XuNet::peerAddress(sock, &nPort); cout << "Connected " << strAddr << ":" << nPort << endl; string strError; const int MaxBufSize = 255; char szBuff[MaxBufSize + 1] = { 0 }; int nRet = 0, nRecvError = 0; WSABUF wsaBuf; WSAOVERLAPPED overLapped; ZeroMemory(&overLapped, sizeof(overLapped)); overLapped.hEvent = WSACreateEvent(); if(NULL == overLapped.hEvent){ nRecvError = WSAGetLastError(); strError = "WSACreateEvent"; goto FUN_CLEANUP; } wsaBuf.buf = szBuff; wsaBuf.len = MaxBufSize; while (!g_bStop) { DWORD dwFlag = 0; DWORD dwRecved = 0; nRet = WSARecv(sock, &wsaBuf, 1, &dwRecved, &dwFlag, &overLapped, NULL); if (SOCKET_ERROR == nRet) { nRecvError = WSAGetLastError(); if (WSA_IO_PENDING != nRecvError) { strError = "WSARecv"; goto FUN_CLEANUP; } nRecvError = 0; } nRet = WSAWaitForMultipleEvents(1, &overLapped.hEvent, TRUE, INFINITE, FALSE); if (WSA_WAIT_FAILED == nRet) { nRecvError = WSAGetLastError(); strError = "WSAWaitForMultipleEvents"; goto FUN_CLEANUP; } nRet = WSAGetOverlappedResult(sock, &overLapped, &dwRecved, FALSE, &dwFlag); if(FALSE == nRet){ nRecvError = WSAGetLastError(); strError = "WSAGetOverlappedResult"; goto FUN_CLEANUP; } if (dwRecved > 0) { string strMsg(szBuff, dwRecved); if (strMsg.substr(0, 3) == MSG_Ack) { g_evtAck.notifyAll(); } else if (nullptr != funMsg) { funMsg(strMsg); } } WSAResetEvent(overLapped.hEvent); }FUN_CLEANUP: WSACloseEvent(overLapped.hEvent); if (nullptr != funError) { funError(strError, nRecvError); }}
通过定时向服务端发送心跳包,及时发现连接问题(服务端会根据心跳,做超时处理)。发送完消息后,会等待服务端做一个应答,只有接收到服务端应答后才会真正确认发送成功。
bool sendMessage(SOCKET sock, string strMsg, int nWaitSecs) { strMsg += MSG_Delim; g_evtAck.reset(); int nLen = send(sock, strMsg.c_str(), strMsg.length(), 0); if (SOCKET_ERROR == nLen) { int nError = WSAGetLastError(); cout << "!!!Send " << strMsg << " fail: " << nError << endl; return false; } return g_evtAck.wait(nWaitSecs);}
心跳包:
void heartBeat(SOCKET sock, functionfunError) { int nLostAck = 0; int nCount = 0; while (!g_bStop) { if (sendMessage(sock, MSG_Alive + std::to_string(++nCount), 1)) { nLostAck = 0; } else { if (++nLostAck > HeartBeat_MaxLost) { if (nullptr != funError) { funError("too many Ack of heartBeat lost", -1); } break; } } if (g_evtQuit.wait(HeartBeat_Seconds)) { cout << "evtQuit got signal" << endl; break; } } closesocket(sock);}
转载地址:http://jgnlf.baihongyu.com/