Winsock解析


一、基本知識

 1、Winsock,一種標准API,一種網絡編程接口,用於兩個或多個應用程序(或進程)之間通過網絡進行數據通信。具有兩個版本:

Winsock 1:

Windows CE平台支持。

頭文件:WinSock.h

庫:wsock32.lib

 Winsock 2:

部分平台如Windows CE貌似不支持。通過前綴WSA可以區別於Winsock 1版本。個別函數如WSAStartup、WSACleanup、WSARecvEx、WSAGetLastError都屬於Winsock 1.1規范的函數;

頭文件:WinSock2.h

庫:ws2_32.lib

 mswsock.h用於編程擴展,使用時必須鏈接mswsock.dll。

 

2、網絡協議:

IP (Internet Protocol)  網際協議,無連接協議;

TCP (Transmission Control Protocol)  傳輸控制協議;

UDP (User Datagram Protocol)  用戶數據協議;

FTP (File Transfer Protocol)  文件傳輸協議;

HTTP (Hypertext Transfer Protocol)  超文本傳輸協議;

 

3、字節存儲順序:

big_endian:大端存儲,存儲順序從高位到低位,地址指向最高有效字節。在網絡中將IP和端口指定為多字節時使用大端存儲,也稱為網絡字節順序(network_byte)。貌似MAC OS使用的是大端存儲方式;

 

little_endian:小端存儲,存儲順序從低位到高位,地址指向最低有效字節。本地主機存儲IP和端口制定的多字節時使用,也稱為主機字節順序(host_byte)大多數系統都是小端存儲;

 

用下面的方式可以檢測是否為大端存儲:

bool IsBig_endian() 

    unsigned short test = 0x1122; 

    if ( *( (unsigned char*)&test ) == 0x11 ) //force unsigned short pointer to ussigned char pointer

    { 

        return true; 

    }  

    else 

    { 

        return false; 

    } 

 

此外有很多函數可以用來進行 主機字節和網絡字節之間的轉換,如:

u_long htonl( u_long hostlong );

int WSAHtonl( SOCKET s, u_long hostlong, u_long FAR *lpnetlong );

而有時網絡IP是點分法表示的,如:192.168.0.1,使用函數unsigned long inet_addr( const char FAR *cp ); 可以將點分法的IP字符串作為一個網絡字節順序的32位u_long返回。

二、快速了解

 

1、Winsock初始化:

