天天看點

Windows Sockets下網絡程式設計的函數與套路詳解

套接字(socket)

AF_INET 網際域—windows sockets隻支援一個通信區域(位址族)。

套接字類型:

  • 流式套接字(SOCK_STREAM) 提供面向連接配接、可靠的資料傳輸服務,資料無差錯、無重複的發送,且按發送順序接收。實際基于TCP實作。
  • 資料報式套接字(SOCK_DGARM)

    提供無連接配接服務。資料包以獨立包形式發送,不提供無差錯保證,資料可能會丢失或重複,并且接收順序混亂,實踐基于UDP實作。

  • 原始套接字(SOCK_RAW)

主機位元組序與網絡位元組序

主機位元組序:

主機位元組序是混亂的,不同的CPU有不同的位元組序類型 這些位元組序是指整數在記憶體中儲存的順序 這個叫做主機序。

最常見的有兩種

1. Little endian(小端):将低序位元組存儲在起始位址

LE little-endian 最符合人的思維的位元組序 位址低位存儲值的低位 位址高位存儲值的高位

怎麼講是最符合人的思維的位元組序,是因為從人的第一觀感來說 低位值小,就應該放在記憶體位址小的地方,也即記憶體位址低位

2. Big endian(大端):将高序位元組存儲在起始位址

反之,高位值就應該放在記憶體位址大的地方,也即記憶體位址高位

BE big-endian 最直覺的位元組序 位址低位存儲值的高位 位址高位存儲值的低位

例子:在記憶體中雙字0x01020304(DWORD)的存儲方式

記憶體位址

4000 4001 4002 4003

LE 04 03 02 01

BE 01 02 03 04

網絡位元組序:

網絡位元組序是确定的。網絡位元組順序是TCP/IP中規定好的一種資料表示格式,它與具體的CPU類型、作業系統等無關,進而可以保證資料在不同主機之間傳輸時能夠被正确解釋。網絡位元組順序采用big-endian(大端)排序方式。

WSAStartup函數

功能:

1.加載套接字庫。

2.進行套接字庫的版本協定,也就是确認使用socket版本。

函數的原型:

參數:

1.wVersionRequested

用來指定準備加載的Winsock庫的版本。高位位元組指定所需的Winsock庫的副版本,而低位位元組則是主版本。如,版本号2.1,其中2是主版本号,1就是副版本号。Windows Socket規範的目前版本是2.2,目前的WinSock庫支援的版本包括:1.0、1.1、2.0、2.1和2.2。可以利用MAKEWORD(x,y)宏(其中x是高位位元組,y是低位位元組)友善地擷取wVersionRequested的正确值。

2.lpWSAData

這是一個傳回值,指向WSADATA結構體指針,WSAStartup函數用其加載的庫版本有關的資訊填在這個結構體中。

typedef struct WSAData {
        WORD                    wVersion;//設定為打算使用的Winsock版本
        WORD                    wHighVersion;//現有的Winsock庫的最高版本
#ifdef _WIN64
        unsigned short          iMaxSockets;
        unsigned short          iMaxUdpDg;
        char FAR *              lpVendorInfo;
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                    szSystemStatus[WSASYS_STATUS_LEN+1];
#else
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                    szSystemStatus[WSASYS_STATUS_LEN+1];
        unsigned short          iMaxSockets;
        unsigned short          iMaxUdpDg;
        char FAR *              lpVendorInfo;
#endif
} WSADATA, FAR * LPWSADATA;

           

傳回值:

如果Ws2_32.dll或底層網絡子系統沒有被正确地初始化或沒有被找到,WSAStarup函數傳回WSASYSNOTREADY。如果請求的版本低于WinSock動态庫所支援的最低版本,則WSAStarup函數将傳回WSAVERNOTSUPPORTED。

對于每一個WSACleanUp函數調用成功後,在最後都應該對應一個WSACleanUp調用,以便釋放為該應用程式配置設定的資源。

例子:

WSADATA wsa;  //存放 windows socket初始化資訊的結構體
    if (WSAStartup(MAKEWORD(2,2), &wsa)) {
         printf("init winsock failed !"); 
         return 0;
		
    }

           

