Winsock解析

1、基本知識html

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

Winsock 1:數組

Windows CE平臺支持。服務器

頭文件:WinSock.h網絡

庫:wsock32.libsocket

 Winsock 2:函數

部分平臺如Windows CE貌似不支持。經過前綴WSA能夠區別於Winsock 1版本。個別函數如WSAStartup、WSACleanup、WSARecvEx、WSAGetLastError都屬於Winsock 1.1規範的函數;測試

頭文件:WinSock2.hspa

庫:ws2_32.lib指針

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

 

二、網絡協議:

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

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

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

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

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

 

三、字節存儲順序:

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返回。

2、快速瞭解

 

一、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();

 

 

二、錯誤處理:

若是已經加載了Winsock庫,則調用Winsock函數出錯後,一般會返回SOCKET_ERROR,而經過使用函數int WSAGetLastError() 能夠得到具體信息值,例如:

 

if ( SOCKET_ERROR == WSACleanup() ) 

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

    return 0; 

根據獲取錯誤信息值,能夠知道錯誤緣由,並進行相應的處理。

 

 

三、尋址:

想要進行通訊就須要知道彼此的地址,通常來講這個地址由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接受。

 

 

四、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()函數得到錯誤信息。

 

 

五、鏈接通訊實現過程:

連結通訊是基於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 

         ); 

 

 

六、無鏈接通訊實現過程:

無鏈接通訊是基於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來斷開鏈接和釋放資源

 

 

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

 

 

七、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

相關文章
相關標籤/搜索