首先確保包含對應版本的頭文件,然后保證鏈接對應的庫文件(可以在代碼中使用#pragma comment(lib, "WS2_32"),或在編譯器項目屬性中鏈接器->輸入->附加依賴項中添加ws2_32.lib)

 通過調用WSAStartup函數來實現加載Winsock庫:

print?

int 

WSAAPI 

WSAStartup( 

    IN WORD wVersionRequested, 

    OUT LPWSADATA lpWSAData 

    ); 

其中參數wVersionRequested用來指定加載Winsock庫的版本,高位字節為次版本,低位字節為主版本,使用宏MAKEWORD(x,y)來生成一個WORD

參數lpWSAData是指向WASDATA結構指針,加載的版本庫信息將會填充這個結構,詳細內容自查。

使用Winsock后需要釋放資源,並取消應用程序掛起的Winsock操作。使用int WASCleanup();

 

 

2、錯誤處理:

如果已經加載了Winsock庫,則調用Winsock函數出錯后,通常會返回SOCKET_ERROR,而通過使用函數int WSAGetLastError() 可以獲得具體信息值,例如:

 

if ( SOCKET_ERROR == WSACleanup() ) 

    cout << "WSACleanup error " << WSAGetLastError() << endl; 

    return 0; 

根據獲取錯誤信息值,可以知道錯誤原因,並進行相應的處理。

 

 

3、尋址:

想要進行通信就需要知道彼此的地址,一般來說這個地址由IP和端口號來決定。在Winsock中使用SOCKADDR_IN結構來指定地址信息:

struct sockaddr_in { 

        short   sin_family; 

        u_short sin_port; 

        struct  in_addr sin_addr; 

        char    sin_zero[8]; 

}; 

sin_family字段通常設置為AF_INET,表示Winsock此時正在使用IP地址族

sin_port用於標示TCP或UDP通信端口,部分端口是為一些服務保留的,如FTP和HTTP使用要注意;

sin_adr字段把地址(例如是IPv4地址)作為一個4字節的量來存儲起來,它是u_long類型,且是網絡字節順序的。可以使用inet_addr來處理點分法表示的IP地址;

sin_zero只充當填充項,以使SOCKADDR_IN結構和SOCKADDR結構長度一樣。

以下簡單的使用SOCKADDR_IN來指定地址:

print?

//創建一個地址 

int serverPort      = 5150; 

 

char FAR serverIP[] = "192.168.1.102"; //本機ip,不知道就ipconfig 

 

SOCKADDR_IN serverAddr; 

serverAddr.sin_family   = AF_INET; 

serverAddr.sin_port = htons( serverPort ); 

serverAddr.sin_addr.s_addr  = inet_addr( serverIP ); 

int serverAddr_size = static_cast<int>( sizeof(serverAddr) ); 

有時作為一個連接通信的服務端來說,在設置監聽socket的地址結構時sin_addr.s_addr的值可以是htonl( INADDR_ANY ),INADDR_ANY允許將socket綁定到系統中所有可用的接口,以便傳到任意接口的連接(端口必須正確)都可以被監聽socket接受。

 

 

4、socket套接字:

套接字是通信時為傳輸提供的句柄,Winsock的操作都是基於套接字實現的。創建一個套接字有socket和WSASocket方法:

 

SOCKET 

WSAAPI 

socket( 

       IN int af,       //協議的地址族,使用IPv4來描述Winsock,設置為AF_INET 

       IN int type,     //套接字類型,TCP/IP設置為SOCK_STREAM,UDP/IP設置為SOCK_DGRAM 

       IN int protocol      //用於給定地址族和類型具有多重入口的傳送限定,TCP設置為IPPROTO_TCP,UDP設置為IPPROTO_UDP 

       ); 

如果創建成功,函數會返回一個有效的SOCKET,否則會返回INVALID_SOCKET,可以用WSAGetLastError()函數獲得錯誤信息。

 

 

5、連接通信實現過程:

連結通信是基於TCP/IP實現的,進行數據傳輸前,通信雙方要進行連接。

 

服務端:

初始化Winsock后,創建一個監聽socket和一個要接受連接的地址結構;

使用bind將監聽socket與地址結構進行關聯;

 

int 

WSAAPI 

bind( 

     IN SOCKET s,               //一個用於監聽的socket 

     IN const struct sockaddr FAR * name,   //指向進行綁定的地址結構 

     IN int namelen             //進行綁定的地址結構的大小 

     ); 

使用listen將bind成功的監聽socket狀態設置為監聽狀態;

 

int 

WSAAPI 

listen( 

       IN SOCKET s,     //一個用於監聽的socket,已經進行bind 

       IN int backlog       //允許掛起連接的隊列的最大長度,超過這個長度后,再有連接將會失敗 

       ); 

 

使用accept接受通過監聽socket獲取的連接,成功后將返回的新的連接socket進行保存以便數據傳輸;

print?

SOCKET 

WSAAPI 

accept( 

       IN SOCKET s,         //處於監聽模式的socket 

       OUT struct sockaddr FAR * addr,  //指向一個地址結構,用來接受連接后獲得對方地址信息 

       IN OUT int FAR * addrlen     //指向一個整數,表示參數2指向地址結構的大小 

       ); 

 

 

客戶端:

初始化Winsock后,創建一個監聽socket和一個要連接的服務器地址結構;

使用connect將socket和服務器地址結構進行初始化連接,成功后將使用socket進行數據傳輸;

print?

int 

WSAAPI 

connect( 

        IN SOCKET s,            //要建立連接的socket 

        IN const struct sockaddr FAR * name,    //指向保存要建立連接信息的地址結構 

        IN int namelen          //參數2指向地址結構的大小 

        ); 

 

 

連接成功后,使用send、recv來進行數據傳輸;

print?

int 

WSAAPI 

send( 

     IN SOCKET s,       //進行連接的socket 

     IN const char FAR * buf,   //指向發送數據的緩沖區 

     IN int len,        //發送數據的字符數 

     IN int flags       //一個標志位,可以是0、MSG_DONTROUTE、MSG_OOB還可以是他們的或運算結果 

     );         //返回已經發送的數據長度 

 

 

int 

WSAAPI 

recv( 

     IN SOCKET s,       //進行連接的socket 

     OUT char FAR * buf,    //指向接受數據的緩沖區 

     IN int len,        //准備接受數據字節數或緩沖區的長度 

     IN int flags       //可以是0、MSG_PEEK、MSG_OOB還可以是他們的或運算結果 

     );         //返回已接受的數據長度 

 

連接結束后,使用shutdown和closesocket來斷開連接和釋放資源;

print?

int 

WSAAPI 

shutdown( 

         IN SOCKET s,   //要關閉的socket 

         IN int how //關閉標志:SD_RECEIVE、SD_SEND、SD_BOTH 

         ); 

 

 

6、無連接通信實現過程:

無連接通信是基於UDP/IP實現的,UDP不能確保可靠的數據傳輸,但能將數據發送到多個目標,或者接受多個源的數據。

初始化Winsock后,可以創建socket和用以進行通信任意地址結構;

使用recvfrom通過socket和通信的地址結構接受數據;

使用sendto通過socket和通信的地址結構發送數據;

print?

int 

WSAAPI 

recvfrom( 

         IN SOCKET s, 

         OUT char FAR * buf, 

         IN int len, 

         IN int flags, 

         OUT struct sockaddr FAR * from, 

         IN OUT int FAR * fromlen 

         ); 

 

int 

WSAAPI 

sendto( 

       IN SOCKET s, 

       IN const char FAR * buf, 

       IN int len, 

       IN int flags, 

       IN const struct sockaddr FAR * to, 

       IN int tolen 

       ); 

 

同樣通信結束后,使用shutdown和closesocket來斷開連接和釋放資源

 

 

上述使用函數都有多個版本,而且相關的一些標志位參數可以提供設置選項,另外,返回的錯誤處理等也有待於詳細研究;

 

 

7、select函數:

 

select()用於確定一個或多個套接口的狀態。對每一個套接口,調用者可查詢它的可讀性、可寫性及錯誤狀態信息。

print?

int 

WSAAPI 

select( 

       IN int nfds,         //指集合中所有文件描述符的范圍,即所有文件描述符的最大值加1,在Windows中值無所謂。  

       IN OUT fd_set FAR * readfds, //可選指針,指向一組等待可讀性檢查的套接字。  

       IN OUT fd_set FAR * writefds,    //可選指針,指向一組等待可寫性檢查的套接字。 

       IN OUT fd_set FAR *exceptfds,    //可選指針,指向一組等待錯誤檢查的套接字。 

       IN const struct timeval FAR * timeout    //select()最多等待時間,對阻塞操作則為NULL。 

       ); 

 

//用fd_set結構來表示一組等待檢查的套接口。在調用返回時,這個結構存有滿足一定條件的套接口組的子集: 

 

typedef struct fd_set { 

    u_int fd_count;               //其中set元素數目 

    SOCKET  fd_array[FD_SETSIZE]; //保存set元素的數組 

} fd_set; 

 

fd_set set; 

FD_ZERO(&set);      /*將set清零使集合中不含任何fd*/    

FD_SET(fd, &set);       /*將fd加入set集合*/    

FD_CLR(fd, &set);       /*將fd從set集合中清除*/    

FD_ISSET(fd, &set);     /*測試fd是否在set集合中*/  

 

select的返回值:

select()調用返回處於就緒狀態並且已經包含在fd_set結構中的描述字總數;

如果超時則返回0;否則的話,返回SOCKET_ERROR錯誤,通過WSAGetLastError獲取相應錯誤代碼。

當返回位0時,所有描述符集清0;

當返回為-1時,不修改任何描述符集;

當返回為非0時,在3個描述符集里,依舊是1的位就是准備好的描述符。這也就是為什么,每次用select后都要用FD_ISSET的原因。

參考:http://www.2cto.com/kf/201111/111568.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM