C/C++ socket套接字詳解(Windows)
本篇部落格是在windows系統下的CodeBlocks環境下編寫而成的,Linux系統以及其他編譯環境暫不適用
關於如何CodeBlocks如何安裝和配置/,可以參考連結(轉自螢火蟲塔莉):CodeBlocks的安裝以及編譯器的配置
常見問題:
編譯時不能識別socket,需要手動匯入lib庫。具體方法連結:https://blog.csdn.net/buaa1214wwj/article/details/52033868
如果導這兩個lib庫仍然不能識別,出現以下情況,重新建立C++工程。
二、socket用法簡介
(1)socket概念
- socket翻譯為套接字,它是計算機之間通訊的一種約定或一種方式,通過socket這種約定,一臺計算機可以接受其他計算機的資料,也可以向其他計算機發送資料。
- 學習socket,也就是學習計算機如何通訊,並且編寫出實用的程式。
(2)Windows程式示例
伺服器演示程式碼Server.cpp
#include <stdio.h> #include <winsock2.h> #pragma comment (lib, "ws2_32.lib") //載入 ws2_32.dll int main(){ //初始化 DLL WSADATA wsaData; WSAStartup( MAKEWORD(2, 2), &wsaData); //建立套接字 SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //繫結套接字 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每個位元組都用0填充 sockAddr.sin_family = PF_INET; //使用IPv4地址 sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址 sockAddr.sin_port = htons(1234); //埠 bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //進入監聽狀態 listen(servSock, 20); //接收客戶端請求 SOCKADDR clntAddr; int nSize = sizeof(SOCKADDR); SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize); //向客戶端傳送資料 char *str = "Hello World!"; send(clntSock, str, strlen(str)+sizeof(char), NULL); //關閉套接字 closesocket(clntSock); closesocket(servSock); //終止 DLL 的使用 WSACleanup(); return 0; }
客戶端演示程式碼Client.cpp
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") //載入 ws2_32.dll int main(){ //初始化DLL WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); //建立套接字 SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //向伺服器發起請求 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每個位元組都用0填充 sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddr.sin_port = htons(1234); connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //接收伺服器傳回的資料 char szBuffer[MAXBYTE] = {0}; recv(sock, szBuffer, MAXBYTE, NULL); //輸出接收到的資料 printf("Message form server: %s\n", szBuffer); //關閉套接字 closesocket(sock); //終止使用 DLL WSACleanup(); system("pause"); return 0; }
(3)程式解釋
1. #pragma comment (lib, "ws2_32.lib") :Windows下的socket程式依賴Winsock.dll或者ws2_32.dll,所以必須提前載入2. WSAStartup()函式對dll初始化,以指明Winsock的版本號,原型:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
1)wVersionRequested 為 WinSock 規範的版本號,一般採用2.2版本,型別為 WORD,等價於 unsigned short,是一個整數,所以需要用 MAKEWORD() 巨集函式對版本號進行轉換。
2)lpWSAData 為指向 WSAData 結構體的指標。
3.socket() 函式建立套接字,原型:(Windows 不把套接字作為普通檔案對待,而是返回 SOCKET 型別的控制代碼)
SOCKET socket(int af, int type, int protocol);
1)af 為地址族(Address Family),也就是 IP 地址型別,常用的有 AF_INET(IPv4) 和 AF_INET6(IPv6) 。
2)type 為資料傳輸方式,常用的有 SOCK_STREAM (面向連線的資料傳輸方式)和 SOCK_DGRAM(無連線的資料傳輸方式)。
3)protocol 表示傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議。
4. bind() 函式伺服器端將套接字與特定的IP地址和埠繫結;connect() 函式客戶端用來建立連線
1) bind()原型:
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
2)connet()原型:
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);
sock 為 socket 檔案描述符,addr 為 sockaddr 結構體變數的指標,addrlen 為 addr 變數的大小,可由 sizeof() 計算得出
sockaddr_in 結構體,成員變數如下:
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址型別
uint16_t sin_port; //16位的埠號
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
5.listen()函式讓套接字進入被動監聽狀態,它的原型為:
int listen(SOCKET sock, int backlog);
sock 為需要進入監聽狀態的套接字,backlog 為請求佇列的最大長度。
6.accept() 函式當套接字處於監聽狀態時,可以通過 accept() 函式來接收客戶端請求,它的原型為:
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
sock 為伺服器端套接字,addr 為 sockaddr_in 結構體變數,addrlen 為引數 addr 的長度,可由 sizeof() 求得。
特別說明:listen() 只是讓套接字進入監聽狀態,並沒有真正接收客戶端請求,listen() 後面的程式碼會繼續執行,直到遇到 accept()。accept() 會阻塞程式執行(後面程式碼不能被執行),直到有新的請求到來。
7. send() 函式傳送資料,recv() 函式接受資料
1)send()原型:
int send(SOCKET sock, const char *buf, int len, int flags);
2)recv()原型:
int recv(SOCKET sock, char *buf, int len, int flags);
sock 為要傳送資料的套接字,buf 為要傳送的資料的緩衝區地址,len 為要傳送的資料的位元組數,flags 為傳送資料時的選項。
8.closesocket()函式關閉套接字,原型為:
void closesocket(SOCKET Sock);
9.WSACleanup()函式終止 DLL 的使用。