接下來講一下如何用WinSock建立基於TCP/IP模型的客戶端和服務器。編程
TCP能夠提供兩個計算機間可靠無誤的數據傳輸,應用程序使用TCP通訊時,會在兩臺計算機之間創建一個虛擬鏈接,鏈接以後計算機之間變能夠以雙向字節流進行數據交換。windows
下面說下簡單的發送數據的客戶端實現.api
建立客戶機的鏈接比較簡單:服務器
struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory( &hints, sizeof(hints) ); hints.ai_family = AF_UNSPEC; //能夠是IPv4或IPv6地址 hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP;
調用getaddrinfo函數肯定服務器ip地址(由命令行參數傳遞)和端口網絡
#define DEFAULT_PORT "27015" // Resolve the server address and port iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed: %d\n", iResult); WSACleanup(); return 1; }
socket原型:異步
socket( socket
IN int af, //協議的地址族,使用IPv4來描述Winsock,設置爲AF_INET tcp
IN int type, //套接字類型,TCP/IP設置爲SOCK_STREAM,UDP/IP設置爲SOCK_DGRAM 函數
IN int protocol //用於給定地址族和類型具備多重入口的傳送限定,TCP設置爲IPPROTO_TCP,UDP設置爲IPPROTO_UDP this
);
調用socket建立嵌套字,錯誤檢測
SOCKET ConnectSocket = INVALID_SOCKET; ptr=result; ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; }
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } //釋放資源 freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; }
客戶端程序在建立套節字以後,要使用 connect函數請求與服務器鏈接,connect原型:
int WSAAPI connect(
IN SOCKET s, //要創建鏈接的socket
IN const struct sockaddr FAR * name, //指向保存要創建鏈接信息的地址結構
IN int namelen //參數2指向地址結構的大小
);
收發數據纔是網絡編程的主題,在已經創建鏈接的套接字上發生數據可使用send或WSASend(WinSock2中),接受可用recv和WSARecv。收發數據都是用char類型(面向字節的數據)。
#define DEFAULT_BUFLEN 512 int recvbuflen = DEFAULT_BUFLEN; char *sendbuf = "this is a test"; char recvbuf[DEFAULT_BUFLEN]; int iResult; iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("send failed: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Bytes Sent: %ld\n", iResult); iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if (iResult > 0) printf("Bytes received: %d\n", iResult); else if (iResult == 0) printf("Connection closed\n"); else printf("recv failed: %d\n", WSAGetLastError()); } while (iResult > 0);
全部收發數據返回的錯誤代碼都是SOCKET_ERROR,一旦有錯誤返回,系統就會調用WSAGetLastError()獲取詳細錯誤信息。
常見錯誤:
WSAECONNABORTED和WSAECONNRESET:鏈接正在關閉(超時或者因爲通訊放正在關閉鏈接)
WSAEWOULDBLOCK:套接字處於非阻塞模式或異步狀態。
使用send和recv函數原型。
int send(
SOCKET s, // 套節字句柄
const char FAR* buf,// 要發送的數據的緩衝區的地址
int len, // 緩衝區的長度
int flags // 指定了的調用方式,一般設爲0
);
int recv(
SOCKET s,
char FAR* buf,
int len,
int flags
);
send函數在一個鏈接的套節字上發送緩衝區內的數據,返回發送數據的實際字節數,recv函數從對方接收數據,並存儲它到指定的緩衝區。flag參數在這兩個函數中一般設爲0。
在阻塞模式下,send將會阻塞線程的執行直到全部的數據發送完畢(或者一個錯誤的發生),而recv函數將返回儘量多的當前信息,一直到緩衝區指定的大小。
函數執行失敗返回INVALID_SOCKET(-1),應該調用closesocket函數將它關閉。若是沒有錯誤發生,函數返回0,不然返回SOCKET_ERROR。函數用法以下:
int closesocket(
__in SOCKET s // 函數惟一的參數就是要關閉的套節字的句柄
);
iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } closesocket(ConnectSocket); WSACleanup();
附完整的客戶端代碼:
#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> #include <WinSock2.h> #include <WS2tcpip.h> #include <IPHlpApi.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "Mswsock.lib") #pragma comment(lib, "Advapi32.lib") #define DEFAULT_PORT "27015" #define DeFAULT_BUFLEN 512 int main(int argc, char* argv[]) { WSADATA wsaData; int iResult; struct addrinfo *result = NULL, *ptr = NULL, hints; char *sendbuf = "this is a test"; char recvbuf[DeFAULT_BUFLEN]; int recvbuflen = DeFAULT_BUFLEN; SOCKET ConnectSocket = INVALID_SOCKET; if (argc != 2) { printf("usage: %s server-name\n", argv[0]); } // 初始化winsock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // 肯定服務器地址和端口 iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed: %d\n", iResult); WSACleanup(); return 1; } // 嘗試鏈接到服務器地址,直到成功 for (ptr = result; ptr != NULL; ptr = ptr->ai_next) { // 建立套接字鏈接到服務器 ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // 鏈接服務器 iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; continue; } break; } // 釋放資源 freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } // 發送sendbuf內容 iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf) + 1, 0); if (iResult == SOCKET_ERROR) { printf("send failed:%d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Byte Send:%ld\n", iResult); // 數據發送完成以後斷開鏈接 iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown faild: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // 收到迴應以後關閉鏈接 do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if (iResult > 0) printf("Byte received: %d\n", iResult); else if (iResult == 0) printf("connection closed\n"); else printf("recv failed: %d\n", WSACleanup()); } while (iResult > 0); // 清除套接字 closesocket(ConnectSocket); WSACleanup(); return 0; }