1. 程式人生 > >C++ Winsock小結

C++ Winsock小結

定址方式

Winsock(Socket API)中,套接字地址結構定義如下:

struct sockaddr_in{

short     sin_family;//指定地址家族即地址格式。TCP/IP設定為AF_INET。

unsignedshort  sin_port;//埠號碼。

struct in_addr  sin_addr;//IP地址。

char      sin_zero[8];//留作備用,需要指定為0。

};

其中結構成員變數sin_addr表示32位的IP地址結構,定義如下:

struct in_addr{

  union{

struct{

  unsigned char s_b1, s_b2, s_b3, s_b4;

}S_un_b;

struct{

  unsigned short s_w1, s_w2;

}S_un_w;

unsigned long S_addr;

 }S_un;

};

位元組順序

1、位元組順序格式

網路位元組順序(大端格式):低地址存放高位位元組,高地址存放低位位元組;

主機位元組順序(小端格式):低地址存放低位元組,高地址存放高位元組。

2、位元組順序轉換函式

1.u_short htons(u_short hostshort);//將一個u_short型別的IP地址從主機位元組順序轉換到網路位元組順序。

2.u_long htonl(u_long hostlong);//將一個u_long型別的IP地址從主機位元組順序轉換到網路位元組順序。

3.u_long ntohl(u_long netlong);//將一個u_long型別的IP地址從網路位元組順序轉換到主機位元組順序。

4.u_short ntohs(u_short netshort);//將一個u_short型別的IP地址從網路位元組順序轉換到主機位元組順序。

5.unsigned long inet_addr(const char FAR *cp);//將一個字串IP轉換到以網路位元組順序排列的IP地址。

6.char FAR* inet_ntoa(struct in_addr in);//將一個以網路位元組順序排列的IP地址轉換為一個字串IP。

MFC中CSocket類中相關函式

1.建立套接字

使用afxsock.h下的CSocket類建立套接字物件本質是呼叫該類的建構函式進行建立,原型如下:

CSocket::CSocket();

如果使用者需要建立套接字物件指標,則應該使用new關鍵字進行建立,程式碼如下:

CSocket *sock;//定義套接字指標物件。

sock=new CSocket;//使用new關鍵字建立套接字物件,返回物件的地址。

2.繫結地址資訊

呼叫該類的函式Bind()將套接字物件與伺服器地址資訊繫結在一起。函式原型如下:

BOOL Bind(const SOCKADDR* lpSockAddr, intnSockAddrLen);

如果函式呼叫成功,則返回true;否則,返回false。

1.引數lpSockAddr指定將要繫結的伺服器地址結構(指標可變,資料不可變);

2.引數nSockAddrLen表示地址結構的長度

伺服器端,當地址資訊繫結套接字成功後,還需要呼叫函式Listen()在指定埠監聽客戶端的連線請求。Listen()函式原型如下:

BOOL Listen(int nConnectionBacklog = 5);

引數nConnectionBacklog表示套接字監聽客戶端請求的最大數目,該引數有效範圍是[1,5],預設為5,表示該套接字只能監聽5個客戶端所傳送的連線請求。

3.連線伺服器

客戶端建立套接字成功以後,可以呼叫函式Connect()向伺服器傳送連線請求,函式原型如下:

BOOL Connect(const SOCKADDR* lpSockAddr,int nSockAddrLen);

函式呼叫成功後,返回true;否則,返回false。

1.引數lpSockAddr表示將連線的伺服器地址結構。

2.引數nSockAddrLen表示地址結構的長度大小。

4.資料交換

無論是伺服器,還是客戶端都是通過函式Send()和Receive()進行資料交換,函式原型如下:

virtual int Send(const void* lpBuf, intnBufLen, int nFlags = 0);

virtual int Receive(void* lpBuf, intnBufLen, int nFlags = 0);

函式Send()用於傳送指定緩衝區的資料;

函式Receive()用於接收對方傳送的資料,並將資料存放在指定緩衝區中。

1.引數lpBuf表示資料緩衝區地址;

2.引數nBufLen表示資料緩衝區的大小;

3.引數nFlags表示資料傳送或接收的標誌,一般情況下,該引數均設定為0

