MFC之CAsyncSocket详解
CAsyncSocket類是從Object類派生而來。CAsyncSocket對象稱為異步套接字對象
使用CAsyncSocket進(jìn)行網(wǎng)絡(luò)編程,可以充分利用Windows操作系統(tǒng)提供的消息驅(qū)動機(jī)制,通過應(yīng)用程序框架來傳遞消息,方便地處理各種網(wǎng)絡(luò)事件。另一方面,作為MFC微軟基礎(chǔ)類庫中的一員,CAsyncSocket可以和MFC的其他類融為一體,大大擴(kuò)展了網(wǎng)絡(luò)編程的空間,方便了編程。
?
使用CAsyncSocket的一般步驟
網(wǎng)絡(luò)應(yīng)用程序一般采用客戶端/服務(wù)器模式,他們使用的CAsyncSocket編程有所不同,下面以表格的形式方式看一下服務(wù)器和客戶端之間的不同
?
| 序號 | 服務(wù)端 | 客戶端 |
| 1 | 構(gòu)造一個套接字 CAsyncSocket sockServer | 構(gòu)造一個套接字 CAsyncSocket sockClient |
| 2 | 創(chuàng)建SOCKET句柄,綁定到指定的端口 sockServer.Create(nPort); | 創(chuàng)建SOCKET句柄,使用默認(rèn)參數(shù) sockClient.Create(); |
| 3 | 啟動監(jiān)聽,時刻準(zhǔn)備接收連接請求 sockServer.Listen(); | ? |
| 4 | ? | 請求鏈接服務(wù)器 sockClient.Connect(strAddress,nPort) |
| 5 | 構(gòu)造一個新的空套接字 CAsyncSocket sockRecv; 接收連接 sockServer.Accept(sockRecv); | ? |
| 6 | 接收數(shù)據(jù) sockRecv.Receive(pBuffer,nLen); | 發(fā)送連接 sockClient.Send(pBuffer,nLen); |
| 7 | 發(fā)送數(shù)據(jù) sockRecv.Send(pBuffer,nLen); | 接收數(shù)據(jù) sockClient.Receive(pBuffer,nLen); |
| 8 | 關(guān)閉套接字對象 sockRecv.Close(); | 關(guān)閉套接字對象 sockClient.Close(); |
ps:客戶端與服務(wù)端都要首先構(gòu)造一個CAsyncSocket對象,然后使用該對象的Create成員函數(shù)來創(chuàng)建底層的SOCKET句柄。服務(wù)器端要綁定到特定的端口
?
對于服務(wù)器端的套接字對象,應(yīng)使用CAsyncSocket::Listen函數(shù)進(jìn)行監(jiān)聽狀態(tài),一旦收到來自客戶端的鏈接請求,就調(diào)用CAsyncSocket::Accept來接收。對于客戶端的套接字對象,應(yīng)當(dāng)使用CAsyncSocket::Connect來連接到一個服務(wù)器端的套接字對象。建立鏈接之后,雙方就可以按照應(yīng)用層協(xié)議交換數(shù)據(jù)了。
這里需要注意,Accept是將一個新的空CAsyncSocket對象作為它的參數(shù),在調(diào)用Accept之前必須構(gòu)造這個對象。與客戶端套接字的連接是通過它建立的,如果這個套接字對象退出,連接也就關(guān)閉。對于這個新的套接字對象,不需要調(diào)用Create來創(chuàng)建它的底層套接字
?
調(diào)用CAsyncSocket對象的其他成員函數(shù),如Send和Receive執(zhí)行與其他套接字對象的通信,這些成員函數(shù)與Windows Sockets API函數(shù)在形式和用法上基本是一致的。
?
關(guān)閉并銷毀CAsyncSocket對象。如果在堆棧上創(chuàng)建了套接字對象,當(dāng)包含此對象的函數(shù)退出時,會調(diào)用該類的析構(gòu)函數(shù),銷毀該對象。在銷毀該對象之前,析構(gòu)函數(shù)會調(diào)用該對象的Close成員函數(shù)。如果在堆上使用new創(chuàng)建了套接字對象,可先調(diào)用Close成員函數(shù)關(guān)閉它,在使用delete來刪除釋放該對象
?
在使用CAsyncSocket進(jìn)行網(wǎng)絡(luò)通信時,我們還需要處理以下幾個問題:
?
1.堵塞處理,CAsyncSocket對象專用于異步操作,不支持堵塞工作模式,如果應(yīng)用程序需要支持堵塞操作,必須自己解決
2.字節(jié)順序的轉(zhuǎn)換。在不同的結(jié)構(gòu)類型的計算機(jī)之間進(jìn)行數(shù)據(jù)傳輸時,可能會有計算機(jī)之間字節(jié)存儲順序不一致的情況。用戶程序需要自己對不用的字節(jié)順序進(jìn)行轉(zhuǎn)換
3.字符串轉(zhuǎn)換。同樣,不同結(jié)構(gòu)類型的計算機(jī)的字符串存儲順序也可能不同,需要自行轉(zhuǎn)換,如Unicode和ANSI字符串之間的轉(zhuǎn)換
?
創(chuàng)建CAsyncSocket對象
創(chuàng)建異步套接字對象一般是分為兩個步驟,首先要構(gòu)造CAsyncSocket對象,其次創(chuàng)建該對象底層的SOCKET句柄
?
1.創(chuàng)建空的CAsyncSocket對象
通過調(diào)用CAsyncSocket構(gòu)造函數(shù),創(chuàng)建一個新的空CAsyncSocket套接字對象,構(gòu)造函數(shù)還帶參數(shù)。套接字對象創(chuàng)建之后必須調(diào)用他的成員函數(shù)來創(chuàng)建底層的套接字?jǐn)?shù)據(jù)結(jié)構(gòu),并綁定他的地址
?
方法如下:
1.
C++
CAsyncSocket Sock
Sock.Create(...)
2.
C++
CAsyncSocket *pSock = new CAsyncSocket;
pSock->Create(...);
delete pSock;
pSock =NULL;
ps:之前見過很多朋友釋放指針的時候總是delete就完事了,往往不知正在給自己的程序帶來前所未有的災(zāi)難,而此時的指針我們稱之為“野指針”,注意:野指針不是NULL指針,而是不可用內(nèi)存的指針,即垃圾指針;野指針是很危險的,因為我們無法通過if去判斷指針是正常指針還是野指針,所以,我們在書寫代碼時一定要養(yǎng)成良好的編程習(xí)慣!!!避免野指針的方法我們可以通過以下幾點:
1.聲明指針一定要初始化,如果不初始化為NULL,那么此時一定要指向一塊合法的內(nèi)存
2.當(dāng)調(diào)用delete或者free去釋放指針后,一定要將指針重新指向NULL,可以參照SkinUI的SafeDelete
3.指針操作超出了變量的作用范圍,比如如下代碼,
C++
class A
{
void Fun(){}
};
class B
{
A *m_A;
B(){m_A=NULL;}
void Fun()
{
A a
m_A =&a;
//注意變量A的生命周期,當(dāng)該方法執(zhí)行完畢后,A會被釋放,此時m_A就變成了無效的野指針
}
void Fun1()
{
m_A->Fun(); //m_A為野指針,到這里也就出現(xiàn)了錯誤
}
};
通過上述方法可以大大的降低代碼出現(xiàn)野指針的風(fēng)險。
?
2.創(chuàng)建CAsyncSocket套接字的底層套接字句柄
通過CAsyncSocket::Create創(chuàng)建該對象的底層套接字句柄,決定套接字對象的具體特性。
函數(shù)原型如下:
C++
BOOL Create(
UINT nSocketPort = 0,
int nSocketType = SOCK_STREAM,
long lEvent = FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE,
LPCTSTR lpszSocketAddress = NULL
);
?
參數(shù):
nSocketPort,指定了一個分配給套接字的傳輸層端口號,默認(rèn)值為0,表示讓系統(tǒng)為這個套接字分配一個自由的端口號。但是對于服務(wù)器應(yīng)用程序而言,一般都需要事先分配一個公認(rèn)的端口號,所以切記,服務(wù)器應(yīng)用程序調(diào)用此函數(shù)時,必須分配一個端口號
nSocketType,套接字的類型,當(dāng)指定為SOCK_STREAM時表示生成流式套接字,若使用SOCK_DGRAM表示生成數(shù)據(jù)報套接字
lEvent,指定為CAsyncSocket對象生成通知消息的套接字事件,默認(rèn)對所有的套接字事件都生成通知消息
lpszSocketAddress,指定套接字的網(wǎng)絡(luò)地址,對Internet通信域來說,就是主機(jī)的域名或者ip地址,比如www.gymsaga.com或123.123.123.123。如果使用默認(rèn)值,表示使用默認(rèn)的本機(jī)ip地址
?
關(guān)于CAsyncSocket可以接受并處理的消息事件
在CAsyncSocket::Create中,參數(shù)lEvent指定了為CAsyncSocket對象生成通知消息的套接字事件,最能體現(xiàn)CAsyncSocket對Windows消息驅(qū)動機(jī)制的支持
先認(rèn)識一下這六種相關(guān)事件和通知消息
關(guān)于lEvent參數(shù)的符號常量,我們可以在WinSock中找到
C++
/*
* Define flags to be used with the WSAAsyncSelect() call.
*/
#define FD_READ 0x01
#define FD_WRITE 0x02
#define FD_OOB 0x04
#define FD_ACCEPT 0x08
#define FD_CONNECT 0x10
#define FD_CLOSE 0x20
它們代表了MFC套接字對象可以接收并處理的6種網(wǎng)絡(luò)事件,當(dāng)事件發(fā)生時,套接字對象會收到相應(yīng)的通知消息,并自動執(zhí)行套接字對象響應(yīng)的事件處理函數(shù)
1:FD_READ : ? 通知有數(shù)據(jù)可讀。當(dāng)一個套接字對象的數(shù)據(jù)輸入緩沖區(qū)收到其他套接字對象發(fā)送來的數(shù)據(jù)時,發(fā)生此事件,并通過該套接字對象 ,告訴它可以調(diào)用Receive成員來接收數(shù)據(jù)
2:FD_WRITE: ? 通知可以寫數(shù)據(jù),當(dāng)一個套接字對象的數(shù)據(jù)輸出緩沖區(qū)中的數(shù)據(jù)已經(jīng)發(fā)送出去,輸出緩沖區(qū)已騰空時,發(fā)生此事件,并通過該套接字對象,告訴它可以調(diào)用Send函數(shù)向外發(fā)送數(shù)據(jù)
3:FD_ACCEPT: ?通知監(jiān)聽套接字有連接請求可以接收。當(dāng)客戶端的鏈接請求到達(dá)服務(wù)器時,進(jìn)一步說,是當(dāng)客戶端的連接請求已經(jīng)進(jìn)入服務(wù)器監(jiān)聽套接字的接收緩沖區(qū)隊列時,發(fā)生此事件,并通過監(jiān)聽套接字對象,告訴它可以調(diào)用Accept成員來接收待決的鏈接請求。這個事件僅對流式套接字有效,并且發(fā)生在服務(wù)器端
4:FD_CONNECT: 通知請求鏈接的套接字,鏈接的要求已經(jīng)被處理。當(dāng)客戶端的連接請求已被處理時,發(fā)生此事件。存在兩種情況:一種是服務(wù)器端已接收了鏈接請求,雙方的連接已經(jīng)建立,通知客戶端套接字,可以使用鏈接來傳輸數(shù)據(jù)了;另一種情況是鏈接請求被拒絕,通知客戶機(jī)套接字,它所請求的連接失敗。這個事件僅對流式套接字有效,并且發(fā)生在客戶端
5:FD_CLOSE: ? 通知套接字已關(guān)閉。當(dāng)鏈接的套接字關(guān)閉時發(fā)生
6:FD_OOB: ? ? 通知將帶外數(shù)據(jù)到達(dá)。當(dāng)對方的流失套接字發(fā)送帶外數(shù)據(jù)時,發(fā)生此事件,并通知接收套接字,正在發(fā)送的套接字有帶外數(shù)據(jù)要求發(fā)送,帶外數(shù)據(jù)是有沒對鏈接的流失套接字相關(guān)的在邏輯上獨(dú)立的通道,帶外數(shù)據(jù)通道典型的是用來發(fā)送緊急數(shù)據(jù)。MFC支持帶外有數(shù)據(jù),使用CAsyncSocket類的高級用戶可能需要使用帶外數(shù)據(jù)通道,但不鼓勵使用CSocket類的用戶使用它,更容易的方法是創(chuàng)建第二個套接字來傳送這樣的數(shù)據(jù)
?
MFC框架對這六種事件的處理
當(dāng)上述的網(wǎng)絡(luò)事件發(fā)生時,MFC框架做何處理呢?MFC框架按照Windows系統(tǒng)的消息驅(qū)動把消息發(fā)送給相應(yīng)的套接字對象,并調(diào)用作為該對象函數(shù)的事件處理函數(shù),事件與處理函數(shù)一一映射。
?
在afxSock.h中我們可以找到CAsyncSocket類對這六種對應(yīng)事件的處理函數(shù)
C++
// Overridable callbacks
protected:
virtual void OnReceive(int nErrorCode);
virtual void OnSend(int nErrorCode);
virtual void OnOutOfBandData(int nErrorCode);
virtual void OnAccept(int nErrorCode);
virtual void OnConnect(int nErrorCode);
virtual void OnClose(int nErrorCode);
其中參數(shù)nErrorCode的值,是在函數(shù)被調(diào)用時,由MFC框架提供的,表明套接字最新的狀況,如果是0,說明成功,如果為非零值,說明套接字對象有某種錯誤
當(dāng)某個網(wǎng)絡(luò)事件發(fā)生時,MFC框架會自動調(diào)用套接字對象對應(yīng)的事件處理函數(shù)。這就相當(dāng)于給套接字對象一個通知,告訴它某個重要的事件已經(jīng)發(fā)生,所以也稱為套接字類的通知函數(shù)或者回調(diào)函數(shù)
?
3.重載套接字對象的回調(diào)函數(shù)
在編程中,一般我們不會直接去使用CAsyncSocket或者CSocket,而是從他們派生出自己的套接字類來。然后在派生類中對這些虛函數(shù)進(jìn)行重載處理,加入應(yīng)用程序?qū)τ诰W(wǎng)絡(luò)事件處理的特定代碼
如果是從CAsyncSocket類派生了自己的套接字類,就必須重載該應(yīng)用程序所感興趣的那些網(wǎng)絡(luò)事件所對應(yīng)的通知函數(shù)。如果從CSocket類派生一個類,是否重載所感興趣的通知函數(shù)則由自己決定。也可以使用CSocket類本身的回調(diào)函數(shù),但默認(rèn)情況下,CSocket本身的回調(diào)函數(shù)什么也不做,只是一個空函數(shù)。
MFC框架自動調(diào)用通知函數(shù),使得用戶可以在套接字被通知的時候來優(yōu)化套接字的行為。例如,用戶可以從自己的OnReceive通知函數(shù)中調(diào)用套接字對象的成員函數(shù)Receive,就是說,在被通知的時候,已經(jīng)有數(shù)據(jù)可讀了,才調(diào)用Receive來讀取它。這個方法不是必須的,但它是一個有效的方案。此外,也可以使用自己的通知函數(shù)跟蹤進(jìn)程,打印TRACE消息等
對于CSocket對象,還有如下一些不同之處
在一個諸如接收或者發(fā)送數(shù)據(jù)的操作期間,一個CSocket對象成為同步的,在同步狀態(tài)期間,在當(dāng)前套接字等待它想要的通知時,任何的為其他套接字的通知被排成隊列,一旦該套接字完成了它的同步操作,并再次成為異步的,其他的套接字才可以開始接收排列的通知
?
重要的一點是:在CSocket中,從來不調(diào)用OnConncet通知函數(shù),對于連接,簡單的調(diào)用Conncet函數(shù),僅當(dāng)連接完成時,無論成功還是失敗,該函數(shù)都返回,連接通知如何被處理是一個MFC內(nèi)部的實現(xiàn)細(xì)節(jié)。
總結(jié)
以上是生活随笔為你收集整理的MFC之CAsyncSocket详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 截胡骁龙8+首发!ROG游戏手机6官宣
- 下一篇: 吉利“放卫星”:一箭九星成功发射