VC 的網(wǎng)絡(luò)編程總結(jié)
使用VC 的網(wǎng)絡(luò)編程總結(jié)1.套接字編程原理1.1 Client/server通信模型1.2 Windows Sockets規(guī)范1.3 套接字1.3.1 套接字定義1.3.2分類(lèi)1.3.3 套接字的作
使用VC 的網(wǎng)絡(luò)編程總結(jié)
1.套接字編程原理
1.1 Client/server通信模型
1.2 Windows Sockets規(guī)范
1.3 套接字
1.3.1 套接字定義
1.3.2分類(lèi)
1.3.3 套接字的作用
1.3.4端口與地址
1.3.5 套接口屬性
2.基本的Windows Sockets API編程
2.1常用函數(shù)
2.2 TCP實(shí)例
2.3 UDP實(shí)例
2.4 Socket 通信阻塞的解決方法
3.MFC 下的Socket 編程的類(lèi)
3.1 CAsyncSocket類(lèi)
3.2 CSocket類(lèi)
3.3 Windows Sockets:帶存檔的套接字的工作方式
3.4 流式套接字通信的操作順序
3.5 使用 CAsyncSocket 類(lèi)
3.6 從套接字類(lèi)派生
3.7 套接字通知
3.8 一個(gè)使用CSocket 類(lèi)的網(wǎng)絡(luò)通信實(shí)例
3.8.1 服務(wù)器端應(yīng)用程序設(shè)計(jì)(ServerDemo)
3.8.2 客戶端應(yīng)用程序設(shè)計(jì)(項(xiàng)目名稱ClientDemo)
4.套接字的托管實(shí)現(xiàn)
4.1 System::Net ::Sockets 命名空間
4.2 實(shí)例:一個(gè)新郵件檢查器
1.套接字編程原理
一個(gè)完整的網(wǎng)間通信進(jìn)程需要由兩個(gè)進(jìn)程組成,并且只能用同一種高層協(xié)議。也就是說(shuō),不可能通信的一端用TCP ,而另一端用UDP 。一個(gè)完整的網(wǎng)絡(luò)信需要一個(gè)五元組來(lái)標(biāo)識(shí):協(xié)議、本地地址、本地端口號(hào)、遠(yuǎn)端地址、遠(yuǎn)端端口號(hào)。
1.1 Client/server通信模型
在客戶/服務(wù)器模式中我們將請(qǐng)求服務(wù)的一方稱為客戶(client ),將提供某種服務(wù)的一方稱為服務(wù)器(server )。
一個(gè)服務(wù)程序通常在一個(gè)眾所周知的地址監(jiān)聽(tīng)對(duì)服務(wù)的請(qǐng)求,也就是說(shuō)服務(wù)進(jìn)程一直處于休眠狀態(tài),直到一個(gè)客戶對(duì)這個(gè)服務(wù)的地址提出了連接請(qǐng)求。在這個(gè)時(shí)刻,服務(wù)程序被“驚醒”并且為客戶提供服務(wù)—對(duì)客戶的請(qǐng)求作出適當(dāng)?shù)姆磻?yīng)。雖然基于連接的服務(wù)是設(shè)計(jì)客戶機(jī)/服務(wù)器應(yīng)用程序時(shí)的標(biāo)準(zhǔn),但有些服務(wù)也是可以通過(guò)無(wú)連接的接口提供的。
客戶機(jī)/服務(wù)器的請(qǐng)求/響應(yīng)過(guò)程示意圖如下所示。
,圖1 客戶/服務(wù)器通信模型
通過(guò)上面的分析,我們不難理解一個(gè)一個(gè)完整的網(wǎng)絡(luò)應(yīng)用程序包括客戶端和服務(wù)器兩個(gè)部分。客戶與服務(wù)器進(jìn)程的作用是非對(duì)稱的,因此編碼不同。服務(wù)進(jìn)程一般是等待客戶請(qǐng)求而啟動(dòng)的,只要系統(tǒng)運(yùn)行,該服務(wù)進(jìn)程一直存在,直到終止或強(qiáng)迫終止。
1.2 Windows Sockets規(guī)范
Windows Sockets 規(guī)范是90年代初Microsoft 公司聯(lián)合其他幾家大公司共同制定的一套在Windows 下的二進(jìn)制兼容網(wǎng)絡(luò)編程接口規(guī)范。它以U.C.Berkeley 大學(xué)BSD UNIX中流行的Socket 接口為基礎(chǔ),主要在其上擴(kuò)充了一組針對(duì)Windows 的擴(kuò)展庫(kù)函數(shù),增加了符合Windows 消息驅(qū)動(dòng)特性的網(wǎng)絡(luò)事件異步選擇機(jī)制,以使程序員能夠充分利用Windows 消息驅(qū)動(dòng)機(jī)制進(jìn)行編程。
Windows Sockets 的用途是將基礎(chǔ)網(wǎng)絡(luò)抽象出來(lái),這樣,您不必對(duì)網(wǎng)絡(luò)非常了解,并且您的應(yīng)用程序可在任何支持套接字的網(wǎng)絡(luò)上運(yùn)行。它為應(yīng)用程序開(kāi)發(fā)者定義了一套簡(jiǎn)單統(tǒng)一的API ,并讓各家網(wǎng)絡(luò)軟件供應(yīng)商共同遵守。
Windows Sockets規(guī)范從90年代初的1.0版本開(kāi)始,經(jīng)過(guò)不斷的完善和發(fā)展,目前已經(jīng)有了Windows Sockets 2版本。值得注意的是,Microsoft 的MFC 庫(kù)現(xiàn)在只支持Windows Sockets 1版本,不支持Windows Sockets 2版本。
MFC 提供了兩個(gè)類(lèi)用以封裝Windows Sockets API 。一個(gè)是CAsyncSocket 類(lèi),它主要是提供給那些具有一定網(wǎng)絡(luò)編程經(jīng)驗(yàn),希望同時(shí)擁有Socket API編程的靈活性和類(lèi)庫(kù)編程便利性的開(kāi)發(fā)者的。另一個(gè)是CSocket 類(lèi),它由CAsyncSocket 類(lèi)派生,它具有更高的抽象化,致力于簡(jiǎn)化網(wǎng)絡(luò)編程所需的操作。
1.3 套接字
1.3.1 套接字定義
套接字是一個(gè)通信終結(jié)點(diǎn),它是Sockets 應(yīng)用程序用來(lái)在網(wǎng)絡(luò)上發(fā)送或接收數(shù)據(jù)包的對(duì)象。套接字具有類(lèi)型,與正在運(yùn)行的進(jìn)程相關(guān)聯(lián),并且可以有名稱。目前,套接字一般只與使用網(wǎng)際協(xié)議組的同一“通信域”中的其他套接字交換數(shù)據(jù)。使用套接字的應(yīng)用程序間通信模型如圖2所示。
圖2 套接字通信模型
1.3.2分類(lèi)
可用的套接字類(lèi)型有以下兩種:
1.3.2.1流式套接字 (stream )
流式套接字提供沒(méi)有記錄邊界的數(shù)據(jù)流,即字節(jié)流。字節(jié)流能確保以正確的順序無(wú)重
,復(fù)地被送達(dá)。
客戶機(jī)圖3 流式套接字(有連接通信) 編程
1.3.2.2 數(shù)據(jù)報(bào)套接字 (UDP )
數(shù)據(jù)報(bào)套接字支持面向記錄的數(shù)據(jù)流,但不能確保能被送達(dá),也無(wú)法確保按照發(fā)送順序或不重復(fù)。
服務(wù)器
創(chuàng)建并初始化套接字客戶機(jī)創(chuàng)建并初始化套接字
監(jiān)聽(tīng)來(lái)自客戶機(jī)的請(qǐng)求
向服務(wù)器發(fā)出請(qǐng)求
進(jìn)行處理
發(fā)送結(jié)果給客戶端
接收結(jié)果
關(guān)閉連接關(guān)閉連接
圖4 數(shù)據(jù)報(bào)套接字(無(wú)連接通信) 編程
“有序”指數(shù)據(jù)包按發(fā)送的順序送達(dá)?!安恢貜?fù)”指一個(gè)特定的數(shù)據(jù)包只能獲取一次。這兩種套接字都是雙向的,是可以同時(shí)在兩個(gè)方向上(全雙工)進(jìn)行通信的數(shù)據(jù)流。
注意 在某些網(wǎng)絡(luò)協(xié)議下(如 XNS ),流可以面向記錄,即作為記錄流而非字節(jié)流。但在更常用的 TCP/IP 協(xié)議下,流為字節(jié)流。Windows Sockets 提供與基礎(chǔ)協(xié)議無(wú)關(guān)的抽象化級(jí)別。
1.3.3 套接字的作用
套接字的作用非常大,至少在下面三種通信上下文中如此:
● 客戶端/服務(wù)器模型。
● 對(duì)等網(wǎng)絡(luò)方案,如聊天應(yīng)用程序。
● 通過(guò)讓接收應(yīng)用程序?qū)⑾⒔忉尀楹瘮?shù)調(diào)用來(lái)進(jìn)行遠(yuǎn)程過(guò)程調(diào)用 (RPC)。
Remote Procedures Call
1.3.4端口與地址
在網(wǎng)絡(luò)上,一個(gè)套接字的標(biāo)識(shí)主要借助于地址和端口來(lái)描述。
套接字的地址指該套接字所在計(jì)算機(jī)的網(wǎng)絡(luò)地址,可以為域名或IP 地址的形式。通常,創(chuàng)建套接字時(shí)不必指明網(wǎng)絡(luò)地址,只有在擁有多個(gè)網(wǎng)絡(luò)地址的機(jī)器時(shí),才需要顯式指定一個(gè)網(wǎng)絡(luò)地址。
同一機(jī)器上可以運(yùn)行多個(gè)網(wǎng)絡(luò)應(yīng)用程序,每個(gè)應(yīng)用程序都有自己的套接字用以進(jìn)行網(wǎng)絡(luò)通信,此時(shí)如果只有地址標(biāo)識(shí)套接字,則當(dāng)一個(gè)通信包到達(dá)機(jī)器時(shí),將無(wú)法確定究竟是哪
,個(gè)應(yīng)用程序的套接字需要接收此信息。由此增加了端口的概念,以協(xié)助區(qū)分同一機(jī)器上不同應(yīng)用程序的套接字。
端口用于標(biāo)識(shí)進(jìn)程,同一機(jī)器上不同的網(wǎng)絡(luò)應(yīng)用程序各有不同的端口,這樣,通過(guò)“網(wǎng)絡(luò)地址 端口號(hào)”的標(biāo)識(shí)方法,便唯一標(biāo)識(shí)了機(jī)器上的應(yīng)用程序了。
某些端口是專(zhuān)門(mén)為公共服務(wù)保留的(Ftp:21,http:80),除非程序是要提供這些服務(wù),否則應(yīng)盡量避免使用這些端口。一般來(lái)說(shuō),端口1024以前的端口號(hào)都是系統(tǒng)保留的或是作為公共服務(wù)的,應(yīng)盡量選擇大于1024的端口號(hào),以避免沖突。 1.3.5 套接口屬性
套接口有一系列的屬性用于標(biāo)識(shí)套接口的狀態(tài)等信息,它們的屬性如表1所示。
可以通過(guò)getsockopt()函數(shù)獲取套接口的屬性,也可以通過(guò)setsockopt()函數(shù)設(shè)置套接口的屬性。
2. 基本的Windows Sockets API編程
● 需要在程序中添加下面的包含語(yǔ)句:#include
在winsock 中,應(yīng)用程序通過(guò)sockaddr_in 結(jié)構(gòu)來(lái)指定IP 地址和服務(wù)端口信息 sockaddr_in internetAddr; int nPortID=5320;
internetAddr.sin_family=AF_INET;
internet.sin_addr.s_addr=inet_addr(“202.202.42.88”); //INADDR_ANY internet.sin_port=htons(nPortID);
ip 地址不容易記憶,還提供了許多地址和名稱解析函數(shù)如gethostbyname,gethostbyaddr 等。 2.1常用函數(shù)
1)WSAStartup 調(diào)用windows Socket DLL 函數(shù)原型 int WSAStartup(
WORD wVersionRequested, //應(yīng)用程序要求的sockets 版本
,LPWSADATA lpWSAData //指向數(shù)據(jù)結(jié)構(gòu)WSDATA 的指針, //得到windows Socket的具體信息
);
WSDA TA 定義如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
#ifdef _WIN64
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpV endorInfo;
char szDescription[WSADESCRIPTION_LEN 1]; char szSystemStatus[WSASYS_STATUS_LEN 1]; #else
char szDescription[WSADESCRIPTION_LEN 1]; char szSystemStatus[WSASYS_STATUS_LEN 1]; unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpV endorInfo;
#endif
} WSADATA, FAR * LPWSADATA;
2)WSACleanup 結(jié)束對(duì)Windows Sockets DLL的調(diào)用
函數(shù)原型:int WSACleanup(void);
3)socket 用于建立Sockets 。
函數(shù)原型:SOCKET socket(
int af, //地址族,一般是AF_INET
int type , //socket類(lèi)型,SOCK_STREAM或SOCK_DGRAM int protocol //協(xié)議類(lèi)型,通常取值 0
);
4)closesocket 關(guān)閉套接字
函數(shù)原型:int closesocket(
SOCKET s //要關(guān)閉的套接字
);
5)bind 將一個(gè)本地地址和一個(gè)SOCKET 描述字連接起來(lái)
函數(shù)原型:int bind(
SOCKET s, //要綁定的套接字
const struct sockaddr FAR* name, //指向SOCKADDR 結(jié)構(gòu)的地址 int namelen //地址結(jié)構(gòu)的sizeof
)
Tcp/ip SOCKADDR結(jié)構(gòu)
struct sockaddr{
unsigned short sa_family;
char sa_data[4];
};
,struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
6)listen 設(shè)定socket 為監(jiān)聽(tīng)狀態(tài)
函數(shù)原型:int listen(
SOCKET s, //進(jìn)行監(jiān)聽(tīng)的socket
int backlog //客戶端可以連接的請(qǐng)求個(gè)數(shù)
);
7)accept 接受一個(gè)socket 的連接請(qǐng)求,同時(shí)返回一個(gè)新的socket ,新的socket 用來(lái)在服務(wù)器與客戶端之間傳遞和接收信息。
函數(shù)原型:SOCKET accept(
SOCKET s, //處于監(jiān)聽(tīng)狀態(tài)的socket
struct sockaddr FAR* addr, //將要接受地址的sockaddr 指針
int FAR* addrlen //地址的長(zhǎng)度
);
8)connect 連接客戶端的socket 到指定的網(wǎng)絡(luò)服務(wù)器。連接成功后,客戶端用此socket 與服務(wù)器通信。
函數(shù)原型:int connect(
SOCKET s, //將要連接的socket
const struct sockaddr FAR* name, //目標(biāo)socket 地址
int namelen //地址結(jié)構(gòu)sizeof
);
9)recv 用于接收已經(jīng)建立連接的socket 數(shù)據(jù)信息
函數(shù)原型:int recv(
SOCKET s,
char FAR* buf, //接收數(shù)據(jù)緩沖區(qū)
int len ,//緩沖區(qū)長(zhǎng)度
int flags //有MSG_PEEK和 MSG_OOB
);
返回值:接收到的字節(jié)數(shù)
10)send 對(duì)已經(jīng)建立連接的socket 發(fā)送數(shù)據(jù)信息
函數(shù)原型:int send(
SOCKET s,
char FAR* buf, //發(fā)送數(shù)據(jù)緩沖區(qū)
int len ,//緩沖區(qū)長(zhǎng)度
int flags //有MSG_PEEK和 MSG_OOB
);
返回值:發(fā)送的字節(jié)數(shù)
11)WSAAsyncSelect 要求socket 在有事件發(fā)生時(shí)通知使用者,本函數(shù)將套接口設(shè)置成為非阻塞方式。
函數(shù)原型:int WSAAsyncSelect(
,SOCKET s,
HWND hWnd, //接收網(wǎng)絡(luò)事件的窗口句柄
unsigned int wMsg,//發(fā)送給窗口的網(wǎng)絡(luò)事件消息
long lEvent //網(wǎng)絡(luò)消息
);
12)sendto 向目標(biāo)地址發(fā)送數(shù)據(jù)信息
int sendto(
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr FAR * to,
int tolen
);
13)recvfrom 接收目標(biāo)地址傳來(lái)的數(shù)據(jù)信息
int recvfrom(
IN SOCKET s,
OUT char FAR * buf,
IN int len,
IN int flags,
OUT struct sockaddr FAR * from,
IN OUT int FAR * fromlen
);
2.2 TCP 實(shí)例
服務(wù)器端需要建立兩個(gè)套接字,一個(gè)用于監(jiān)聽(tīng)連接請(qǐng)求,另一個(gè)用來(lái)與請(qǐng)求連接的套接字建立連接,實(shí)際的數(shù)據(jù)傳送是通過(guò)后一個(gè)套接字。而客戶端只需要一個(gè)套接字即可。
窗體版TCP server(阻塞式)
在stdafx.h 文件中加入 #include
//啟動(dòng)TCP server 按鈕事件處理
void CTcpServerDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知處理程序代碼
if (WSAStartup(0x0101,&ws)!=0)
{
m_edit1="WSAStartup() failed!";
UpdateData(false );
return ;
}
//創(chuàng)建套接字
servsock=socket(AF_INET,SOCK_STREAM,0);
//填充服務(wù)器地址結(jié)構(gòu)
servport=5555;
memset(&sa,0,sizeof (sa));
sa.sin_family=AF_INET;
sa.sin_port=htons(servport);
sa.sin_addr.s_addr=inet_addr("202.202.42.88");
//綁定套接字到服務(wù)器地址結(jié)構(gòu)
err=bind(servsock,(const sockaddr *)&sa,sizeof (sa)); if (err!=0)
{
m_edit1="Bind failed!";
UpdateData(false );
//監(jiān)聽(tīng)套接字
err=listen(servsock,5);
if (err!=0)
{
m_edit1="Listen failed!";
UpdateData(false ); return ; }
return ;
}
m_edit1.SetString("Waiting request...");
UpdateData(false );
this ->RedrawWindow ();//如不調(diào)用此句,則在阻塞Socket 方式下窗體無(wú)法正常刷新 //等待連接請(qǐng)求
len=sizeof (cliaddr);
clisock=accept(servsock,(struct sockaddr *)&cliaddr,&len);
m_edit1.Format("Accept
Client:s:dn",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
}
//發(fā)送消息事件處理
void CTcpServerDlg::OnBnClickedButton2()
{
//發(fā)送歡迎詞
send(clisock,buff,strlen(buff),0);
}
//關(guān)閉連接事件處理
void CTcpServerDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知處理程序代碼 //關(guān)閉連接 m_edit1="Connection Closed!"; UpdateData(false ); // TODO: 在此添加控件通知處理程序代碼 sprintf(buff,"Welcome you s",inet_ntoa(cliaddr.sin_addr)); UpdateData(false );
closesocket(clisock);
closesocket(servsock);
WSACleanup();
}