winsock編程學習筆記


以下部分轉自博客http://blog.csdn.net/phunxm/article/details/5085869

套接字地址(sockaddr、sockaddr_in)

1 /*
2  * Structure used by kernel to store most addresses.
3  */
4 struct sockaddr {
5        u_short sa_family;/* address family,AF_X */
6        char    sa_data[14];/* up to 14 bytes of direct address */
7 };

包含了一些遠程電腦的地址、端口和套接字的數目,它里面的數據是混雜在一起的。sa_data域的定義有些不確定性,注釋暗示內容可能超過14個字節。這種不確定性是經過深思熟慮的。套接字是個非常強大的接口。多數人可能認為比Internet接口強不到哪里——大多數應用現在很可能都用它——套接字可被用於幾乎任何種類的進程間通信,Internet(更精確的說是IP)只是其支持的協議簇中的一種。

1 /*
2  * Socket address, internet style.
3  */
4 struct sockaddr_in {
5        short   sin_family;/* internet address family */
6        u_short sin_port;/* port number */
7        struct  in_addr sin_addr;/* internet address */
8        char    sin_zero[8];/* padding bits */
9 };

這個結構提供了方便的手段來訪問socket address(struct sockaddr)結構中的每一個元素。注意sin_zero[8]是為了使sockaddr和sockaddr_in結構具有相同的尺寸,使用sockaddr_in的時候要把sin_zero全部設為零(使用memset函數)。

 

Winsock庫的加載和卸載

要使用Windows Socket API進行編程,首先必須調用WSAStartup()函數初始化Winsock動態庫。

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

  • 參數一wVersionRequested:為我們要求初始化的Winsock版本號
  • 參數二lpWSAData:為實際初始化成功的WSA(Windows Socket API)版本信息。

 

套接字的創建和釋放

要使用套接字,首先必須調用socket()函數創建一個套接字描述符,就如同操作文件時,首先得調用fopen()函數打開一個文件。

1 // The socket function creates a socket that is bound to a specific service provider.
2 SOCKET socket(int af,// [in] Address family specification.
3            int type,// [in] Type specification for the new socket.
4            int protocol// [in] Protocol to be used with the socket that is specific to the indicated address family.
5            );
1 // The closesocket function closes an existing socket.
2 int closesocket(
3     SOCKET s// [in] Descriptor identifying the socket to close.
4 );

 af:通信協議的協議族 ,AF_INET(IPv4)或 AF_INET6(IPv6)            

 type:指定要創建的套接字的類型。SOCK_STREAM, SOCK_ DGRAM,SOCK_RAW

 protocol指定應用程序所使用的通信協議 :

         IPPROTO_TCP, IPPROTO_UDP, 0(如果不想指定)

 

綁定套接字到指定的IP地址和端口

對於傳輸套接字,在執行收發數據前需要對本地端口進行綁定,這是因為傳輸層需要使用端口來區分具體的通信端點

1 // The bind function associates a local address with a socket.
2 int bind(
3        SOCKET s, // [in] Descriptor identifying an unbound socket.
4        const struct sockaddr FAR *name, // [in] Address to assign to the socket from the SOCKADDR structure.
5        int namelen // [in] Length of the value in the name parameter.                        
6        );

成功返回0,失敗返回SOCKET_ERROR

TCP服務器設置套接字進入監聽狀態

服務器為了接受連接,首先使用socket()函數創建一個套接字,然后使用bind()函數將它綁定到一個本地地址(端口),再用listen()函數為到達的連接指定一個backlog。

1 // The listen function places a socket a state where it is listening for an incoming connection.
2 int listen(
3         SOCKET s,// [in] Descriptor identifying a bound, unconnected socket.
4         int backlog// [in] Maximum length of the queue of pending connections.
5         );

backlog參數指定了正在等待連接的最大隊列長度。這個參數非常重要,因為服務器完全可能同時收到幾個連接請求。假定backlog參數為2,如果三個客戶機同時發出請求,那么頭兩個會被放在一個“待決”(等待處理)隊列中,以便應用程序依次為它們提供服務。而第三個連接會造成一個WSAECONNREFUSED錯誤。注意,一旦服務器接受了一個連接(accept返回),那個連接請求就會從隊列中刪去,以便別人可繼續發出請求。

該函數執行成功返回0,失敗返回SOCKET_ERROR 

 

