轉自:http://webbery.tianyablog.com
閱讀本文前,我們假設您已經:
1,知道如何創建一個單文檔的App Wizard
2,知道C++ 類、函數重載等簡單知識
3,知道如何給View類或者Doc文檔添加成員變量
4,會用MFC的IDE調試工具最好,那麼本文的程序您可以copy去調試
5,知道如何爲某個框架類添加虛函數或消息處理函數
網絡編程的內容和MFC關聯並不大,並不是MFC架構的主要內容,所以我記得比較簡略。
一、ISO/OSI七層參考模型
二、套接字(socket)的一些文字描述
套接字存在於通信區域中。通信區域也叫地址族,它是一個抽象的概念,主要用於將通過套接字通信的進程的共有特性綜合在一起。套接字通常只與同一區域的套
接字交換數據(也有可能跨區域通信,但這支在執行了某種轉換進程後才能實現)。Windows
Sockets只支持一個通信區域:網際域(AF_INET),這個域被使用網際協議簇通信的進程使用。
網絡字節順序不同的計算
機存放多字節值的順序不同,有的機器在起始地址存放低字節(低位先存),有的機器在起始地址存放高字節(高位先存)。基於Intel的CPU,即我們通常
使用的PC機採用的是低位先存。爲保證數據的正確性,在網絡協議中需要制定網絡字節順序。TCP/IP協議使用16位整數和32位整數的高位先存格式。
網間進程通信完全是異步的,相互通信的進程間既不存在父子關係,又不共享內存緩衝區,因此需要一種機制爲希望通信的進程間建立聯繫,爲二者的數據交換提供同步,這就是基於客戶機/服務器模式的TCP/IP。
套接字的類型
流式套接字(SOCK_STREAM)
提供面向連接,可靠的數據傳輸服務,數據無差錯,無重複的發送,且按發送順序接收
數據報套接字(SOCK_DGRAM)
提供無連接服務。數據報以獨立包形式發送,不提供錯誤保證,數據可能丟失或重複,並且接收順序混亂。基於UDP原始套接字(SOCK_RAW)。
基於TCP的socket編程服務器和客戶端進行通信都使用send/recv
基於UDP的socket編程服務器端爲接收端,客戶端爲發送端。發送數據爲sendto,接收數據爲recvfrom
三、一些結構定義和函數
(一) 、3個結構定義:
1, SOCKET socket (
int af, //指定地址族,對於TCP/IP只能是AF_INET(PF_INET)
int type, //SOCK_STREAM,SOCK_DGRAM
int protocol //推薦爲零,可自動選擇協議
);
2, struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
3, 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;
};
4, struct sockaddr {u_short sa_family;char sa_data[14];};
(二) 、4個函數定義
1, SOCKET accept (
SOCKET s,
struct sockaddr FAR* addr,
int FAR* addrlen //必須在傳入一個addrlen之前爲它賦初始值,否則調用失敗
); //int len=sizeof(SOCKADDR);
2, unsigned long inet_addr ( const char FAR * cp );//用來把IP地址轉化爲ULONG類型,用於IN_ADDR結構
3, char FAR * inet_ntoa ( struct in_addr in );//返回一個點分十進制地址值
4, int bind ( SOCKET s, const struct sockaddr FAR* name, int namelen);
在爲我們的網絡程序指定端口號時,我們要用1024以上的端口.
兩個類型轉換函數:
(1) htonl把一個u_long類型從主機字節序轉換爲網絡字節序
(2) htons把一個u_short類型從主機字節序轉換爲網絡字節序
四、TCP聊天程序服務器端程序
#include
#include
main(){
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 1, 1);
int 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_STREAM,0); //第三個參數爲零表示自動選擇協議
SOCKADDR_IN addrSrv; //定義一個地址結構體的變量
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);//htons把一個u_short類型從主機字節序轉換爲網絡字節序
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
listen(sockSrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1) {
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[100];
sprintf(sendBuf,"Welcome %s to here",inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
recv(sockConn,recvBuf,100,0);
printf("%s/n",recvBuf);
closesocket(sockConn);
}
}
要在控制檯使用套接字,需要加入頭文件 #include 和庫函數ws2_32.lib
要鏈接一個動態鏈接庫,我們要在VC++菜單欄中選擇Project--->Settings--->Link,在其中的Object/Library modules中先打入一個空格,再添加庫函數ws2_32.lib
五、TCP聊天程序客戶端程序
#include
#include
main(){
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 1, 1);
int 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_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr="127.0.0.1";
//本地迴路地址,不管本地主機上有沒有網卡,都可以測試網絡
TCP和UDP編程代碼大致相同,不同之處在於,TCP使用send/recv;UDP使用sendto/recvfrom;
sendto(sockClient,"Hello!",strlen("Hello!")+1,0,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
六、UDP的聊天程序服務器版:
#include
#include
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 1, 1);
int 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);
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 sendBuf[100],recvBuf[100],temp[200];
SOCKADDR_IN addrClent;
int len=sizeof(SOCKADDR);
while(1) {
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClent,&len);
if('q'==recvBuf[0]) {
sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClent,len);
printf("chat end!/n");
break;
}
sprintf(temp,"%s:%s",inet_ntoa(addrClent.sin_addr),recvBuf);
printf("%s/n",temp);
printf("please input data:/n");
gets(sendBuf);
sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClent,len);
}
closesocket(sockSrv);
WSACleanup();
}
七、UDP聊天程序客戶機版:
#include
#include
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);
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);
char sendBuf[100];
char recvBuf[100];
char temp[200];
int len=sizeof(SOCKADDR);
while(1)
{
printf("please input data/n");
gets(sendBuf);
sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrSrv,
len);
recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,
&len);
if('q'==recvBuf[0])
{
sendto(sockClient,"q",strlen("q")+1,0,(SOCKADDR*)&addrSrv,len);
printf("chat end!/n");
break;
}
sprintf(temp,"%s:%s",inet_ntoa(addrSrv.sin_addr),recvBuf);
printf("%s/n",temp);
}
closesocket(sockClient);
WSACleanup();
}
記着要加載庫函數ws2_32.lib
啓動順序應遵循先服務器後客戶機,否則容易出錯。
發送字符時應該多加一個空字符作爲結束字符。