windows socket程式設計總結
windows的網路程式設計介面沒有像linux那麼豐富,功能也要少很多,下面針對幾個主要的介面做一下介紹:
1. int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
這個函式比較簡單,目的是初始化socket,所有其他socket介面呼叫之前都要呼叫這個介面,呼叫成功會返回0,呼叫失敗返回非0值。
例:
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
{ */ printf("WSAStartup failed with error: %d\n", err); return 1; }
2. int WSACleanup(void);
此函式與 WSAStartup 相對,是釋放socket庫資源,到所有的socket函式後面使用,一般在程式結束的地方呼叫。
3. SOCKET socket(int af,int type,int protocol);
此函式為建立一個SOCKET物件,針對TCP和UDP傳入的引數會有點不一樣,呼叫失敗會返回INVALID_SOCKET。
例:TCP:SOCKET s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
UDP:SOCKET s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
4. int connect(SOCKETs, const struct sockaddr*name, intnamelen);
函式呼叫失敗返回SOCKET_ERROR.
此函式在TCP中的使用:
a.此函式呼叫成功客戶端與伺服器之間就建立了一條連結。
b.若TCP客戶沒有收到SYN分節的響應,則返回ETIMEOUT錯誤。
c.若TCP客戶收到RST響應,則返回ECONNREFUSED錯誤。
d.若TCP客戶connect失敗,則必須關閉socket重新建立以後才能再次呼叫connect函式。
e.呼叫此函式的TCP狀態變化:CLOSED->SYN_SENT->ESTABLISHED。
阻塞模式:
(1)如果TCP服務端沒有開啟,則connect會立馬返回SOCKET_ERROR(-1),呼叫WSAGetLastError會返回WSAECONNREFUSED(10061)錯誤。
(2)如果TCP服務端執行正常(服務端必須呼叫監聽函式(listen)以後,TCP/IP核心才會接收TCP客戶端的連線),則connect會立馬返回0,表示連線成功。
非阻塞模式:
(1)如果TCP服務端沒有開啟,則connect會立馬返回SOCKET_ERROR(-1),呼叫WSAGetLastError會返回WSAEWOULDBLOCK(10035)錯誤。
(2)如果TCP服務端執行正常,則connect會立馬返回SOCKET_ERROR(-1),呼叫WSAGetLastError會返回WSAEWOULDBLOCK(10035)錯誤。
由以上可知,如果將socket設定為非阻塞,通過返回值是沒有辦法判斷connect是否連線成功,此時要判斷connect是否呼叫成功,需要先呼叫select,發現描述符變成可寫以後,則connect成功,否則失敗。
例.阻塞模式呼叫:
struct sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(27015); // Connect to server.
connect(s, (SOCKADDR *) & clientService, sizeof (clientService));
非阻塞模式呼叫:
FD_SET mask;
u_long value=1;
TIMEVAL timeout;
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(27015);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ioctlsocket(sSocket,FIONBIO,&value);//設定為非阻塞
connect(sSocket,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
timeout.tv_sec=2;
timeout.tv_usec=0;
FD_ZERO(&mask);
FD_SET(sSocket,&mask);
value=select(NULL,NULL,&mask,NULL,&timeout);
if(value && value!=SOCKET_ERROR)
{
//連線成功
value = 0;
ioctlsocket(sSocket,FIONBIO,&value);//設定為阻塞
}
else
{
shutdown(sSocket,SD_BOTH);
closesocket(sSocket);
sSocket = 0;
}
此函式在UDP中的使用:
a.UDP中也能呼叫此函式,但是並沒有建立一條真正的連結,只是限定UDP的通訊對端的IP和埠。由於UDP有了對端的IP和埠,因此傳送和接收資料的時候也可以使用send和recv函式,後面會介紹這2個函式。
5. SOCKET accept(SOCKET s,struct sockaddr *addr,int *addrlen);
此函式為TCP伺服器用來接收TCP客戶端的連線的。
如果將 s 監聽的socket設定阻塞模式的,此函式會一直阻塞到與客戶端連線到來為止。
如果將 s 監聽的socket設定為非阻塞模式,此函式呼叫失敗會返回INVALID_SOCKET,如果呼叫WSAGetLastError函式返回WSAEWOULDBLOCK(10035)表示還沒有TCP客戶端來連線TCP伺服器。
如果監聽的 s 為非阻塞模式,則收到的socket也為非阻塞模式。
如果監聽的 s 為阻塞模式,則收到的socket也為阻塞模式。
例:阻塞模式:SOCKET sockClient=accept(sockListen,NULL,NULL);//此函式返回表示已經有客戶端連線到來,所以sockClient不會為INVALID_SOCKET.
非阻塞模式:SOCKETsockClient=accept(sockListen,NULL,NULL);
if(INVALID_SOCKET==sockClient)//表示此次呼叫沒有客戶端連線到來
{
printf("TCP_Server accept failed!\n");
continue;
}
6.int listen(SOCKETs,intbacklog);
此函式為TCP伺服器用來將 SOCKET s由主動模式變為被動模式。 backlog 為TCP協議棧中已經完成3次握手和正在進行3次握手的連線數。比如通常將backlog設定成5,則最多同一時間只能有5個客戶端連線過來。等其中有一連線已經被accept介面取出以後,又可以有新的客戶端連線進來。
函式呼叫失敗返回:SOCKET_ERROR。
例:if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR)
printf("listen function failed with error: %d\n", WSAGetLastError());
7.int bind(SOCKETs,const struct sockaddr*name,intnamelen);
此函式為 SOCKET s 關聯一個本地地址。呼叫失敗,返回SOCKET_ERROR。
如果不呼叫bind函式,當呼叫connect(TCP客戶端)和listen(TCP服務端)時,核心就要為相應的套接字選擇一個臨時埠。指定埠0為未指定埠。bind被呼叫時選擇一個臨時埠,若未指定IP地址(INADDR_ANY),則核心將等到套接字已經連線(TCP)或已在套接字上發出資料報(UDP)時選擇IP。例:
struct service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr("127.0.0.1"); service.sin_port = htons(27015); // Bind the socket. iResult = bind(ListenSocket, (SOCKADDR *) &service, sizeof (service)); if (iResult == SOCKET_ERROR) {}
8.intsendto(SOCKET s,const char * buf,int len,int flags,const struct sockaddr * to,int tolen);
int recvfrom(SOCKET s,char * buf,int len,int flags,struct sockaddr * from,int * fromlen);
int send(SOCKET s,const char * buf,int len,int flags);
阻塞模式:
(1)如果對端沒有接收,並且TCP/IP緩衝區也已經滿,則send函式會阻塞,直到有緩衝區存放為止。
(2)如果對端關閉socket,則send函式返回SOCKET_ERROR(-1),呼叫WSAGetLastError函式返回WSAECONNABORTED(10053)或者WSAECONNRESET(10054)錯誤。
非阻塞模式:
(1)如果對端沒有接收,並且TCP/IP緩衝區也已經滿,則send函式會返回SOCKET_ERROR(-1),呼叫WSAGetLastError函式返回WSAEWOULDBLOCK(10035)錯誤。
(2)如果對端關閉socket,則send函式返回SOCKET_ERROR(-1),呼叫WSAGetLastError函式返回WSAECONNABORTED(10053)或者WSAECONNRESET(10054)錯誤。
int recv(SOCKET s,char * buf,int len,int flags);
阻塞模式:
(1)recv會一直等待,如果有資料到來則接收資料,返回接收的資料大小。
(2)recv會一直等待,如果對端關閉了socket,則recv會返回0.
非阻塞模式:
(1)recv會立馬返回,如果沒有任何資料能接收,則返回SOCKET_ERROR(-1),呼叫WSAGetLastError函式則返回WSAEWOULDBLOCK(10035)錯誤。
(2)recv會立馬返回,如果對端關閉了socket,則recv會返回0.
(3)recv會立馬返回,如果TCP有資料接收,則直接返回接收到的資料大小。
以上4個函式我TCP和UDP用來接收和傳送資料的,在呼叫這幾個介面之前可以呼叫ioctlsocket 函式將這幾個函式變成非阻塞式的。
返回值為真實發送和接收的資料大小。
9.intshutdown(SOCKET s,int how);
此函式為關閉連線的接收部分或者傳送部分或者傳送接收都關閉,函式呼叫失敗返回:SOCKET_ERROR。
how引數對應下表:
Value | Meaning |
---|---|
|
關閉接收這一部分 |
|
關閉傳送這一部分 |
|
關閉傳送和接收 |
10. int closesocket(SOCKET s);
預設行為:把套接字標記為已關閉,然後返回到呼叫程序,TCP傳送完已經排隊的資料,傳送完畢以後發生正常的TCP 4分組終止序列,減少對應套接字的一個引用計.關閉傳送和接收。可以通過呼叫setsocketopt函式來改變這個預設行為。函式呼叫失敗返回:SOCKET_ERROR。
11.int getsockname(SOCKET s,struct sockaddr * name,int * namelen);
intgetpeername(SOCKET s,struct sockaddr * name,int * namelen);
此函式的作用:檢視核心選擇的臨時埠和IP地址,返回與套接字有關的本地協議地址和外地協議地址,TCP客戶沒有呼叫bind就直接呼叫connect,用getsockname來返回核心賦予該連線的本地IP地址和本地埠號.函式呼叫失敗返回:SOCKET_ERROR.
12.//主機位元組序和網路位元組序的轉換
u_shorthtons(u_short hostshort);
u_longhtonl(u_long hostlong);
u_shortntohs(u_short netshort);
u_longntohl(u_long netlong);
13.IPV4字串和整形(為網路位元組序)之間的轉換
unsigned long inet_addr(const char * cp);
char * inet_ntoa(struct in_addr in);
14 . int ioctlsocket(SOCKET s,long cmd,u_long FAR* argp);
此函式為設定connect,accept,recv,recvfrom是否為阻塞模式。如果呼叫失敗,返回:SOCKET_ERROR 。
例:
u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);
控制為阻塞方式。
u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);
控制為非阻塞方式。
14.設定獲取獲取socket的配置項。
int
getsockopt(SOCKET s,int level,int optname,char * optval,int * optlen);
intsetsockopt(SOCKET s,int level,int optname,const char * optval,int optlen);
由於這2個函式的引數輸入情況比較多,所以要想知道具體使用,請參考一下連結: SetSockOpt使用
15.int select( int nfds, fd_set FAR , fd_set FAR , fd_set FAR , const struct timeval FAR );
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.
Error code | Meaning |
---|---|
WSANOTINITIALISED | A successful WSAStartup call must occur before using this function. |
WSAEFAULT | The Windows Sockets implementation was unable to allocate needed resources for its internal operations, or the readfds, writefds, exceptfds, or timeval parameters are not part of the user address space. |
WSAENETDOWN | The network subsystem has failed. |
WSAEINVAL | The time-out value is not valid, or all three descriptor parameters were NULL. |
WSAEINTR | A blocking Windows Socket 1.1 call was canceled through WSACancelBlockingCall. |
WSAEINPROGRESS | A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. |
WSAENOTSOCK | One of the descriptor sets contains an entry that is not a socket. |
readfds:
- If listen has been called and a connection is pending, accept will succeed.
- Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
- Connection has been closed/reset/terminated.
writefds:
- If processing a connect call (nonblocking), connection has succeeded.
- Data can be sent.
exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
- OOB data is available for reading (only if SO_OOBINLINE is disabled).
對TCP和UDP的一些總結:
TCP是基於流模式的,傳送端經過多次傳送的資料,接收端也許一次就能全部接收。
UDP是基於報式的,傳送端每次傳送了多大的資料包,接收端就會接收到多大的資料包,如果因為傳送端傳送的某個資料包太大,接收端的資料緩衝區太小,則recvfrom函式會返回-1。表示此函式呼叫失敗。所以建議UDP接收緩衝區要設定的大一點。防止出現以上錯誤。
下面是我寫的一個TCP和UDP通訊demo,裡面測試了以上的部分介面,有些介面呼叫被註釋了,要測試的話要去掉註釋。