客戶端主動連接

1 // The connect function establishes a connection to a specified socket.
2 int connect(
3           SOCKET s,// [in] Descriptor identifying an unconnected socket.
4           const struct sockaddr FAR *name,// [in] Name of the socket to which the connection should be established.
5           int namelen// [in] Length of name.
6          );

客戶端是連接的發起者initiate),它通過調用connect()函數主動(active)連接服務器。參數二填寫欲連接的目標服務器的地址。如果連接的計算機並沒有在指定端口上監聽,則connect()調用返回SOCKET_ERROR,WSAGetLastError()=WSAECONNREFUSED;另一種錯誤是WSAETIMEOUT,例如由於路由或網絡故障,客戶端遲遲接受不到服務器回饋的[SYN,ACK]信號。

 

TCP服務器接受客戶連接請求

1 // The accept function permits an incoming connection attempt on a socket.
2 SOCKET accept(
3            SOCKET s,// [in] Descriptor identifying a socket that has been placed in a listening state with the listen function.
4            struct sockaddr FAR *addr,// [out] receives the address of the connecting entity, as known to the communications layer.
5            int FAR *addrlen// [out] the length of addr.
6            );

服務器進入listen狀態后,循環調用accept()接受客戶的連接。參數一為監聽套接字;參數二為遠端客戶的地址信息;該函數返回一個套接字句柄,負責后續與該遠端客戶的會話通信。監聽套接字總是默默無聞的在門口守望(listen),迎接(accept)客戶的到來並安排服務員(會話套接字)接客。

該函數執行成功返回新的socket,失敗返回SOCKET_ERROR 

 

在一個已綁定或已連接的套接字上獲取連接名和對方地址信息獲取sockaddr

1 int getsockname (SOCKET s, struct sockaddr *name, int* namelen);

獲取hostname

Host即通常意義上的機器名(Machine Name)或域名(Domain Name)。

1 int gethostname (char FAR *name, int namelen);

gethostname()函數可以取得調用主機的機器名。返回的這個name傳給gethostbyname()調用可以取得相應IP地址。

1 struct hostent* gethostbyname(const char* name);

gethostbyname()函數主要用來做DNS解析,傳入域名(例如www.baidu.com),返回hostent結構。struct hostent存放主機信息。

 1 /*
 2  * Structures returned by network data base library, taken from the
 3  * BSD file netdb.h. All addresses are supplied in host order, and
 4  * returned in network order (suitable for use in system calls).
 5  */
 6 struct hostent {
 7        char   FAR *h_name;          /* official name of host */
 8        char   FAR *FAR *h_aliases; /* alias list */
 9        short  h_addrtype;            /* host address type */
10        short  h_length;              /* length of address */
11        char   FAR *FAR *h_addr_list;/* list of addresses */
12 #defineh_addr h_addr_list[0]         /* address, for backward compat */
13 };
14 /* Microsoft Windows Extended data types */
15 typedef struct hostent HOSTENT, *PHOSTENT, *LPHOSTENT;

以下代碼段獲取百度(www.baidu.com)機器名和地址。

1 struct hostent *pHostBaiDu = gethostbyname("www.baidu.com");
2 printf("Host name: %s/n", pHostBaiDu->h_name);
3 printf("IP Address: %s/n", inet_ntoa(*((struct in_addr*)pHostBaiDu->h_addr)));

 

I/O通信

從I/O的角度來看,套接字也是文件,它提供了同文件讀寫(fread()/fwrite())對應的收發數據操作接口:send()/recv()。

發送數據

1 // The send function sends data on a connected socket.
2 int send(
3        SOCKET s,// [in] Descriptor identifying a connected socket.
4        const char FAR *buf,// [in] Buffer containing the data to be transmitted.
5        int len,// [in] Length of the data in buf.
6        int flags// [in] Indicator specifying the way in which the call is made.
7        );

send()函數在一個已連接的套接字s上執行數據發送操作。對於客戶機而言,發送的目標地址即connect()調用時所指定的地址;對於服務器而言,發送的目標地址即accept()調用所返回的地址。發送的內容為保存在緩沖區buf中,發送的內容長度為len。最后一個參數flags,通常情況下填0。

成功返回發送字節數,出錯返回SOCKET_ERROR

 

接收數據

