在上上篇中,我說了Winsock2的基本函數,上一篇,我們製作了一個TCP Server端,並且實現了TCP Server端要支持長數據接收和接收超時機制(地址:http://blog.csdn.net/zuishikonghuan/article/details/48027823)
這一篇呢,就再寫一個TCP Client端的實現,同樣要求這個TCP Client端要支持長數據接收和接收超時機制
由於函數已經講過了,所以socket函數不再重複,請見上一篇:[Win32] Windows Sockets 2筆記(2)基本函數。地址:http://blog.csdn.net/zuishikonghuan/article/details/48001135
超時機制比較容易,就是select函數,詳細見上一篇
要想實現長數據接收,因爲雙方無法得知對方要發送多長的數據,同時,發送和接收都是協議完成的,socket只是進行了簡單的緩衝區複製,因此無論是recv一次,還是循環recv直到出錯,都是不可取的。
有兩種解決方法:
方法1。指定一個結束符,一直接收到出現結束符爲止,這就要求正文中不能有這個結束符,如果有必須替換或者編碼
方法2。指定一個應用層協議,或自己設計一個應用層協議,能讓對方預先知道你要發多長的包,比如HTTP就在協議頭裏有一個Content-length字段,可以讓對方知道數據包有多長。
在本例中,我將採用方法2,並預定一個協議,從數據包開頭計算,直到出現第一個“@”字符,這個字符之前的數據是數據長度。
Server端可以這樣發送長數據:
send(Socket, "5005@", 5, 0);
char msg[1001] = { 0 };
RtlFillMemory(msg, 1000, 'A');
for (int i = 0; i < 5; i++)
send(Socket, msg, strlen(msg), 0);
Client端源代碼如下:
#include <winsock2.h>
#pragma comment(lib , "Ws2_32.lib")
#include <Windows.h>
//等待接收操作
//參數1:套接字描述符 參數2:等待的秒數
BOOL WaitRecv(SOCKET s, int timeout){
fd_set nfds;
RtlZeroMemory(&nfds, sizeof(fd_set));
nfds.fd_array[0] = s;
nfds.fd_count = 1;
timeval time = { timeout, 0 };
if (select(0, &nfds, NULL, NULL, &time) <= 0){
return FALSE;
}
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
SOCKET Socket;
sockaddr_in addr;
if (WSAStartup(wVersionRequested, &wsaData) != 0){
MessageBox(NULL, TEXT("Winsock開啓失敗"), TEXT("錯誤"), MB_ICONERROR);
return 1;
}
Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (Socket == INVALID_SOCKET)
{
MessageBox(NULL, TEXT("Socket創建失敗"), TEXT("錯誤"), MB_ICONERROR);
return 1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//服務器地址
if (connect(Socket, (sockaddr*)&addr, sizeof(sockaddr_in)) == SOCKET_ERROR)
{
MessageBox(NULL, TEXT("Socket連接到服務器失敗"), TEXT("錯誤"), MB_ICONERROR);
exit(1);
}
send(Socket, "2@", 2, 0);
char msg[1001] = { 0 };
int readlen = 0;
int addrlen = sizeof(sockaddr_in);
RtlZeroMemory(msg, 1001);
if (!WaitRecv(Socket, 12)){//等待接收
closesocket(Socket);
return 0;
}
int r = recv(Socket, msg, 1000, 0);
int len = 0;
if (r > 0){
unsigned int x = (unsigned int)strstr(msg, "@");//找到第一個出現@的位置,按照預先的協議,@之前是完整的數據長度
if (x != 0){
msg[x - (unsigned int)msg] = '\0';//將@換成字符串結束符
len = atoi(msg);//獲取長度
msg[x - (unsigned int)msg] = '@';//替換回來
readlen = r;//已經讀取的字節
MessageBoxA(NULL, msg, "收到數據", 0);
while (readlen < len){//一直接收,直到達到我們認爲的長度
RtlZeroMemory(msg, 1001);
if (!WaitRecv(Socket, 2))break;//超時
r = recv(Socket, msg, 1000, 0);
if (r <= 0)break;
readlen += r;
MessageBoxA(NULL, msg, "收到數據", 0);
}
}
else{
closesocket(Socket);
}
}
MessageBoxA(NULL, "數據接收完成", "", 0);
closesocket(Socket);
return 0;
}