天天看點

通信程式設計:Winsock 接口載入

目錄

  • Winsock 程式設計接口
  • Winsock 的載入和釋放
    • 載入與釋放操作
    • CInitSock 類
  • Winsock 尋址方式
    • sockaddr_in 結構
    • sockaddr_in 結構初始化
  • 參考資料
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 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
    );
           

由于每次使用 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 是 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 結構大小對齊
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 sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");