目錄
實現功能:
- Tcp客戶端
- Tcp服務端
- 客戶端等待服務端啓動
- 服務端等待客戶端啓動
服務端實現流程:
- 鏈接相關庫
//鏈接相關庫
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)//WSAStartup返回0表示設置初始化成功
{
std::cout << "添加相關鏈接庫失敗" << std::endl;
return false;
}
- 創建監聽socket
/*創建套接字*/
//AF_INET表示IPv4,SOCK_STREAM數據傳輸方式,IPPROTO_TCP傳輸協議;
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET)
{
printf("套接字創建失敗");
closesocket(listenSocket);
return false;
}
- bind IP和端口
//綁定端口
sockaddr_in addrListen;
addrListen.sin_family = AF_INET; //指定IP格式
addrListen.sin_port = htons(20001); //綁定端口號
addrListen.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY表示任何IP
if (bind(listenSocket, (SOCKADDR*)&addrListen, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
printf("綁定失敗");
closesocket(listenSocket);
return false;
}
- 開始監聽
/*開始監聽*/
if (listen(listenSocket, 5) == SOCKET_ERROR)
{
printf("監聽出錯");
closesocket(listenSocket);
return false;
}
- 開線程,等待客戶端socket連接,直到鏈接爲止
- 連接上後,調用recv,用返回的socket進行阻塞方式數據接收
//開啓線程
_revDataThead = new std::thread(&TcpServerImpl::RevData, this);
//等待鏈接函數
Bool WaitClientConnect()
{
/*等待連接,連接後建立一個新的套接字*/
//對應此時所建立連接的套接字的句柄
sockaddr_in remoteAddr; //接收連接到服務器上的地址信息
int remoteAddrLen = sizeof(remoteAddr);
/*等待客戶端請求,服務器接收請求*/
revSocket = accept(listenSocket, (SOCKADDR*)&remoteAddr, &remoteAddrLen); //等待客戶端接入,直到有客戶端連接上來爲止
if (revSocket == INVALID_SOCKET)
{
printf("客戶端發出請求,服務器接收請求失敗:\n", WSAGetLastError());
closesocket(revSocket);
return false;
}
else
{
printf("客服端與服務器建立連接成功:%s \n", inet_ntoa(remoteAddr.sin_addr));
return true;
}
}
//數據接收線程
void TcpServerImpl::RevData()
{
while (1)//用於阻塞,重新等待鏈接(客戶端斷開後重新連接)
{
printf("等待連接...\n");
if (WaitClientConnect())
{
char revData[200];
memset(revData, 0, sizeof(revData));
while (recv(revSocket, revData, 200, 0) > 0)//用於阻塞接受多個多個客戶端,當返回值 <0 or ==0 均需要重新等待客戶端連接
{
printf("接收到客戶端發送的數據: %s\n", revData);
//收到數據TODO處理
//------------測試,收到客戶端的數據後回覆----begin-----
std::string str = "朕已閱";
char sendstr[200];
memset(sendstr, 0, 200);
memcpy(sendstr, str.c_str(), sizeof(str));
SendData(sendstr);
//------------測試-----------------------------end--------
}
}
closesocket(revSocket);
Sleep(1000);
}
}
- 實現send函數
void TcpServerImpl::SendData(char*sendData)
{
if (send(revSocket, sendData, strlen(sendData), 0) == SOCKET_ERROR)
{
printf("服務端send()出現錯誤 : %d\n", WSAGetLastError());;
}
else
{
printf("服務端發送數據:%c 成功!\n", sendData);
}
}
客戶端實現流程
- 鏈接相關庫
//鏈接相關庫
WORD sockVerson = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVerson, &wsaData) != 0)
{
return false;
}
- 開線程,創建客戶端socket,循環嘗試連接服務器
- 連接上服務器後,調用recv阻塞進入數據接收階段
//單開線程保持與服務端的聯繫,連接成功後等待服務端的消息
revDataThead = new std::thread(&TcpClientImpl::RevData,this);
bool TcpClientImpl::CreateSocketAndConnect()
{
//建立客戶端socket
clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET)
{
printf("套接字創建失敗: %d \n", WSAGetLastError());
closesocket(clientSocket);
clientSocket = NULL;
return false;
}
//定義要連接的服務器地址
sockaddr_in addrConServer;
addrConServer.sin_family = AF_INET;
addrConServer.sin_port = htons(20001);
addrConServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(clientSocket, (SOCKADDR*)&addrConServer, sizeof(addrConServer)) == SOCKET_ERROR)
{
printf("客戶端建立連接失敗!\n");
closesocket(clientSocket);
clientSocket = NULL;
return false;
}
else
{
printf("客戶端建立連接成功,準備發送數據!\n");
return true;
}
}
void TcpClientImpl::RevData()
{
while (1)//嘗試重新創建與連接(當服務端斷開再啓動後,期間需要保持嘗試連接)
{
if (CreateSocketAndConnect())
{
char revSerData[200];
memset(revSerData, 0, sizeof(revSerData));
while (recv(clientSocket, revSerData, sizeof(revSerData), 0) > 0)//阻塞進入數據接收階段,<0或==0情況均需要重新連接
{
printf("服務器發送的數據: %s\n", revSerData);
//接收到服務端數據,TODO處理
}
}
else
{
Sleep(1000);
continue;
}
closesocket(clientSocket);
clientSocket = NULL;
Sleep(1000);
}
}
- 實現send函數
bool TcpClientImpl::SendData(char* buffer)
{
//發送數據
int sendRes = send(clientSocket, buffer, (int)strlen(buffer), 0);
if (sendRes == SOCKET_ERROR)
{
printf("客戶端send()出現錯誤 : %d\n", WSAGetLastError());
return false;
}
else
printf("客戶端發送數據:%c 成功!\n", buffer);
}
運行結果
其他問題
後面遇到涉及在線程中怎麼將數據發出到其他線程或者線程以外的對象處理的問題,想了幾種方案:
- 如果用到數據的線程是等待數據接收後同步的,可以定義全局變量賦值就行了,只是要加鎖
- 當需要觸發其他線程處理的時候,可以用信號的形式觸發,實際中用過QT和boost信號實現。
- 線程以外對象,處理函數寫在對象中,開接受線程是,將對象傳入,等待接收到數據後,直接調用對象處理數據。
注:本文采用VS2015編譯