5.關閉套接字物件

當伺服器和客戶端的通訊完成以後,使用者還必須呼叫函式Close()將套接字物件關閉。否則,程式可能在退出時發生錯誤。該函式原型如下:

virtual void Close();

所有的Winsock函式均是從動態連結庫WS2_32.DLL中匯出的,在標頭檔案下邊使用#pragma comment(lib, “LibName.lib”)顯式連結LibName.lib庫。語句如下:

#pragma comment(lib, “ws2_32.lib”)  //連結ws2_32.lib庫,沒有分號。

呼叫WSAStartup()對該庫進行初始化,函式原型如下:

int WSAStartup(WORD  wVersionRequested, LPWSADATA  lpWSAData);

該函式呼叫成功,將返回0;否則,呼叫函式失敗。

1.引數wVersionRequested表示當前套接字型檔的版本號;

2.引數lpWSAData指向結構體WSADATA的指標變數,表示獲取到的套接字詳細資訊。

程式退出時,使用者還應該呼叫函式WSACleanup()釋放該套接字型檔,如下:

::WSACleanup();

在Socket  API中,建立套接字控制代碼的函式是socket()。函式原型如下:

SOCKET socket(int af, int type, intprotocol);

1.引數af指定套接字所使用的地址格式,本章只能設定成AF_INET;

2.引數type表示套接字型別,SOCK_STREAM表示流式套接字(TCP),SOCK_DGRAM表示資料包套接字(UDP),SOCK_RAW表示原始套接字(未提及);

3.引數protocol:如果引數type指定TCP或UDP,該引數可以設定為0。

對於伺服器而言,套接字建立成功後,還應該使用bind()函式將套接字與地址結構資訊相關聯,函式原型如下:

int bind(SOCKET s, const struct sockaddrFAR* name, int namelen);

1.引數s表示套接字控制代碼;

2.引數name表示地址結構資訊的變數指標;

3.引數namelen表示地址結構的大小。

函式呼叫成功,則返回0;否則函式呼叫失敗。

當伺服器程式將套接字控制代碼繫結套接字地址成功後,呼叫函式listen()實現監聽埠的功能,函式原型如下:

int listen(SOCKET s, int backlog);

1.引數s表示實現監聽功能的套接字控制代碼;

2.引數backlog指定監聽的最大連線數量。

注:該函式僅被用於流式套接字,如果多個客戶端同時向伺服器傳送連線請求,並且超過了最大監聽數,則客戶端將返回錯誤程式碼

4.連線

客戶端程式連線伺服器使用函式connect()實現,函式原型如下:

int connect(SOCKET s, const struct sockaddrFAR* name, int namelen);

1.引數s表示套接字控制代碼;

2.引數name表示將要連線的伺服器地址結構資訊的變數指標;

3.引數namelen表示地址結構的大小。

如果伺服器接收到客戶端的連線請求,則呼叫函式accept()接受該請求,函式原型如下:

SOCKET accept(SOCKET s, struct sockaddrFAR* addr, int FAR* addrlen);

1.引數s表示套接字控制代碼;

2.引數addr表示地址結構資訊的指標變數;

3.引數addrlen表示地址結構的大小。

如果函式呼叫成功,則返回一個新的套接字控制代碼用於通訊雙方資料的傳輸

呼叫函式send()和recv()進行資料的傳送和接收,函式原型如下:

int send(SOCKET s, const char FAR* buf, intlen, int flags);

int recv(SOCKET s, char FAR* buf, int len,int flags);

1.如果伺服器使用上面的函式進行資料手法,則引數s應該為監聽函式返回的新套接字控制代碼;如果客戶端使用以上函式進行資料收發,則引數s應該為客戶端建立的套接字控制代碼

2.send函式僅僅是把buf中的資料copy到s的傳送緩衝區的剩餘空間裡,copy資料成功後,返回copy的位元組數。

3.recv函式僅僅是把s的接收緩衝中的協議接收的資料copy到buf中,返回值為copy的位元組數。即返回值<0:出錯;返回值=0:連線關閉;返回值>0:接收的位元組數。

當套接字使用完畢或程式退出時,使用者應該呼叫函式closesocket()關閉套接字控制代碼,函式原型如下:

