Winsock網路編程快速入門

來源:互聯網
上載者:User
一、基本知識

 

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)  超文字傳輸通訊協定 (HTTP);

 

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 ){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庫:

intWSAAPIWSAStartup(    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來指定地址:
//建立一個地址int serverPort= 5150;char FAR serverIP[] = "192.168.1.102"; //本機ip,不知道就ipconfigSOCKADDR_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方法:
SOCKETWSAAPIsocket(   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與地址結構進行關聯;
intWSAAPIbind( IN SOCKET s,//一個用於監聽的socket IN const struct sockaddr FAR * name,//指向進行綁定的地址結構 IN int namelen//進行綁定的地址結構的大小 );
使用listen將bind成功的監聽socket狀態設定為監聽狀態;
intWSAAPIlisten(   IN SOCKET s,//一個用於監聽的socket,已經進行bind   IN int backlog//允許掛起串連的隊列的最大長度,超過這個長度後,再有串連將會失敗   );
 
使用accept接受通過監聽socket擷取的串連,成功後將返回的新的串連socket進行儲存以便資料轉送;
SOCKETWSAAPIaccept(   IN SOCKET s,//處於監聽模式的socket   OUT struct sockaddr FAR * addr,//指向一個地址結構,用來接受串連後獲得對方地址資訊   IN OUT int FAR * addrlen//指向一個整數,表示參數2指向地址結構的大小   );
 
 
用戶端:
初始化Winsock後,建立一個監聽socket和一個要已連線的服務器地址結構;
使用connect將socket和伺服器位址結構進行初始化串連,成功後將使用socket進行資料轉送;
intWSAAPIconnect(IN SOCKET s,//要建立串連的socketIN const struct sockaddr FAR * name,//指向儲存要建立串連資訊的地址結構IN int namelen//參數2指向地址結構的大小);
 
 
串連成功後,使用send、recv來進行資料轉送;
intWSAAPIsend( IN SOCKET s,//進行串連的socket IN const char FAR * buf,//指向發送資料的緩衝區 IN int len,//發送資料的字元數 IN int flags//一個標誌位,可以是0、MSG_DONTROUTE、MSG_OOB還可以是他們的或運算結果 );//返回已經發送的資料長度intWSAAPIrecv( IN SOCKET s,//進行串連的socket OUT char FAR * buf,//指向接受資料的緩衝區 IN int len,//準備接受資料位元組數或緩衝區的長度 IN int flags//可以是0、MSG_PEEK、MSG_OOB還可以是他們的或運算結果 );//返回已接受的資料長度
 
串連結束後,使用shutdown和closesocket來中斷連線和釋放資源;
intWSAAPIshutdown( IN SOCKET s,//要關閉的socket IN int how//關閉標誌:SD_RECEIVE、SD_SEND、SD_BOTH );
 
 
6、無串連通訊實現過程:
無串連通訊是基於UDP/IP實現的,UDP不能確保可靠的資料轉送,但能將資料發送到多個目標,或者接受多個源的資料。
初始化Winsock後,可以建立socket和用以進行通訊任意地址結構;
使用recvfrom通過socket和通訊的地址結構接受資料;
使用sendto通過socket和通訊的地址結構發送資料;
intWSAAPIrecvfrom( IN SOCKET s, OUT char FAR * buf, IN int len, IN int flags, OUT struct sockaddr FAR * from, IN OUT int FAR * fromlen );intWSAAPIsendto(   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()用於確定一個或多個套介面的狀態。對每一個套介面,調用者可查詢它的可讀性、可寫性及錯誤狀態資訊。
  
intWSAAPIselect(   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的原因。
  三、簡單實踐
 
利用上述內容,實現一個基於TCP/IP的串連通訊。
 
服務端:
 
//******************************************************************
#include "stdafx.h"#include <iostream>#include <WinSock2.h>#pragma comment(lib, "WS2_32")using namespace std;# define REQUEST_BACKLOG 5//******************************//好吧不用寫這些糾結的函數,就是看著清晰些//初始化Winsockbool InitWSA( const WORD &wVersion, WSADATA *wsadata ){int Ret = 0;if ( ( Ret = WSAStartup( wVersion,wsadata ) ) != 0 ){cout << "WSAStartup failed, error "  << Ret << endl;return false;}return true;}//結束Winsockvoid cleanWSA(){if ( WSACleanup() == SOCKET_ERROR ){cout << "WSACleanup failed, error "  << WSAGetLastError() << endl;}}//IPv4定址,通過ip填充SOCKADDR_IN結構void InitSockAddrByIP( SOCKADDR_IN *pSockAddr, const char FAR *strIP, const INT &nPortID ){pSockAddr->sin_family= AF_INET;pSockAddr->sin_port= htons( nPortID );if ( 0 != strlen(strIP) ){pSockAddr->sin_addr.s_addr= inet_addr( strIP );}else{pSockAddr->sin_addr.s_addr= htonl( INADDR_ANY );}}//bindbool bindAddr( const SOCKADDR_IN *pSockAddr, SOCKET *pSocket ){int bindResult = bind( *pSocket, (sockaddr *)(pSockAddr), sizeof(*pSockAddr));if ( SOCKET_ERROR == bindResult ){cout << "bind error :" << WSAGetLastError() << endl;return false;} return true;}//listenbool setListener( SOCKET *pSocket, int backlog ){int nResult = listen( *pSocket, backlog );if ( SOCKET_ERROR == nResult ){cout << "listen error :" << WSAGetLastError() << endl;return false;} return true;}//******************************//程式入口int _tmain(int argc, _TCHAR* argv[]){//初始化WinsockWSADATA wsadata;if ( !InitWSA( MAKEWORD(2,2), &wsadata ) ){return 0;}//指定串連ip地址和伺服器口SOCKADDR_IN InternetAddr;//char FAR strIP[]= "198.0.0.0";char FAR strIP[]= "";INTnPortID= 5150;InitSockAddrByIP( &InternetAddr, strIP, nPortID );//建立listener_socketSOCKET listener_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );if ( INVALID_SOCKET == listener_socket ){cout << "listener_socket creat failed " << endl;return 0;}//bindif ( !bindAddr( &InternetAddr, &listener_socket ) ){return 0;}//監聽if ( !setListener( &listener_socket, REQUEST_BACKLOG ) ){return 0;}cout << "server started~~~ " << endl;//建立 socket儲存結構fd_set fdSocket;FD_ZERO( &fdSocket );FD_SET( listener_socket, &fdSocket );//尋找可讀的 socketwhile( true ){fd_set fdSocket_temp;fdSocket_temp = fdSocket;fd_set fdRead;fdRead = fdSocket;fd_set fdExceptds;fdExceptds = fdSocket;int nResult_select = select( 0, &fdRead, NULL, &fdExceptds, NULL );if ( 0 < nResult_select ){unsigned int socket_count = fdSocket_temp.fd_count;for ( unsigned int i=0; i< socket_count; i++ ){//可讀的if ( FD_ISSET( fdSocket_temp.fd_array[i], &fdRead ) ){//找到所有可讀串連if ( fdSocket_temp.fd_array[i] == listener_socket ){if ( fdSocket.fd_count < FD_SETSIZE ){//接受新的串連SOCKADDR_IN ClientAddr;int addrlen = static_cast<int>(sizeof(ClientAddr));//一定要賦值SOCKET newClient_socket = accept( listener_socket, (sockaddr *)&ClientAddr, &addrlen );if ( INVALID_SOCKET == newClient_socket ){cout << " accept error " << WSAGetLastError() << endl;}else{FD_SET( newClient_socket, &fdSocket );cout << "find new connect: " << inet_ntoa( ClientAddr.sin_addr ) << endl;}}else{cout<<"too much connections"<<endl;continue;}} else{//接收資料char recvbuff[1024];int  ret = 0;ret = recv( fdSocket_temp.fd_array[i], recvbuff, static_cast<int>( strlen(recvbuff) ), 0 );if ( 0 < ret ){recvbuff[ret] = '\0';cout << "recv : " << recvbuff << endl;//回複char backbuf[1024] = "receive info!";send( fdSocket_temp.fd_array[i], backbuf, static_cast<int>( strlen(backbuf) ), 0 );}else{//該串連斷開closesocket( fdSocket_temp.fd_array[i] );FD_CLR( fdSocket_temp.fd_array[i], &fdSocket );}}}else if( fdSocket_temp.fd_array[i] != listener_socket ){//該串連斷開closesocket( fdSocket_temp.fd_array[i] );FD_CLR( fdSocket_temp.fd_array[i], &fdSocket );}if ( FD_ISSET( fdSocket_temp.fd_array[i], &fdExceptds )  &&  (fdSocket_temp.fd_array[i] != listener_socket) ){//該串連斷開closesocket( fdSocket_temp.fd_array[i] );FD_CLR( fdSocket_temp.fd_array[i], &fdSocket );}}//end for}else if( SOCKET_ERROR == nResult_select ){cout << "select error : " << WSAGetLastError() << endl;return 0;}Sleep( 50 );//不要挑戰你的機器}//end whileclosesocket( listener_socket );cleanWSA();return 0;}

 

 


 
用戶端:
 
//******************************************************************
#include "stdafx.h"#include <WinSock2.h>#include <iostream>#pragma comment(lib, "WS2_32")using namespace std;int _tmain(int argc, _TCHAR* argv[]){int result = 0;//初始化winsockWSADATA wsadata;result = WSAStartup( MAKEWORD(2,2), &wsadata );if ( 0 != result ){cout << "WSAStartup error " << result << endl;return 0;}//建立一個地址int serverPort= 5150;char FAR serverIP[] = "192.168.1.102"; //本機ip,不知道就ipconfigSOCKADDR_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) );//建立一個socketSOCKET socket_toServer;socket_toServer = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );if ( INVALID_SOCKET == socket_toServer ){cout << "socket_toServer creat failed " << WSAGetLastError() << endl;return 0;}//串連result = connect( socket_toServer, (sockaddr *)&serverAddr, serverAddr_size );if ( SOCKET_ERROR == result ){cout << "connect error :" << WSAGetLastError() << endl;return 0;}char sendbuff[2048];char recvbuff[2048];while( true ){cout << "input send info:" << endl;cin >> sendbuff;int ret = send( socket_toServer, sendbuff, static_cast<int>( strlen(sendbuff) ), 0 );if ( SOCKET_ERROR == ret ){cout << "send error " << WSAGetLastError() <<  endl;break;}//處理接受的訊息,由於之前沒有accept和listen,這裡使用recvfrom來接受int nRecv = 0;nRecv = recvfrom( socket_toServer, recvbuff, static_cast<int>( strlen(recvbuff) ), 0, (sockaddr *)&serverAddr, &serverAddr_size );if ( 0 < nRecv ){recvbuff[nRecv] = '\0';cout << "receive : " << recvbuff << endl;cout << "from : " << inet_ntoa( serverAddr.sin_addr ) << endl;cout << " " << endl;}}//清除各種資料和連結closesocket( socket_toServer );if ( SOCKET_ERROR == WSACleanup() ){cout << "WSACleanup error " << WSAGetLastError() << endl;return 0;}return 0;}

 

如題,上述內容只是協助快速入門,要掌握Winsock還要經過大量的學習和實踐。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.