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