int closesocket(SOCKET s);

引數s表示即將關閉的套接字控制代碼。

#include <Winsock2.h>

#include <cstdio>

#include<iostream>

#pragma comment(lib,"ws2_32.lib")

using namespace std;

int main()

{

       /*載入winsock庫*/

       WSADATAwsaData;

    //引數:版本號,指向WSADATA結構體的指標

       WSAStartup(MAKEWORD(2,2), &wsaData);

       SOCKETsockData;

       /*構造監聽socket,流式socket

*引數:套接字所使用的地址格式,套接字型別,協議編號

*AF_INET是IPv4 網路協議的套接字型別

*/

       SOCKETsockServer = socket(AF_INET, SOCK_STREAM, 0);

       /*配置監聽地址和埠*/

       SOCKADDR_INaddrServer;

       addrServer.sin_port= htons(6000); //繫結埠6000

       addrServer.sin_addr.S_un.S_addr= htonl(INADDR_ANY);//INADDR_ANY表示任何IP,傳遞unsigned long型別的IP地址

       addrServer.sin_family= AF_INET; //指定地址格式,表示TCP/IP格式

       /*繫結監聽socket*/

       bind(sockServer,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));

       //Listen監聽端

       listen(sockServer,5);//5為等待連線數目

       cout<< "伺服器已啟動!" << endl <<"監聽中..." << endl;

       intlen = sizeof(SOCKADDR);

       charsendBuf[100] = "hello!";//傳送至客戶端的字串

       charrecvBuf[100];//接受客戶端返回的字串

       //阻塞程序,等待客戶端接入,直到有客戶端連線上來為止

       sockData= accept(sockServer, (SOCKADDR*)&addrServer, &len);

       while(1){

              //接收並列印客戶端資料

              recv(sockData,recvBuf, sizeof(recvBuf), 0);

              if(strcmp(recvBuf, "q") == 0){

                     cout<< "對方已關閉連線!" << endl;

                     break;

              }

              cout<< "recv:" << recvBuf << endl;

              //輸入併發送資料

              cout<< "send:";

              cin>> sendBuf;

              send(sockData,sendBuf, strlen(sendBuf) + 1, 0);

              if(strcmp(sendBuf, "q") == 0){

                     cout<< "主動關閉連線!" << endl;

                     break;

              }

       }

       //關閉socket

       closesocket(sockData);

       closesocket(sockServer);

       //釋放winsock庫

       WSACleanup();

       return0;

}

#include <Winsock2.h>

#include <cstdio>

#include<iostream>

#pragma comment(lib,"ws2_32.lib")

using namespace std;

int main()

{

       /*載入winsock庫*/

       WSADATAwsaData;

       //MAKEWORD(2,2)生成WORD型別引數,表示版本為2.2

       WSAStartup(MAKEWORD(2,2), &wsaData);

       //新建客戶端通訊socket,流式socket

       SOCKETsockClient = socket(AF_INET, SOCK_STREAM, 0);

       //定義要連線的服務端地址

       SOCKADDR_INaddrServer;  //服務端地址

       addrServer.sin_port= htons(6000);//連線埠6000

       addrServer.sin_addr.S_un.S_addr= inet_addr("127.0.0.1");//目標IP(127.0.0.1是回送地址)

       addrServer.sin_family= AF_INET;

       //連線到服務端

       connect(sockClient,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));

       charsendBuf[20]= "Hello Socket!";

       charrecvBuf[20];

       do{

              //傳送資料

              send(sockClient,sendBuf, strlen(sendBuf) + 1, 0);

              if(strcmp(sendBuf, "q") == 0){

                     cout<< "主動關閉連線!" << endl;

                     break;

              }

              //接收資料

              recv(sockClient,recvBuf, sizeof(recvBuf), 0);

              if(strcmp(recvBuf, "q") == 0){

                     cout<< "對方已關閉連線!" << endl;

                     break;

              }

              cout<< "recv:" << recvBuf << endl;

              cout<< "next send:";

              cin>> sendBuf;

       }while (1);

       //關閉socket

       closesocket(sockClient);

       //釋放winsock

       WSACleanup();

       return0;

}