Windows網路通訊(一):socket同步程式設計
網路通訊常用API
1. WSAStartup用於初始化WinSock環境
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData );wVersionRequested:當前程序能夠使用Windows Socket的最高版本,目前指定2.2即可。
lpWSAData:指向一個WSAData結構體,接受Socket詳細資訊。
成功返回0
2. socket建立一個指定型別的SOCKET用於通訊
SOCKET WSAAPI socket( int af, int type, int protocol );af:address family,指定用於通訊的網路地址型別,可以取值AF_INET(IPv4),AF_INET6(IPv6),AF_BTH(藍芽)等。
type:指定傳輸型別,可以取值SOCK_STREAM(用於TCP),SOCK_DGRAM(用於UDP)等。
protocol:通訊協議,可以取值IPPROTO_TCP,IPPROTO_UDP等。
成功返回一個可以用於通訊的SOCKET,否則返回INVALID_SOCKET。
3. bind將socket和網路地址和埠繫結起來
int bind( SOCKET s, const structsockaddr *name, int namelen );s:一個未繫結的socket。
name:指向一個sockaddr物件,用於指定繫結的ip和埠資訊。
namelen:sockaddr的長度,為什麼這裡還需要指定長度呢,因為name是根據socket的型別來指定不同的結構體的,可能是sockaddr_in(IPv4)或者sockaddr_in6(IPv6)。
成功返回0
4. listen將SOCKET設為監聽狀態,可以被客戶端連線
int listen( SOCKET s, int backlog );s:一個未被連線的socket
backlog:可以連線的客戶端的最大數目,如果指定為SOMAXCONN,則設定為最大的連線數量。
成功返回0
5. send通過指定socket傳送資料
int send( SOCKET s, const char *buf, int len, int flags );s:一個已經連線的socket。
buf:待發送資料
len:待發送資料的長度
flags:傳送的一個標誌設定,一般設為0
成功返回已傳送的位元組數目。這個數目可能小於len的。失敗返回SOCKET_ERROR。
6. recv通過指定的socket接受資料
int recv( SOCKET s, char *buf, int len, int flags );s:一個已經連線的socket
buf:接收資料的快取區
len:快取區長度
flags:接受資料的一個標誌,一般設為0。
成功返回已接受資料的長度,失敗返回SOCKET_ERROR,如果已經斷開連線,返回0
7. shutdown關閉一個SOCKET的send或者recv功能
int shutdown( SOCKET s, int how );s:socket
how:指定該socket的某個功能不需要再使用,可以取值SD_RECEIVE(接收功能),SD_SEND(傳送功能),SD_BOTH(傳送和接收功能)。
成功返回0,失敗返回SOCKET_ERROR
8. connect連線到服務端,服務端開啟listen後,客戶端就可以使用connect進行連線
int connect( SOCKET s, const struct sockaddr *name, int namelen );s:一個未連線的socket
name,namelen:和bind中name,namelen引數一樣
成功返回0,失敗返回SOCKET_ERROR
9. closesocket關閉一個已經存在的socket
int closesocket( SOCKET s );s:一個待關閉的socket。
成功返回0,失敗返回SOCKET_ERROR
10. accept接收一個來自客戶端的連線
SOCKET accept( SOCKET s, struct sockaddr *addr, int *addrlen );s:一個已經listen的socket
addr:用於儲存接收到的客戶端的sockaddr資訊
addrlen:連線的客戶端的sockaddr長度。
socket通訊示例
服務端和客戶端測試程式碼
// NetWork1.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #include <stdio.h> #include <tchar.h> #include <winsock2.h> #include <ws2tcpip.h> #pragma comment(lib, "ws2_32.lib") #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT 12345 //啟動客戶端 int startClient() { SOCKET ConnectSocket = INVALID_SOCKET; struct sockaddr_in clientService; char *sendbuf = "[Client]:客戶端測試文字"; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN; // 建立一個TCP套接字 ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ConnectSocket == INVALID_SOCKET) { printf("socket建立失敗: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 指定連線埠和ip資訊 clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(DEFAULT_PORT); // 連線服務端 iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService)); if (iResult == SOCKET_ERROR) { printf("連線失敗: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } if (ConnectSocket == INVALID_SOCKET) { printf("無法連線到指定服務端!\n"); WSACleanup(); return 1; } // 傳送一段資料 iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("傳送資料失敗: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("已傳送資料大小: %ld\n", iResult); // 關閉傳送功能,但是仍然可以接收 iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("關閉傳送功能失敗: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // 接收資料 do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if (iResult > 0) printf("已接收: %d\n", iResult); else if (iResult == 0) printf("連線關閉\n"); else printf("接收資料失敗: %d\n", WSAGetLastError()); } while (iResult > 0); //關閉套接字 closesocket(ConnectSocket); WSACleanup(); return 0; } int startServer() { int iResult; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; sockaddr_in service; int iSendResult; char recvbuf[DEFAULT_BUFLEN]; int recvbuflen = DEFAULT_BUFLEN; char *sendbuf = "[Server]:服務端測試文字"; // 建立TCP套接字 ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket建立失敗: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 設定繫結的ip和埠資訊 service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr("127.0.0.1"); service.sin_port = htons(DEFAULT_PORT); // 繫結套接字 iResult = bind(ListenSocket, (SOCKADDR *)&service, sizeof(service)); if (iResult == SOCKET_ERROR) { printf("套接字繫結失敗:%d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } iResult = listen(ListenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { printf("套接字監聽失敗: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // 接受來自客戶端的連線 ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // 關閉服務端套接字,表示不需要再接收新的客戶端連線了,但是已經連線的套接字還是能通訊 closesocket(ListenSocket); do { //接收來自客戶端的訊息 iResult = recv(ClientSocket, recvbuf, recvbuflen, 0); if (iResult > 0) { printf("已接收資料大小: %d\n", iResult); // 傳送資料到客戶端,這裡就是將資料 iSendResult = send(ClientSocket, sendbuf, (int)strlen(sendbuf), 0); if (iSendResult == SOCKET_ERROR) { printf("傳送失敗: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } printf("傳送位元組大小: %d\n", iSendResult); } else if (iResult == 0) printf("連線已關閉\n"); else { printf("接收資料失敗: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } } while (iResult > 0); //關閉套接字以及清理套接字環境 closesocket(ClientSocket); WSACleanup(); return 0; } int main(int argc, char *argv[]) { int iResult; WSADATA wsaData; //初始化套接字環境 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("初始化socket環境失敗: %d\n", iResult); return 1; } if (argc == 2 && strcmp(argv[1], "c") == 0) { //客戶端 return startClient(); } else if (argc == 2 && strcmp(argv[1], "s") == 0) { //服務端 return startServer(); } return 1; }
執行結果
後記
以上只是一個簡單的socket通訊示例,所有api呼叫都是阻塞的,非阻塞呼叫將在下文寫出。