Windows程式設計__孫鑫C++Lesson14《網路編程》
本節要點:
1.網路通訊協定參考模型簡介
2.通訊端簡介
3.網路位元組順序
4.客戶機/伺服器模式簡介
5.Windows Sockets的實現
6.Windows網路編程函數準備
7.基於TCP的通訊端編程
8.基於UDP的通訊端編程
//*************************************************************************************
1.網路通訊協定參考模型簡介
OSI七層參考模型 TCP/IP的四層模型 這部分內容涉及理論知識比較豐富,請參見謝希仁《電腦網路》.
這裡僅把協助理解的圖列在下面:
2.通訊端簡介
通訊端存在於通訊地區中。通訊地區也叫地址族,它是一個抽象的概念,主要用於將通過通訊端通訊的進程的公有特性綜合結合在一起。
通訊端通常只與同一地區的通訊端交換資料(也有可能跨地區通訊,但這隻在執行了某種轉換進程後才能實現)。Windows Sockets只支援一個通訊地區:網際域(AF_INET),這個域被使用網際協議簇通訊的進程使用。
3.網路位元組順序
不同的電腦存放多位元組值得順序不同,有的機器在起始地址存放低位位元組(低位先存),有的機器在起始地址存放高位位元組(高位先存)。基於Intel的cpu,即我們常用的pc機採用的是低位先存。為保證資料的正確性,在網路通訊協定中需要使用指定的網路位元組順序。TCP/IP協議使用16位整數和32位整數的高位先存格式。
4.客戶機/伺服器模式簡介 詳細內容請參見謝希仁《電腦網路》.
這裡介紹如所示:
5.Windows Sockets的實現
(1)Windows Sockets是從伯克利通訊端擴充而來,以動態連結程式庫的形式提供給我們使用。Windows Sockets擴充主要是提供了一些非同步函數,並增加了符合Windows訊息驅動特性的網路事件非同步選擇機制。
(2)通訊端的類型
流式通訊端(SOCK_STREAM)提供連線導向、可靠的Data Transmission Service,資料無差錯、無重複的發送,且按發送順序接收,基於TCP協議實現的。
資料報式通訊端(SOCK_DGRAM)提供無串連服務。資料包以獨立包形式發送,不提供無錯保證,資料可能丟失或者重複,並且接收順序混亂,基於UDP協議實現的。
原始通訊端(SOCK_RAM) 這裡不做介紹。
6.Windows網路編程函數準備
(1)
int WSAStartup(
WORD wVersionRequested,//Windows Sockets版本資訊 高位元組指定最低版本,
//低位位元組表示主要版本
LPWSADATA lpWSAData //
);
該函數載入了Ws2_32.dll動態連結程式庫,每次成功調用後應用程式必須在使用完後調用WSACleanup 釋放Ws2_32.dll的資源,終止其使用.
(2)SOCKET socket(
int af, //指定地址族 對於TCP/IP協議的通訊端
int type, //指定Socket類型
int protocol //是與特定的地址家族相關的協議 指定為0那麼他就會根據
地址格式和通訊端類別,自動為你選擇一個合適的協議
);
建立一個和指定服務提供者綁定的通訊端。
(3)int bind(
SOCKET s, //待綁定的通訊端
const struct sockaddr FAR *name, //指定了該通訊端的本地地址資訊 指向sockaddr指標
int namelen //指定第二個參數的長度
);
用來綁定一個本地地址和通訊端。
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
在TCP/IP協議中,我們可以用SOCKADDR_IN結構來替換sockaddr,以方便我們填寫地址資訊。
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
(4)inet_addr 將包含一個IPv4地址的字串轉換為IN_ADDR結構的合適的地址
inet_ntoa 將一個IPv4地址轉換為形如a.b.c.d形式的字串
htonl 將一個 u_long結構的32位主機位元組序的數轉換為TCP/IP網路位元組序
htons 將一個 u_short結構的16位主機位元組序的數轉換為TCP/IP網路位元組序
(5)TCP發送資料
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);
TCP接受資料
int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);
(6)UDP發送資料
int sendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
);
UDP接受資料
int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR *from,
int FAR *fromlen
);
7.基於TCP的通訊端編程
注意:添加兩個程式到一個工程,通過選中組建來切換工程。先啟動伺服器端,後啟動用戶端。
TCP用戶端和伺服器端的編寫過程如所示:
//實驗代碼如下
//*************************************************************************************
//TcpSrv.cpp
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
//載入Ws2_32.dll
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1) {
WSACleanup( );
return;
}
SOCKET SocketSrv=socket(AF_INET,SOCK_STREAM,0 );//step1 建立通訊端
SOCKADDR_IN AddrSrv;
AddrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//網路位元組序
AddrSrv.sin_family=AF_INET;
AddrSrv.sin_port=htons(6000);//連接埠號碼兩個位元組
bind(SocketSrv,(SOCKADDR *)&AddrSrv,sizeof(SOCKADDR));//step2 綁定到通訊端
listen(SocketSrv,5);//step3 監聽串連請求 5為隊列長度
SOCKADDR_IN AddrClient;
int len=sizeof(SOCKADDR);//必須賦初值
//伺服器端程式迴圈運行
while(1)
{
SOCKET ScoketConn=accept(SocketSrv, (SOCKADDR*)&AddrClient, &len);//step4 接受請求 AddrClient接收串連請求方的資訊
char SendBuf[100];
sprintf(SendBuf,"Wellcome! Connect from %s Success!",inet_ntoa(AddrClient.sin_addr));
send(ScoketConn,SendBuf,strlen(SendBuf)+1,0);//step5 發送資料用串連的通訊端 不能用處於監聽狀態的通訊端
char RecvBuf[100];
recv(ScoketConn,RecvBuf,100,0);//step5 接收資料
printf("%s\n",RecvBuf);
closesocket(ScoketConn);
}
//實際上還需做以下操作 但上述是死迴圈因此沒有執行下述操作
closesocket(SocketSrv);//關閉通訊端
WSACleanup();//終止庫引用
}
//*************************************************************************************
//TcpClient.cpp
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1) {
WSACleanup( );
return;
}
SOCKET SocketClient=socket(AF_INET,SOCK_STREAM,0 );//step1 建立通訊端
SOCKADDR_IN AddrSrv;
AddrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//本地迴路地址
AddrSrv.sin_family=AF_INET;
AddrSrv.sin_port=htons(6000);
connect(SocketClient,(SOCKADDR *)&AddrSrv,sizeof(SOCKADDR));//step2 串連請求
char RecvBuf[100];
recv(SocketClient,RecvBuf,100,0);//接收伺服器資料
printf("%s\n",RecvBuf);
char sendbuf[100];
sprintf(sendbuf,"this is from %s","liming");
send(SocketClient,sendbuf,strlen(sendbuf)+1,0);//發送資料
closesocket(SocketClient);//關閉通訊端 釋放資源
WSACleanup();//終止通訊端庫的引用
}
//*************************************************************************************
啟動程式後(注意先啟動伺服器程式,後啟動用戶端程式)運行效果:
//*************************************************************************************
8.基於UDP的通訊端編程
UDP服務端和用戶端編寫過程如所示:
//實驗代碼如下
//*************************************************************************************
//NetSrv.cpp
#include <Winsock2.h>
#include <stdio.h>
void main()
{
//載入dll檔案
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1) {
WSACleanup( );
return;
}
SOCKET SockSrv=socket(AF_INET,SOCK_DGRAM,0);//建立基於UDP的通訊端
SOCKADDR_IN AddrSrv;
AddrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
AddrSrv.sin_family=AF_INET;
AddrSrv.sin_port=htons(6000);
bind(SockSrv,(SOCKADDR *)&AddrSrv,sizeof(SOCKADDR));//綁定通訊端
char RecvBuf[100];
char SendBuf[100];
char TempBuf[200];
SOCKADDR_IN AddrClient;
int len=sizeof(SOCKADDR);//監聽請求
while(1)
{
recvfrom(SockSrv,RecvBuf,100,0,(SOCKADDR *)&AddrClient,&len);//接受資料
if( 'q'==RecvBuf[0] )//對方是否想要對出聊天
{
sendto(SockSrv,"q",strlen("q")+1,0,(SOCKADDR *)&AddrClient,sizeof(SOCKADDR));//回傳一個'q'終止聊天
printf("chat end!\n");
break;//終止迴圈
}
sprintf(TempBuf,"Message from %s is :%s \n",inet_ntoa(AddrClient.sin_addr),RecvBuf);
puts(TempBuf);//顯示接受資料
printf("please reply:\n");
gets(SendBuf);
sendto(SockSrv,SendBuf,strlen(SendBuf)+1,0,(SOCKADDR *)&AddrClient,sizeof(SOCKADDR));//發送資料
}
//資源釋放處理
closesocket(SockSrv);
WSACleanup();
}
//*************************************************************************************
//NetClient.cpp
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1) {
WSACleanup( );
return;
}
SOCKET SockClient=socket(AF_INET,SOCK_DGRAM,0);//建立基於UDP的通訊端
SOCKADDR_IN AddrClient;
AddrClient.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
AddrClient.sin_family=AF_INET;
AddrClient.sin_port=htons(6000);
char RecvBuf[100];
char SendBuf[100];
char TempBuf[200];
SOCKADDR_IN AddrSrv;
int len=sizeof(SOCKADDR);
while(1)
{
printf("plase input Message\n");
gets(SendBuf);
sendto(SockClient,SendBuf,strlen(SendBuf)+1,0,(SOCKADDR *)&AddrClient,sizeof(SOCKADDR));//發送資料
recvfrom(SockClient,RecvBuf,100,0,(SOCKADDR *)&AddrSrv,&len);
if('q'==RecvBuf[0])//是否終止聊天
{
sendto(SockClient,"q",strlen("q")+1,0,(SOCKADDR *)&AddrClient,sizeof(SOCKADDR));
printf("chat end!\n");
break;
}
sprintf(TempBuf,"Message From %s is : %s\n",inet_ntoa(AddrSrv.sin_addr),RecvBuf);
puts(TempBuf);
}
//資源釋放處理
closesocket(SockClient);
WSACleanup();
}
//*************************************************************************************
啟動程式後(注意先啟動伺服器程式,後啟動用戶端程式)運行效果:
//*************************************************************************************
本節小結"
1.認識TCP協議和UDP協議的區別
2.認識Windows sockets通訊端
3.掌握簡單的基於TCP、基於UDP的伺服器端和用戶端的程式編寫
對於這幾個函數中的一些參數特別是如AddrClient.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");這種語句中的資料結構,用起來並不是很方便,還有待理解。