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)。大多數系統都是小端存儲;
?
用下面的方式可以檢測是否為大端存儲:
print?
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庫:
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() 可以獲得具體信息值,例如:
print?
if ( SOCKET_ERROR == WSACleanup() )?
{?
??? cout << "WSACleanup error " << WSAGetLastError() << endl;?
??? return 0;?
}?
根據獲取錯誤信息值,可以知道錯誤原因,并進行相應的處理。
?
?
3、尋址:
想要進行通信就需要知道彼此的地址,一般來說這個地址由IP和端口號來決定。在Winsock中使用SOCKADDR_IN結構來指定地址信息:
print?
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方法:
print?
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與地址結構進行關聯;
print?
int?
WSAAPI?
bind(?
???? IN SOCKET s,?????????????? //一個用于監聽的socket?
???? IN const struct sockaddr FAR * name,?? //指向進行綁定的地址結構?
???? IN int namelen???????????? //進行綁定的地址結構的大小?
???? );?
使用listen將bind成功的監聽socket狀態設置為監聽狀態;
print?
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的原因。
?
?
三、簡單實踐
?
利用上述內容,實現一個基于TCP/IP的連接通信。
?
服務端:
?
//******************************************************************
print?
#include "stdafx.h"?
?
#include <iostream>?
?
#include <WinSock2.h>?
?
#pragma comment(lib, "WS2_32")?
?
?
using namespace std;?
?
?
?
# define REQUEST_BACKLOG 5?
?
?
//******************************?
//好吧不用寫這些糾結的函數,就是看著清晰些?
//初始化Winsock?
bool 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;?
}?
?
?
//結束Winsock?
void 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 );?
??? }?
}?
?
?
//bind?
bool 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;?
}?
?
?
//listen?
bool 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[])?
{?
?
??? //初始化Winsock?
??? WSADATA wsadata;?
??? if ( !InitWSA( MAKEWORD(2,2), &wsadata ) )?
??? {?
??????? return 0;?
??? }?
?
?
??? //指定連接ip地址和服務器口?
?
??? SOCKADDR_IN InternetAddr;?
??? //char FAR strIP[]? = "198.0.0.0";?
??? char FAR strIP[]??? = "";?
??? INT nPortID???????? = 5150;?
??? InitSockAddrByIP( &InternetAddr, strIP, nPortID );?
?
?
?
??? //創建listener_socket?
??? SOCKET listener_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );?
??? if ( INVALID_SOCKET == listener_socket )?
??? {?
??????? cout << "listener_socket creat failed " << endl;?
??????? return 0;?
??? }?
?
?
??? //bind?
??? if ( !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 );?
?
?
??? //查找可讀的socket?
??? while( 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 while?
?
?
??? closesocket( listener_socket );?
??? cleanWSA();?
?
??? return 0;?
}?
?
?
?
客戶端:
?
//******************************************************************
print?
#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;?
?
??? //初始化winsock?
?
??? WSADATA 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,不知道就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?
??? SOCKET 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网络编程快速入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rtp问题引领汇总
- 下一篇: 深入浅出根据函数调用过程谈栈回溯原理