socket函數

作用:

加載了套接字後,就可以調用socket函數建立套接字。

函數原型:

參數:

  1. af指定位址族,對于TCP/IP協定的套接字,它隻能是AF_INET(也可以寫成PF_INET,IPv4)或AF_INET6(IPv6);
  2. type指定socket類型,對于1.1版本的socket,它隻支援兩種類型的套接字,SOCK_STREAM指定産生流式套接字,SOCK_DGRAM産生資料包套接字。
  3. protocol與特定的位址族相關的協定,如果指定為0,那麼系統會根據位址格式和套接字類别,自動選擇一個适合的協定。

傳回值:

socket調用成功傳回一個新的SOCKET資料類型的套接字描述符;如果失敗,函數傳回一個INVALID_SOCKET值,錯誤資訊可以通過WSAGetLastError函數檢視。

例子:

bing函數

在建立套接字之後,應該将該套接字綁定到本地的某個位址和端口上,這就要通過bing函數來實作。

參數:

  1. s指定要綁定的套接字
  2. 指定了該套接字的本地位址資訊,這是一個指向sockaddr結構體的指針變量,由于該結構體是為所有的位址家族準備的,這個結構體通常會随着所使用的網絡協定不同而不同。
typedef struct sockaddr {

#if (_WIN32_WINNT < 0x0600)
    u_short sa_family;
#else
    ADDRESS_FAMILY sa_family;           // 位址族.
#endif //(_WIN32_WINNT < 0x0600)

CHAR sa_data[14];                   // 表示一塊記憶體分區,起占位作用。
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
           

由于實際要求的訓示記憶體區,對于不同的協定家族,用不同的結構體來替換sockaddr。在基于TCP/IP的socket程式設計的過程中,可以用sockaddr_in結構體來替換sockaddr,以友善我們填寫位址資訊。

sockaddr_in結構體如下:

typedef struct sockaddr_in {

#if(_WIN32_WINNT < 0x0600)
short   sin_family;    
#else //(_WIN32_WINNT < 0x0600)
    ADDRESS_FAMILY sin_family;		//位址族,對于IP位址一緻是AF_INET
#endif //(_WIN32_WINNT < 0x0600)
    USHORT sin_port;   //指定要配置設定給套接字的端口
    IN_ADDR sin_addr;		//給出的是套接字的主機IP位址。
    CHAR sin_zero[8];		//一個填充數,保證與sockaddr結構體長度一樣
} SOCKADDR_IN, *PSOCKADDR_IN;

           

其中sin_addr是

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;
#define s_addr  S_un.S_addr /* can be used for most tcp & ip code */
#define s_host  S_un.S_un_b.s_b2    // host on imp
#define s_net   S_un.S_un_b.s_b1    // network
#define s_imp   S_un.S_un_w.s_w2    // imp
#define s_impno S_un.S_un_b.s_b4    // imp #
#define s_lh    S_un.S_un_b.s_b3    // logical host
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
           

利用這個結構體将一個點分十進制格式的IP位址轉換為u_long類型,并将結果賦給成員S_addr。

  1. namelent指定了第二個參數的長度。

傳回值:

bing函數調用成功傳回0。如果失敗就傳回一個SOCKET_ERROR,錯誤資訊可以通過WSAGetLastError函數傳回。

inet_addr和inet_nota函數

可以将IP位址指定為INADDR_ANY,允許套接字向任何配置設定給本地機器的IP位址發送或接收資料。在大多數情況下一個機器隻有一個IP位址,但有的機器可能會有很多個網卡,每個網卡都有一個自己的IP位址,用INADDR_ANY可以簡化應用程式的編寫。将位址指定為INADDR_ANY,将允許一個獨立應用接受來自多個接口的回應。如果我們隻想讓套接字使用多個IP位址中的一個,就必須指定實際的位址,用inet_addr函數來實作。

inet_addr函數原型:

參數:

cp指定了以點分十進制表示的IP位址(如192.168.4.7)。并且inet_addr函數會傳回一個适合配置設定給S_addr的u_long類型的數值。

inet_ntoa函數會完成相反的轉換,它接受一個in_addr結構體類型的參數并傳回一個以點分十進制格式表示的IP位址字元串。

inet_ntoa函數原型:

inet_pton和inet_ntop函數(我用的vs2017好像淘汰了這個兩個函數,linux下有這兩個函數)

作用:

這個兩個函數是為了适應IPv6而出現的,用于将Internet網絡位址在“标準文本表示形式”和“數字二進制形式”之間的轉換,對于IPv4和IPv6都适用。

inet_pton函數的原型:

該函數作用:

該函數是将标準文本表示形式的IPv4和IPv6 Internet網絡位址轉換為數字二進制形式。

參數:

  1. Family可以是AF_INET(IPv4)和AF_INET6(IPv6)。
  2. pszAddrString指向以空結尾的字元串的指針,該字元串包含要轉換為數字二進制形式的IP位址的文本表示形式。
  3. pAddrBuf指向緩沖區的指針,用于存放轉換後二進制表示形式的IP位址。如果Family指定AF_INET,則pAddrBuf的大小應該足以容納in_addr結構體;如果Family指定AF_INET6,則pAddrBuf的大小應該足以容納in6_addr結構體。

傳回值:

如果轉換成功,傳回1,pAddrBuf指向的緩沖區包含轉換後的二進制數字格式的IP位址;如果pAddrBuf參數指向的字元串不是有效的IPv4點分十進制字元串或IPv6位址字元串,則函數傳回0;如果轉換出錯傳回-1,錯誤代碼可以通過WASGetLastError函數來查。

inet_ntop函數的原型:

作用:

該函數将IPv4或IPv6 Internet網絡位址轉換為Internet标準格式的字元串。

參數:

  1. Family 可以是AF_INET(IPv4)也可以是AF_INET6(IPv6)。
  2. 參數 pAddr指定一個二進制結構的IP位址。當Family參數是AF_INET時,pAddr參數必須指向一個包含要轉換的IPv4位址的in_addr結構;當Family參數是AF_INET6時,pAddr參數指向一個包含要轉換的IPv6位址的 in6_addr結構。
  3. 參數pStringBuf指定一個緩沖區,用來接收轉換後以空字元串結尾的字元串形式的IP位址。對于IPv4位址,此緩沖區應至少可以容納16個字元;對于IPv6位址,此緩沖區至少可以容納46個字元。
  4. 參數StringBufSize指定緩沖區pStringBuf的大小,對于IPv4網絡位址,這個大小至少是INET_ADDRSTRLEN位元組長,對于IPv6網絡位址,這個大小至少是INET6_ADDRSTRLEN位元組長。

傳回值:

成功傳回轉換後的字元串的首位址,失敗傳回NULL,錯誤代碼可以用WSAGetLastError函數來得到。

listen函數

作用:

将指定的套接字設定為監聽模式。

函數原型:

參數:

  1. s是套接字描述符
  2. Backlog是等待連接配接隊列的最大長度。如果設定為SOMAXCONN,那麼下層的服務提供者将負責把這個套接字設定為最大的合理值。要注意的是,設定這個值是為了設定等待連接配接隊列的最大長度,而不是在一個端口上同時可以進行連接配接的數目。例如将backlog設定為2,當有三個請求同時到來時,前兩個請求放到等待請求隊列中,然後由應用程式依次為這些請求服務,而第三個連接配接請求就被拒絕。

accept函數

作用:

Windows Sockets的accept函數接受用戶端發送的連接配接請求。

函數原型:

參數:

  1. s套接字描述符,該套接字通過listen函數将其設定為監聽狀态。
  2. addr是指向一個緩沖區的指針,儲存發起連接配接的這個用戶端的IP位址和端口資訊。
  3. addrlen是一個傳回值,指向一個整型的指針,傳回包含位址資訊長度。

send 函數

作用:

Windows Sockets的send函數通過一個已建立連接配接的套接字發送資料。

函數原型:

參數:

  1. s已建立連接配接的套接字。
  2. buf指向一個緩沖區,該緩沖區包含要傳遞的資料。
  3. len是緩沖區的大小。
  4. flags設定的值将影響函數的行為,一般将其設定為0。

recv函數

作用:

Windows Socket的rev函數從一個已連接配接的套接字接收資料。

函數原型:

參數:

  1. s是建立連接配接後準備接收資料的那個套接字。
  2. buf是指向緩沖區的指針,用來儲存接收的資料。
  3. len緩沖區的長度。
  4. flags通過設定這個值可以影響函數的調用行為,一般設定為0。

connect函數

作用:

Windows Sockets的connect函數将與一個特定的套接字建立連接配接。

函數原型:

參數:

  1. s是即将在其上建立連接配接的那個套接字。
  2. name設定連接配接伺服器端位址資訊。
  3. namelen指定伺服器端位址的長度。

recvfrom函數

作用:

Windows Socket的recvfrom函數将接收一個資料報資訊并儲存位址。

函數的原型:

參數:

  1. s是準備接收資料的套接字。
  2. buf是一個指定接收資料的緩沖區。
  3. len是緩沖區大小。
  4. flags設定這個值可以影響這些函數。
  5. from是一個指向位址結構體的指針,主要用來接收發送資料方的位址資訊。
  6. fromlen是一個整型指針,它是一個in/out類型的參數(in是裝置發到主機的 out中主機發到裝置的),在調用它之前需要指定一個初始值,調用函數後,會通過這個參數傳回位址結構體的大小。

sendto函數

作用:

Windows Sockets的sendto函數将向一個特定的目的方發動資料。

函數原型:

參數:

  1. s套接字描述符。
  2. buf指向緩沖區的指針,包含要發送的資料。
  3. len指定緩沖區的大小。
  4. flags設定這個值影響函數調用的行為,一般設定為0。
  5. to是一個可選的指針,指定目标套接字的位址。
  6. tolen是參數to中指定的位址的長度。

htons和htonl函數

他們的作用:

把一個值從主機位元組順序轉換為TCP/IP網絡位元組順序。

htons函數原型:

參數:

hostshort是一個16位數值的主機位元組順序。

htonl函數原型

參數:

hostlong是一個32位數值的主機位元組順序。

closesocket函數

作用:

關閉套接字。

函數的原型:

參數:

s要關閉的套接字。

WSACleanup函數

作用:

釋放加載的套接字庫。

函數的原型:

基于TCP面向連接配接的socket程式設計套路

伺服器程式的流程:

1.建立套接字(socket)。

2.将套接字綁定到一個本地位址和端口上(bing)。

3.将套接字設定為監聽模式,準備接收客戶請求(listen)。

4.等待客戶請求到來(accept);當請求來到後,接受連接配接請求,傳回一個新的對應于此次連擊的套接字。

5.用于傳回套接字和用戶端進行通信(send/recv)。

6.傳回,等待另一個客戶請求。

7.關閉套接字

用戶端的流程:

1.建立套接字(socket)。

2.向伺服器發送連接配接請求(connet)。

3.向伺服器端進行通信(send/recv)。

4.關閉套接字。

基于UDP面向無連接配接的socket程式設計套路

服務端也叫接收端,對于UDP來說伺服器和用戶端這種概念不是很強化。把伺服器端,即先啟動的一端稱為接收端;發送資料的一端稱為發送端,也稱用戶端。

接收端的程式設計流程:

1.建立套接字(socket)。

2.将套接字綁定到一個本地位址和端口上(bing)。

3.等待接收資料(recvfrom)。

4.關閉套接字。

用戶端的編寫流程:

1.建立套接字(socket)。

2.向伺服器發送資料(sendto)。

3.關閉套接字。

雜談

當你看了很多有關于網絡程式設計TCP、UDP的相關文章和書籍後,你覺得自己行了。然後再看看别人寫的代碼,還是有很多關于網絡程式設計的函數以及流程是你所不認識看不懂的,不認識的。系統函數千千萬,多看看多學學多寫寫吧。