Visual C++網絡編程是指用戶使用MFC類庫(微軟基礎類庫)在VC編譯器中,以實現網絡應用。用戶通過VC編程實現的
網絡軟件可以在網絡中不同的計算機之間互傳文件,圖像等信息。
基礎知識:
如果用戶要進行VC網絡編程,則必須首先了解計算機網絡通信的基本框架和工作原理。在兩台或多台計算機之
間進行網絡通信時,其通信的雙方還必須遵循相同的通信原則好數據格式。
1.OSI七層網絡模型
OSI網絡模型是一個開放式系統互聯的參考模型。
發送信息的計算機 接收信息的計算機
7.應用層 7.應用層 表示計算機網絡中的物理設備。常見的有計算機網卡等
6.表示層 6.表示層 將傳輸數據進行壓縮與解壓縮
5.會話層 5.會話層 將傳輸數據進行網絡傳輸
4.數據傳輸層 4.數據傳輸層 進行信息的網絡傳輸
3.網絡層 3.網絡層 建立物理網絡的鏈接
2.數據鏈路層 2.數據鏈路層 將傳輸數據以某種格式進行表示
1.物理硬件層 1.物理硬件層 應用程序接口
發送方數據傳輸由高到低,接收方數據傳輸由低到高。各層數據對等通信。
2.TCP/IP協議
TCP/IP協議實際上是一個協議簇,其包含了很多協議。例如,FTP(文本傳輸協議),SMTP(郵件傳輸協議)等應用層協議。
TCP/IP協議的網絡模型有4層:
數據鏈路層:網卡等網絡硬件設備以及驅動程序
網絡層: IP協議等互聯協議
數據傳輸層:為應用程序提供通信方法,通常為TCP,UDP協議
應用層: 負責處理應用程序的實際用於層協議
在數據傳輸層中,包括了TCP和UDP協議。其中,TCP協議是基於面向連接的通信協議。其具有重發機制,即當數據破壞或者丟失時,發送方將重發該數據。而UDP協議是基於用戶數據報協議,屬於不可靠連接通信協議。(沒有重發機制)
3.C/S編程模型
C/S編程模型是基於可靠連接的通信模型。在通信的雙方必須使用各自的IP地址以及端口進行通信。否則,通信過程將無法實現。通常情況下,當用戶使用C/S模型進行通信時,其通信的一方稱為客戶端,則另一端稱為服務端。
服務器端等待客戶端連接請求的到來,這個過程稱為監聽過程。通常,服務器監聽功能是在特定的IP地址和端口上進行。然后客戶端像服務器發送連接請求,服務器響應連接請求則連接成功。否則,客戶端的鏈接請求失敗。
由於客戶端鏈接服務器時,需要使用服務器的IP地址和監聽端口號才能完成連接。所以,服務器的IP地址和端口號必須是固定的。部分協議所使用的端口號:HTTP協議(網頁瀏覽服務)所使用的端口號是80,FTP(超文本傳輸協議)端口號是21
注意:用戶在實際編程中,通信雙方的鏈接以及數據通信均是基於Socket(套接字)進行的
4.Socket套接字
網絡應用程序可以使用MFC中封裝的套接字類進行編程,也可以使用WindowsAPI函數進行程序開發。相比較而言,MFC網絡編程比較簡單一點,用戶使用也比較方便。
用戶在Windows中編寫網絡通信程序時,需要使用WindowsSockets(Windows套接字)。與Windows套接字相關的API函數稱為Winsock函數。
在網絡通信的雙方,均有各自的套接字,並且該套接字與特定的IP地址和端口號相關聯。通常,套接字主要有兩種類型,分別是流式套接字(SOCK_STREAM)和數據報套接字(SOCK_DGRAM)。其中,流式套接字是專門用於使用TCP協議通信的應用程序中,而數據報套接字則是專門用於使用UDP協議進行通信的應用程序中。
5.網絡字節順序
網絡字節順序是指TCP/IP協議中規定的數據傳輸使用格式,與之相對的字節順序是主機字節順序。網絡字節順序表示首先將數據中最重要的字節進行存儲。例如,當數據0x357451使用網絡字節順序進行存儲時,該值在內存中的存放順序將是0X35,.0x74,0x51。因為通信數據可能會在不同的機器之間進行傳輸,所以通信數據必須以相同的格式進行整理。只有經過格式處理的通信數據,才能在不同的機器之間進行傳輸。
6.Windows Sockets介紹
在MFC類庫中,幾乎封裝了Windows Sockets的全部功能。介紹兩個主要的套接字相關類,分別是CAsyncSocket類和CSocket類
微軟基礎庫中,CAsyncSocket類封裝了異步套接字的基本功能。使用該類進行網絡數據傳輸的步驟如下:
1).調用構造函數創建套接字對象
2).如果創建服務器端套接字,則調用函數Bind()綁定本地IP和端口,然后調用函數Listen()監聽客戶端的請求。如果請求到來,則調用函數Accept()響應該請求。如果創建客戶端套接字,則直接調用函數Connect()連接服務器即可。
3).調用Send()等功能函數進行數據傳輸與處理
4).關閉或銷毀套接字對象。
注意:在MFC中,所有類中均有一個變量m_hWnd表示該類的實例句柄
CSocket類派生於CAsyncSocket類。該類不但具有CAsyncSocket類的基本功能,還具有串行化功能。用戶在實際編程中,通過將CSocket類與CSocketFile類和CArchive類一起使用,能夠很好地管理數據以及發送數據。使用該類進行網絡編程的步驟如下:
1).創建CSocket類對象
2).如果創建服務器端套接字,則調用函數Bind()綁定本地IP和端口,然后調用函數Listen()監聽客戶端的請求。如果請求到來,則調用函數Accept()響應該請求。如果創建客戶端套接字,則直接調用函數Connect()連接服務器即可。
3).創建與CSocket類對象相關聯的CSocketFile類對象
4).創建與CSocket類相關聯的CArchive對象
5).使用CArchive類對象在客戶端和服務器之間進行數據傳輸
6).關閉或銷毀CSocket類,CSocketFile類和CArchive類的三個對象
7.Socket套接字編程
套接字是由美國伯克利大學提出並設計的一種在網絡中不不同主機之間進行數據交互的通信橋梁。在實際生活中,人么所使用的網絡通信軟件功能均是基於Socket套接字作為通信橋梁實現。所以,套接字在網絡編程中,有着非常重要得作用。
在Socket套接字編程中,為了准確定位通信雙方和數據傳輸的有效性,完整性,編程時必須使用統一的尋址方式和字節排序順序。
1)尋址方式:因為套接字需要在各種網絡協議中使用,所以為了區分程序所使用的網絡協議必須使用同一的尋址方式。例如,在TCPIP協議通信中,用戶使用IP地址和端口號進行確定通信雙方。而在其他的協議中不一定也使用該方式確定通信雙方。
在Winsock(Socket API)中,用戶可以使用TCP/IP地址家族中統一的套接字地址結構解決TCP/IP尋址中可能出現的問題。該套接字地址結構定義如下:
1 struct sockaddr_in{ 2 short sin_family; //指定地址家族即地址格式
3 unsigned short sin_port; //端口號碼
4 struct in_addr sin_addr; //IP地址
5 char sin_zero[8]; //留作備用,需要指定為0
6 }
在這個結構中,成員sin_family指定使用該套接字地址的地址家族。在這里必須設置為AF_INET,表示程序所使用的的地址家族是TCP/IP
注意:該結構的最后一個成員並未實際使用,主要是為了與第一個版本的套接字地址結構大小相同而設置。在實際使用時,將這8個字節直接設為0即可。
該結構成員變量sin_addr表示32位的IP地址結構。其結構定義如下:
1 struct in_addr{ 2 union{ //聯合函數(公用一塊內存,各成員中最長的為准)
3 struct{ 4 unsigned char s_b1,s_b2,s_b3,s_b4; 5 }S_un_b; //用4個u_char字符描述IP地址
6 struct{ 7 unsigned short s_w1,s_w2; 8 }S_un_w; //用2個u_short類型描述IP地址
9 struct{
10 unsigned long S_addr; //用1個u_logn類型描述IP地址
11 }S_un; 12 };
通常我們在網絡編程中使用一個u_long類型的字符進行描述IP地址,例如:
1 sockaddr_in addr; 2 addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.0")
在程序中,首先定義sockaddr_in結構對象addr,然后為IP地址結構in_addr中的成員S_addr賦值。因為結構成員S_addr所描述的IP地址均為網絡字節順序,所以程序調用inet_addr()函數將字符串IP轉換為以網絡字節順序排列的IP地址。
2)字節順序:在Socket套接字編程中,傳輸數據的排列順序以網絡字節順序和主機字節順序為主。通常情況下,如果用戶將數據通過網絡發送時,需要將數據轉換成以網絡字節順序排序,否則可能造成數據損壞。如果用戶是將網絡中接收到的數據存儲在本地計算機上,那么需要將數據轉換成以主機字節順序排列。
網絡字節順序將數據中最重要的字節首先進行存儲,而主機字節順序則將不重要的字節首先存儲。
IP地址結構in_addr中的成員S_addr的值均是以網絡字節順序排列。
2.1)字節順序轉換函數:
在Winsock中提供了幾個關於網絡字節順序與主機字節順序之間的轉換函數。函數定義如下:
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
實例:
1 sockaddr_in addr; //定義套接字地址結構變量
2 in_addr in_add; //定能夠以IP地址結構變量
3 addr.sin_family = AF_INET; //定義地址家族為TCP/IP
4 addr.sin_port = htons(80); //指定端口號
5 addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1") //將字符串IP轉換為網絡字節順序排列的IP
6 char address[] = inet_ntoa(addr.sin_addr.S_un.S_addr) //將網絡字節順序排列的IP裝換為字符串IP
在程序中,首先使用inet_addr()將字符串IP “127.0.0.1”轉換為以網絡字節順序排列的IP地址結構成員S_addr中。然后,再使用函數inet_ntoa()將成員所表示的IP值轉換成字符串IP
8.Socket相關函數
1)創建套接字
使用CSocket類創建套接字對象是通過該類的構造函數創建的。CSocket:CSocket()
創建CSocket類對象
1 CSocket sock;
如果用戶需要創建套接字對象指針,則應該使用關鍵字new進行創建。
1 CSocket* sock = new CSocket
2)綁定地址信息
BOOL Bind(const SOCKADDR* lpSockAddr,int nSockAddrLen);
該函數的作用是將套接字對象與服務器地址結構綁定在一起。如果函數調用成功,則返回true。否則,返回false。參數lpSockAddr指定將要綁定的服務器地址結構,參數nSockAddrLen表示地址結構的長度。
在服務器端,當地址信息綁定套接字成功后,還需要調用函數Listen()在指定端口監聽客戶端的鏈接請求。
1 BOOL Listen(int nConnectionBacklog = 5)
參數nConectionBacklog表示套接字監聽客戶端請求的最大數目。
1 CSocket sock; //創建套接字對象
2 sockaddr_in addr; //定義套接字地址結構變量
3 in_addr in_add; //定義IP地址結構變量
4 addr.sin_family = AF_INET //指定地址家族為TCP/IP
5 addr.sin_port = htons(80) //指定端口號
6 addr.sin_addr.S_un,S_addr = inet_addr("127.0.0.1") //將字符串IP轉換為網絡字節順序排列的IP
7
8 sock.Bind((SOCKADDR*)addr,sizeof(addr)) //綁定套接字與地址結構
9 sock.Listen(5); //監聽端口
3)連接服務器
客戶端創建套接字成功以后,可以調用函數Connect()向服務器發送連接請求。函數原型如下:
1 Bool Connect(const const SOCKADDR* lpSockAddr,int nSockAddrLen);
實例:
1 CSocket sock; //創建套接字對象
2 sockaddr_in addr; //定義套接字地址結構變量
3 in_addr in_add; //定義IP地址結構變量
4 addr.sin_family = AF_INET; //指定地址家族為TCP/IP
5 addr.sin_port = htons(80); //指定端口號
6 addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //將字符串IP轉換為網絡字節順序排列的IP
7
8 sock.Connect((SOCKADDR*)addr,sizeof(addr)) //連接服務器
4)數據交換
無論是服務端,還是客戶端都是通過函數Send()和Receive()進行數據交換。函數原型:
1 Virtual int Send(const void* lpBuf,int nBufLen,int nFlags = 0); 2 Virtual int Receive(void* lpBuf,int nBufLen,int nFlags = 0);
其中,函數send()用於發送指定緩沖區的數據,函數Recive()用於接收對方發送的數據,並將數據存放在指定緩沖區中。參數lpBuf表示數據緩沖區地址。參數nBufLenvia表示數據緩沖區的大小。參數nFlags表示數據發送或接收的標志,一般情況下,該參數均設置為0。例如,使用這兩個函數進行數據的發送和接收。
1 char buff[] = '123456'; 2 sock.Send(&buff,sizeof(buff),0) 3 sock.Receive(&buff,sizeof(buff),0) 4 Virtual int Receive(void* lpBuf,int nBufLen,int nFlags = 0); 5 Virtual int Receive(void* lpBuf,int nBufLen,int nFlags = 0);
5)關閉套接字對象
1 Virtual void close();sock.Close(); //關閉套接字對象套接字關閉的同時,也將服務器與客戶端之間連接關閉了。