1 // The recv function receives data from a connected or bound socket.
2 int recv(
3        SOCKET s,// [in] Descriptor identifying a connected socket.
4        char FAR *buf,// [out] Buffer for the incoming data.
5        int len,// [in] Length of buf.
6        int flags// [in] Flag specifying the way in which the call is made.
7        );

recv()函數在一個已連接的套接字s上執行數據接收操作。對於客戶機而言,數據的源地址即connect()調用時所指定的地址;對於服務器而言,數據的源地址即accept()調用所返回的地址。接收的內容為保存至長度為len的緩沖區buf,最后一個參數flags,通常情況下填0。

成功返回接收的數據的字節數量,失敗返回SOCKET_ERROR,如果接受數據時網絡中斷返回0。

 

關閉套接字(TCP連接)

1 // The shutdown function disables sends or receives on a socket.
2 int shutdown(
3            SOCKET s,// [in] Descriptor identifying a socket.
4            int how// [in] Flag that describes what types of operation will no longer be allowed.
5            );

 

WinSock TCP C/S通信示例

 

 

簡單通信示例:

客戶端代碼:

 1 #include <winsock2.h>
 2 #include <stdio.h> 
 3 #define SERVPORT    5050              // 端口為5150
 4 #define MAXDATASIZE 100
 5 #define SERVIP      “127.0.0.1”      // 服務器IP地址為“127.0.0.1”,
 6 #pragma comment(lib,"ws2_32.lib")
 7  
 8 void main(int argc, char *argv[])
 9 {
10     WSADATA              wsaData;
11     SOCKET               sConnect;
12     SOCKADDR_IN          serverAddr;    
13     int            recvbytes;
14     char            buf[MAXDATASIZE]; 
15     WSAStartup(MAKEWORD(2,2), &wsaData);
16     sConnect = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
17     serverAddr.sin_family = AF_INET;
18     serverAddr.sin_port = htons(SERVPORT);    
19     serverAddr.sin_addr.s_addr = inet_addr(SERVIP);
20     memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero));
21     if(
22     connect(sConnect,   (SOCKADDR*)&serverAddr,sizeof(SOCKADDR))==SOCKET_ERROR)
23     {
24         printf("connect failed!\n");
25         return;
26     }
27     recvbytes = recv(sConnect, buf, MAXDATASIZE, 0);
28     if (recvbytes == SOCKET_ERROR){    printf("recv failed!\n");}
29     else{buf[recvbytes] = '\0';printf("%s\n",buf);    } 
30     closesocket(sConnect);
31     WSACleanup();
32 }
33     

服務端代碼:

 1 #include <winsock2.h>
 2 #include <stdio.h> 
 3 #define SERVPORT    5050
 4 #pragma comment(lib,"ws2_32.lib") 
 5 void main(void)
 6 {
 7     WSADATA              wsaData;
 8     SOCKET               sListen;        // 監聽socket
 9     SOCKET               sClient;        // 連接socket
10     SOCKADDR_IN          serverAddr;        // 本機地址信息
11     SOCKADDR_IN          clientAddr;        // 客戶端地址信息
12     int                    clientAddrLen;    // 地址結構的長度
13     int                  nResult;
14     WSAStartup(MAKEWORD(2,2), &wsaData);   
15     sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);         
16     serverAddr.sin_family = AF_INET;
17     serverAddr.sin_port = htons(SERVPORT);    
18     serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
19     memset(&(serverAddr.sin_zero), 0, sizeof(serverAddr.sin_zero)); 
20     nResult = bind(sListen, (SOCKADDR *)&serverAddr, sizeof(SOCKADDR));
21    if (nResult == SOCKET_ERROR)
22    {    
23         printf("bind failed!\n"); 
24     return;   
25 26     listen(sListen, 5);  
27     while(1)
28    { 
29         clientAddrLen = sizeof (SOCKADDR);
30         sClient = accept(sListen, (SOCKADDR *)&clientAddr, &clientAddrLen);
31         if(sClient == INVALID_SOCKET){printf("Accept failed!");    }
32         else{
33             printf("Accepted client: %s : %d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));        
34         nResult = send(sClient, "Connect success!", 16, 0);
35         if (nResult == SOCKET_ERROR){printf("send failed!");}
36     }
37     closesocket(sClient);
38     }
39      closesocket(sListen); 
40      WSACleanup();
41 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM