Winsock 編程接口
Winsock 是 Windows 下網絡編程的規范,該規范是 Windows 下得到廣泛應用的、開放的、支持多種協議的網絡編程接口。從 1991 年的 1.0 版到 1995 年的 2.0.8 版,經過不斷完善並在 Intel、Microsoft、Sun、SGI、Informix、Novell 等公司的全力支持下,已成為 Windows 網絡編程的事實上的標准。——百度百科
通過 Winsock 編程接口就可以令多個應用程序通過網絡來進行通信,Winsock 編程接口有 Winsock1 和 Winsock2 兩個版本,目前主要使用 Winsock2 來進行開發。想要使用 Winsock2 庫,就需要包含頭文件來使用相關的 socket 函數和結構體,同時還要添加到 WS2_32.lib 的鏈接。
#include <winsock2.h>
#pragma comment(lib, "WS2_32") // 鏈接到 WS2_32.lib
Winsock 的載入和釋放
載入與釋放操作
每個基於 Winsock 開發的程序都需要載入對應版本的 Winsock DLL,這樣才能使用 Winsock 提供的工具包。 想要載入 Winsock 庫,需要使用 WSAStartup() 函數:
int
WSAAPI
WSAStartup(
_In_ WORD wVersionRequested,
_Out_ LPWSADATA lpWSAData
);
參數 | 類型 | 數據類型 | 說明 |
---|---|---|---|
wVersionRequested | 輸入 | WORD | 指定要加載的 Winsock 版本 |
lpWSAData | 返回值 | LPWSADATA | 一個指向 WSADATA 結構的指針 |
其中 wVersionRequested 參數有 2 個字節,高字節指定次版本號,低字節指定主版本號,一般來說使用 Winsock2 時高字節和低字節都是 2。建立這個參數時,可以使用 MAKEWORD(a, b) 宏。函數的返回值時 LPWSADATA 結構,里面存儲了加載的庫的版本相關信息。
#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
想要釋放 Winksock 庫,可以使用 WSACleanup() 函數。
int
WSAAPI
WSACleanup(
void
);
CInitSock 類
由於每次使用 Winksock 程序都需要載入 Winksock 庫,因為從封裝性的角度來考慮,可以封裝一個工具類來專門載入和釋放 Winksock 庫。首先先簡單介紹一下 C++ 面向對象編程的構造器和析構器,注意和 Java 不同的是 Java 的類不需要寫析構器。
函數 | 函數名 | 返回值 | 功能 |
---|---|---|---|
構造器 | 和類名相同 | 無 | 不需要用戶顯式調用,而是在創建對象時自動執行 |
析構器 | 在類名前面加一個 “~” 符號 | 無 | 不需要程序員顯式調用,而是在銷毀對象時自動執行 |
其實這個工具類只需要寫構造器和析構器即可,其中構造器需要使用 MAKEWORD(a, b) 宏給一個 WORD 指定版本號,然后調用 WSAStartup() 函數載入 Winsock2 庫。析構器則只需要調用 WSACleanup() 方法,目的就是在不需要使用 Winsock2 時自動把它釋放掉。
#include <winsock2.h>
#pragma comment(lib, "WS2_32") // 鏈接到 WS2_32.lib
class CInitSock
{
public:
/*CInitSock 的構造器*/
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
{
// 初始化WS2_32.dll
WSADATA wsaData;
WORD sockVersion = MAKEWORD(minorVer, majorVer);
if (::WSAStartup(sockVersion, &wsaData) != 0)
{
exit(0);
}
}
/*CInitSock 的析構器*/
~CInitSock()
{
::WSACleanup();
}
};
為了以后調用方便,這個工具類可以寫在 initsock.h 頭文件中。
Winsock 尋址方式
sockaddr_in 結構
Winsock 是 Windows 下網絡編程的規范,是支持多種協議的網絡編程接口,因此編址也需要顧及不同的協議棧。Winsock 的第一個版本使用 sockaddr 結構來編址,里面的 sa_family 成員制定了使用的編址方式。而對於 TCP/ IP 協議棧,可以直接使用 sockaddr_in 結構。
typedef struct sockaddr_in {
#if(_WIN32_WINNT < 0x0600)
short sin_family;
#else //(_WIN32_WINNT < 0x0600)
ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)
USHORT sin_port;
IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
其中有幾個重要的成員:
成員變量 | 說明 |
---|---|
sin_family | 地址家族 |
sin_port | 端口號 |
sin_addr | IPv4 地址 |
sin_zero[8] | 占位,用於和 sockaddr 結構大小對齊 |
其中對於 sin_family 變量必須使用 AF_INET 作為地址家族,表示使用 IP 編址。in_addr 結構用來存儲 IP 地址,底層是使用一個共用體 union 來實現,可以用 4 個 uchar 或 2 個 ushort 或 1 個 ulong 來存儲。
typedef struct in_addr {
union {
struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { USHORT s_w1,s_w2; } S_un_w;
ULONG S_addr;
} S_un;
sockaddr_in 結構初始化
因此對於 sockaddr_in 結構的初始化,實際上就是分別指定地址家族,綁定端口號和 IP 地址。
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
參考資料
《Windows 網絡與通信編程》,陳香凝 王燁陽 陳婷婷 張錚 編著,人民郵電出版社