连接
接收
发送
在《[C++]-Windows下Socket连接之服务端》中介绍了Windows下Socket编程的一些基本知识与服务端实现,现在介绍一下客户端的实现。相比于服务端,客户端流程相对简单些,主要就是:
-
连接服务端;
-
收发消息
此客户端实现,除发送接口外,其他的都使用IOCP(I/O Completion Port,I/O完成端口)接口WSAXXX
。IOCP是性能良好的I/O模型,可以支持大并发(通过完成端口,避免大量线程的创建),更适合在服务端使用。
等待服务端应答及退出事件,都是通过C++条件变量实现的(参见《条件变量condition_variable存在的一些问题》)
XuEvent g_evtQuit;
XuEvent g_evtAck;
连接
与服务端相似,在使用socket前,需要先通过initSocket
初始化,并在退出前做清理工作。
连接时,先设定要连接的地址与端口,然后发起连接,成功后进行收发处理即可。
SOCKET openConnection(const string &strHost, unsigned short nPort,
function<void(const string&, int)> funError,
function<void(const string&)> 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,
function<void(const string&, int)> funError,
function<void(const string&)> 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, function<void(const string&, int)> funError) {
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);
}