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的緣由。