看了王艷平老師編著的《Windows程序設計》裡的WinSock編程,在這裡整理一下。
u_short htons(u_short hostshort) //轉換一個u_short類型從主機字節順序到TCP/IP網絡字節順序
u_long htonl(u_long hostlong) //轉換一個u_long類型從主機字節順序到TCP/IP網絡字節順序
u_short ntohs(u_short netshort) //轉換一個u_short類型從TCP/IP網絡字節順序到主機字節順序
u_long ntohl(u_long netlong) //轉換一個u_long類型從TCP/IP網絡字節順序到主機字節順序
使用WinSock編程的一般步驟是確定的。可分為以下幾步:
①WinSock庫的裝入,初始化和釋放。
所有的WinSock函數都是從WS2_32.dll庫導出的,VC++在默認情況下並沒有連接到該庫,如果想使用WinSock API,就必須包含相應的庫文件。
#pragma comment(lib, “wsock32.lib”);
WSAstartup必須是應用程序首先調用的WinSock函數。他允許應用程序指定所需的Windows Sockets API的版本,獲取特定的WinSock實現的詳細信息。僅當這個函數調用成功之後,應用程序才能調用其他的Winsock API。
int WSAstartup(
WORD wVersionRequested, //應用程序支持的最高Winsock版本。高字節為次版本號,低字節為主版本號。
LPWSADATA lpWSAData)); //一個指向WSADATA結構的指針,他用來返回DLL庫的詳細信息。
函數調用成功返回0,否則要調用WSAGetLastError函數查看出錯的原因。此函數的作用相當於WIN32 API GetLastError,他取得最後發生錯誤的代碼。
每一個對WSAStartup的調用必須對應一個WSACleanup的調用,這個函數釋放Winsock庫。
int WSACleanup(void)。
②套接字的創建和關閉
使用套接字之前,必須調用socket函數創建一個套接字對象。此函數調用成功將返回套接字句柄,調用失敗返回INVALID_SOCKET(-1),可以通過WSAGetLastError取得錯誤信息。
SOCKET socket(
int af, //用來指定套接字使用的地址格式。WinSock中只支持AF_INET
int type, //用來指定套節字的類型,有SOCK_STREAM, SOCK_DGRAM, SOCK_RAW。
int protocol); //配合type參數使用,用來指定使用的協議類型,可以是IPPROTO_TCP等
當type參數指定為SOCK_STREAM或者SOCK_DGRAM時,系統已經明確確定使用TCP或者UDP協議來工作,所以protocol參數可以指定為0.
當不使用socket創建的套接字時,應該用closesocket函數將他關閉。如果沒有錯誤發生,函數返回0,否則返回SOCKET_ERROR。函數用法如下:
int closesocket(SOCKET s);
③綁定套接字到指定的IP地址和端口號(服務器)
為套接字關聯本地地址的函數是bind,用法如下
int bind(
SOCKET s, //套接字句柄
const struct sockaddr* name, //要關聯的本地地址
int namelen); //地址的長度
bind函數通過安排一個本地名稱到未命名的socket建立此socket的本地關聯。
//填充sockaddr_sin結構
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
//綁定這個套節字到一個本地地址
if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf(“Failed bind()\n”);
::WSACleanup();
return 0;
}
sockaddr_in結構中的sin_family字段用來指定地址家族,該字段和socket函數中的af參數含義相同,所以唯一可用的值就是AF_INET。sin_port和sin_addr字段分別指定套接字需要綁定的端口號的IP地址。放入這兩個字段的數據的字節順序必須是網絡字節順序。由於網絡字節順序和Intel CPU的字節順序剛好相反,所以必須首先調用htons函數進行轉換。
如果應用程序不關心所使用的地址,可以為互聯網地址指定為INADDR_ANY(系統自動使用當前主機配置的IP地址),為端口號指定為0(程序執行時會分配一個唯一的端口號到這個應用程序,其值在1024到5000之間)。應用程序可以在bind之後使用getsockname來知道為它分配的地址。但是要注意,知道套接字連接上之後getsockname才可能填寫互聯網地址,因為對一個主機來說可能有多個地址是可用的。
④設置套接字進入監聽狀態(服務器)
listen函數置套接字進入監聽狀態。
int listen(
SOCKET s, //套接字句柄
int backlog); //監聽對列中允許保—持的尚未處理的最大連接數量。
為了接受連接,首先用socket函數創建一個套接字,然後使用bing函數綁定他到一個本地地址,再用listen函數為達到的連接指定一個backlog,最後使用accept接受請求的連接。
listen函數只用在支持連接的套接字上,如SOCK_STREAM類型。函數成功執行之後,套接字s進入了被動模式,到來的連接會被通知,排隊等候接受處理。
在同一時間內處理多個連接請求的服務器通常使用listen函數:如果一個連接請求到達,並且排隊已滿,客戶端將接收WSAECONNREFUSED錯誤。
⑤接受連接(服務器)
accept函數用於接收到來的請求。
SOCKET accept(
SOCKET s, //套接字句柄
struct sockaddr* addr, //一個指向sockaddr結構的指針,用於取得對方的地址信息
int* addrlen); //是一個指向地址長度的指針
該函數在s上取出未處理連接中的第一個連接,然後為這個連接創建一個新的套接字,返回它的句柄。新創建的套接字是處理實際連接的套接字。它與s有相同的屬性。
程序默認工作在阻塞模式下,這種方式下如果沒有未處理的連接存在,accept函數會一直等待下去直到有新的連接才返回。
addrlen參數用於指定addr所指空間的大小,也用於返回返回地址的實際長度。如果addr或者addrlen是NULL,則沒有關於遠程地址的信息返回。
⑥請求連接(客戶端)
客戶端在創建套接字之後,要用connect函數請求與服務器連接,函數原型如下。
int connect(
SOCKET s, //套接字句柄
const struct sockaddr FAR* name, //指向sockaddr結構的指針,包含想要連接的服務器的地址信息
int namelen); //sockaddr結構的長度
第一個參數是此連接使用的客戶端套接字。另兩個參數用來尋址遠程套接字。
⑦收發數據
對流套接字來說,一般使用send和recv函數來收發數據。
int send(
SOCKET s, //套接字句柄
const char FAR* buf, //要發送數據的緩沖區地址
int len, //緩沖區長度
int flags); //指定了調用方式,通常設為0
int recv(
SOCKET s, //套接字句柄
char FAR* buf, //要接受數據的緩沖區地址
int len, //緩沖區長度
int flags); //指定了調用方式,通常設為0
send函數在一個連接的套接字上發送緩沖區內的數據,返回發揮數據的實際字節數。recv函數從對方接受數據,並存儲到他指定的緩沖區。flags參數在這兩個函數中通常指定為0.
在阻塞模式下,send將會阻塞線程的執行直到所有的數據發送完畢(或者一個錯誤發生),而recv函數將返回盡可能多的當前可用信息,一直到緩沖區指定的大小。