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;
}