隨著計算機網絡化的深入,計算機網絡編程在程序設計的過程中變得日益重要。由于C
++語言對底層操作的優越性,許多文章都曾經介紹過用VC ++進行Socket
編程的方法。但由于都是直接利用動態連接庫wsock32.dll
進行操作,實現起來比較煩瑣。其實,VC ++的MFC 類庫中提供了CAsyncSocket
這樣一個套接字類,用他來實現Socket 編程,是非常方便的。
本文將用一個Echo 例程來介紹CAsyncSocket 類的用法。
一、客戶端
1 .創建一個Dialog Based 項目
CSockClient。
2 .設計對話框
去掉Ok 和Cancle 兩個按鈕,增加ID_Connect (連接)、ID_Send(發送)、ID_Exit(關閉)按鈕,增加ListBox
控件IDC_LISTMSG 和Edit 控件IDC_EDITMSG,并按下表在ClassWizard 中為CCSockClientDlg
類添加變量。
3 .CAsyncSocket 類用DoCallBack 函數處理MFC 消息
當一個網絡事件發生時,DoCallBack 函數按網絡事件類型:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT
分別調用OnReceive、OnSend、OnAclearcase/" target="_blank" >ccept、OnConnect 函數。由于MFC
把這些事件處理函數定義為虛函數,所以要生成一個新的C
++類,以重載這些函數,做法如下:
以Public 方式繼承CAsyncSocket 類,生成新類MySock;
為MySock 類添加虛函數OnReceive、OnConnect、OnSend
4 .在MySock.ccp 中添加以下代碼
?。nclude "CSockClient.h"
?。nclude "CSockClientDlg.h"
5 .在MySock.h 中添加以下代碼
public:
BOOL m_bConnected; // 是否連接
UINT m_nLength;// 消息長度
char m_szBuffer[4096];// 消息緩沖區
6 .在MySock.ccp 中重載各函數
MySock::MySock()
{
m_nLength=0;
memset(m_szBuffer,0,sizeof(m_szBuffer));
m_bConnected=FALSE;
}
MySock:: ~MySock()
{
// 關閉套接字
if(m_hSocket!=INVALID_SOCKET)
Close();
}
void MySock::OnReceive(int nErrorCode)
{
m_nLength=Receive(m_szBuffer,sizeof(m_szBuffer),0);
// 下面兩行代碼用來獲取對話框指針
CCSockClientApp *pApp=(CCSockClientApp *)
AfxGetApp();
CCSockClientDlg *pDlg=(CCSockClientDlg *)
pApp ->m_pMainWnd;
pDlg ->m_MSGS.InsertString(0,m_szBuffer);
memset(m_szBuffer,0,sizeof(m_szBuffer));
CAsyncSocket::OnReceive(nErrorCode);
}
void MySock::OnSend(int nErrorCode)
{
Send(m_szBuffer,m_nLength,0);
m_nLength=0;
memset(m_szBuffer,0,sizeof(m_szBuffer));
// 繼續提請一個" 讀" 的網絡事件,
// 接收Server 消息
AsyncSelect(FD_READ);
CAsyncSocket::OnSend(nErrorCode);
}
void MySock::OnConnect(int nErrorCode)
{
if (nErrorCode==0)
{
m_bConnected=TRUE;
CCSockClientApp *pApp=(CCSockClientApp *)
AfxGetApp();
CCSockClientDlg *pDlg=(CCSockClientDlg *)
pApp ->m_pMainWnd;
memcpy(m_szBuffer,"Connected to ",13);
strncat(m_szBuffer,pDlg ->m_szServerAdr,sizeof
(pDlg ->m_szServerAdr));
pDlg ->m_MSGS.InsertString(0,m_szBuffer);
AsyncSelect(FD_READ);
// 提請一個" 讀" 的網絡事件,準備接收
}
CAsyncSocket::OnConnect(nErrorCode);
}
7 .新建對話框IDD_Addr
用來輸入IP 地址和Port;生成新類CAddrDlg。增加兩個Edit 控件:IDC_Addr、IDC_Port
按下表在ClassWizard 中為CAddrDlg 類添加變量。
8 .在CSockClientDlg.ccp 中添加代碼
?。nclude "AddrDlg.h"
protected:
int TryCount;
MySock m_clientSocket;
UINT m_szPort;
public:
char m_szServerAdr[256];
9 .雙擊IDD_CSOCKCLIENT_DIALOG 對話框中的" 連接"
按鈕,添加以下代碼
void CCSockClientDlg::OnConnect()
{
m_clientSocket.ShutDown(2);
m_clientSocket.m_hSocket=INVALID_SOCKET;
m_clientSocket.m_bConnected=FALSE;
CAddrDlg m_Dlg;
// 默認端口1088
m_Dlg.m_Port=1088;
if (m_Dlg.DoModal()==IDOK &&
!m_Dlg.m_Addr.IsEmpty())
{
memcpy(m_szServerAdr,m_Dlg.m_Addr,sizeof
(m_szServerAdr));
m_szPort=m_Dlg.m_Port;
// 建立計時器,每1 秒嘗試連接一次,
// 直到連上或TryCount>10
SetTimer(1,1000,NULL);
TryCount=0;
}
}
10 .添加Windows 消息WM_TIMER 響應函數OnTimer
void CCSockClientDlg::OnTimer(UINT nIDEvent)
{
if (m_clientSocket.m_hSocket==INVALID_
SOCKET)
{
BOOL bFlag=m_clientSocket.Create(0,SOCK_
STREAM,FD_CONNECT);
if(!bFlag)
{
AfxMessageBox("Socket Error!");
m_clientSocket.Close();
PostQuitMessage(0);
return;
}
}
m_clientSocket.Connect(m_szServerAdr,m_szPort);
TryCount ++;
if (TryCount>=10 || m_clientSocket.m_
bConnected)
{
KillTimer(1);
if (TryCount>=10)
AfxMessageBox("Connect Failed!");
return;
}
CDialog::OnTimer(nIDEvent);
}
11 .雙擊IDD_CSOCKCLIENT_DIALOG 對話框中的" 發送"
按鈕,添加以下代碼
void CCSockClientDlg::OnSend()
{
if (m_clientSocket.m_bConnected)
{
m_clientSocket.m_nLength=m_MSG.GetWindow ?
Text(m_clientSocket.m_szBuffer, sizeof
(m_clientSocket.m_szBuffer));
m_clientSocket.AsyncSelect(FD_WRITE);
m_MSG.SetWindowText("");
}
}
12 .雙擊IDD_CSOCKCLIENT_DIALOG 對話框中的" 關閉"
按鈕,添加以下代碼
void CCSockClientDlg::OnExit()
{
// 關閉Socket
m_clientSocket.ShutDown(2);
// 關閉對話框
EndDialog(0);
}
13 .運行此項目,連接時輸入主機名或IP 均可,CAsyncSocket
類會自動處理。
二、服務端
Server 端的編程與Client 端的類似,下面主要介紹其Listen 及Accept
函數
1 .建立一個CNewSocket 類
重載CAsyncSocket 類的OnReceive、OnSend
函數,如何進行信息的顯示和發送可以參考Client
程序。本例中采用將收到信息原封不動發回的方法來實現Echo
功能,代碼如下:
CNewSocket::OnReceive(int nErrorCOde)
{
m_nLength=Receive(m_szBuffer,sizeof
(m_szBuffer),0);
// 直接轉發消息
AsyncSelect(FD_WRITE);
}
CNewSocket::OnSend(int nErrorCode)
{
Send(m_szBuffer,m_nLength,0);
}
2 .建立一個CMyServerSocket 類
重載CAsyncSocket 類的OnAccept 函數。代碼如下:
在MyServerSocket.h 中聲明變量:
public::
CNewSocket * m_pSocket;
void CMyServerSocket::OnAccept(int nErrorCode)
{
// 偵聽到連接請求,調用Accept 函數
CNewSocket *pSocket = new CNewSocket();
if (Accept(*pSocket))
{
pSocket ->AsyncSelect(FD_READ);
m_pSocket=pSocket;
}
else
delete pSocket;
}
3 .為對話框添加一個" 偵聽" 按鈕添加如下代碼。
在CsockServerDlg.ccp 中聲明變量:
public:
CMyServerSocket m_srvrSocket;
void CCSockServerDlg::OnListen()
{
if (m_srvrSocket.m_hSocket==INVALID_SOCKET)
{
BOOL bFlag=m_srvrSocket.Create
(UserPort,SOCK_STREAM,FD_ACCEPT);
if (!bFlag)
{
AfxMessageBox("Socket Error !");
M_srvrSocket.Close();
PostQuitMessage(0);
Return;
}
}
//" 偵聽" 成功,等待連接請求
if (!m_srvrSocket。Listen(1))
{
int nErrorCode = m_srvrSocket.GetLastError();
if (nError !=WSAEWOULDBLOCK)
{
AfxMessageBox("Socket Error !");
M_srvrSocket.Close();
PostQuitMessage(0);
Return;
}
}
}
4 .目前程序只能實現Echo 功能
將信息原封不動的轉發,若能將Accept 中由CNewSocket *pSocket = new
CNewSocket();得到的Socket 指針存入一個CList 或一個數組中,便像Client
端那樣,對所有的連接進行讀寫控制。
三、總結
CAsyncSocket 類為我們使用Socket 提供了極大方便。建立Socket 的WSAStartup
過程和bind 過程被簡化成為Create 過程,IP 地址類型轉換、主機名和IP
地址轉換的過程中許多復雜的變量類型都被簡化成字符串和整數操作,特別是CAsyncSocket
類的異步特點,完全可以替代煩瑣的線程操作。MFC
提供了大量的類庫,我們若能靈活地使用它們,便會大大提高編程的效率?! ?/p>