002之MFCSocket異步編程


當今的網絡程序通用體系結構大多為C/S模式,服務器監聽收到來自客戶端的請求,然后響應並作出應答。

界面對話框如下,輸入IP信息進行通信后再進行連接,連接成功即可開始通信。左側為客戶端,右側為服務端。

 

1、創建基於對話框的MFC項目,包含Windows套接字。在工程中創建基於CasyncSocket的類用於通信。

        客戶端只需要一個進行通信,服務器端需要兩個,一個用於監聽,一個用於通信(頭文件包含在h中與cpp中會有差別)。

1)客戶端:新建基於CAsyncSocket的類CClientSocket用於客戶端通信;並在CXXXDlg.h中聲明創建實例對象m_ClientSocket;

2)服務端:新建基於CAsyncSocket的類CServerSocket用於服務,類CListenSocket用於監聽;並在CXXXDlg.h中聲明創建實例對象。m_ListenSocket/m_ServerSocket。注意互相在頭文件中包含雙方的頭文件,便於后續使用。

 

2、在客戶端/服務器端界面中編輯完善相應的控件,並增加相應的變量

  

 

    

Tips:部分控件設置Control變量主要是用於實現按鈕在不同時期的可用性。

 

3、客戶端實現連接/斷開函數,服務器端實現監聽與斷開事件

1)客戶端:發起連接,調用Create函數創建套接字並執行connect建立連接:斷開時執行通信斷開即可,並在對話框中顯示通知。

 1     #include "ClientSocket.h"
 2 
 3 void Ccase003Dlg::OnBnClickedBnConnect()
 4 {
 5     // TODO: 在此添加控件通知處理程序代碼
 6     // TODO: 在此添加控件通知處理程序代碼
 7     BYTE nfield[4];
 8     CString strIP;
 9     int sPort;
10     UpdateData();    //默認為true,將輸入值傳入控件,否則將控件變量值輸出到文本框中
11 
12     if(ServerIP.IsBlank()||m_str_port.IsEmpty())//判斷輸入變量是否合法
13     {
14         AfxMessageBox("IP地址與端口不能為空");
15         return ;        //如不執行return 則會繼續執行賦IP操作
16     }
17 
18     //將IP傳給地址框
19     sPort=atoi(m_str_port);
20     ServerIP.GetAddress(nfield[0],nfield[1],nfield[2],nfield[3]);
21     strIP.Format("%d.%d.%d.%d",nfield[0],nfield[1],nfield[2],nfield[3]);
22 //初始化套接字創建socket句柄,默認有winsock自動選擇端口號,默認為流式套接字
23 //第三個參數用來指定感興趣的網絡事件掩碼位,默認全部包含,最后一個是指定套接字的網絡地址
24 //成功返回非零,可調用GetLastError獲取錯誤信息,客戶端不接收其他客戶連接,默認即可
25     m_clientsocket.Create();        
26 //    m_clientsocket.Connect(strIP,atoi(m_str_port));    //用於建立連接;第二種結構為sockaddr結構指針,用於winapi
27     m_clientsocket.Connect(strIP,sPort);
28 }
29 
30 void Ccase003Dlg::OnBnClickedBnDisconnect()
31 {
32     // TODO: 在此添加控件通知處理程序代碼
33 
34         m_clientsocket.Close();        //關閉套接字並釋放描述符
35         m_listbox.AddString("已斷開連接!");
36              m_listbox.SetTopIndex(m_listbox.GetCount()-1);
37 //權限控制的內容過於重復, 暫時剔除
38 }

 

2)服務器端: 監聽在獲取IP地址后,調用Create創建套接字,並偵聽連接請求。

 1 void Ccase004Dlg::OnBnClickedListen()
 2 {
 3     // TODO: 在此添加控件通知處理程序代碼
 4     BYTE nfield[4];
 5     CString strIP;
 6     int sPort;
 7     UpdateData();    //默認為true,將輸入值傳入控件,否則將控件變量值輸出到文本框中
 8 
 9     if(ServerIP.IsBlank())//判斷輸入變量是否合法
10     {
11         AfxMessageBox("IP地址與端口不能為空");
12         return ;        //如不執行return 則會繼續執行賦IP操作
13     }
14     
15     sPort = atoi(m_str_port);
16     //將IP傳給地址框
17     ServerIP.GetAddress(nfield[0],nfield[1],nfield[2],nfield[3]);
18     strIP.Format("%d.%d.%d.%d",nfield[0],nfield[1],nfield[2],nfield[3]);
19     //初始化套接字創建socket句柄,默認有winsock自動選擇端口號,默認為流式套接字,1
20     //第三個參數用來指定感興趣的網絡事件掩碼位,默認全部包含,最后一個是指定套接字的網絡地址
21     //成功返回非零,可調用GetLastError獲取錯誤信息,客戶端不接收其他客戶連接,默認即可
22     m_listensocket.Create(sPort,1,FD_ACCEPT,strIP);        
23 
24     m_listensocket.Listen(3);    //參數用於指定願意接受客戶端數目,必須調用
25     m_listbox.AddString("開始監聽");
26     m_listbox.AddString("地址"+strIP+"端口"+m_str_port);
27     m_listbox.AddString("等待客戶連接...");
28     m_listbox.SetTopIndex(m_listbox.GetCount()-1);    //設置為信息往下滾動
29 
30 }
31 
32 void Ccase004Dlg::OnBnClickedStopListen()
33 {
34     // TODO: 在此添加控件通知處理程序代碼
35     m_listensocket.Close();
36     m_listbox.AddString("停止監聽");
37     m_listbox.SetTopIndex(m_listbox.GetCount()-1);    //設置為信息往下滾動
38 }

 

3)完成其他對應的功能模塊:可以看出,除了監聽/連接設置了不同的函數,發送信息、接收信息、斷開連接、停止監聽、清空列表、重新輸入都是一樣的代碼。也就是說除了創建連接時有所差異,其余的代碼都可以通用。

 1 void Ccase003Dlg::OnBnClickedBnDisconnect()
 2 {
 3     // TODO: 在此添加控件通知處理程序代碼
 4     m_clientsocket.Close();
 5     m_listbox.AddString("已斷開連接");
 6     m_listbox.SetTopIndex(m_listbox.GetCount()-1);
 7 
 8 }
 9 
10 void Ccase003Dlg::OnBnClickedBnSend()
11 {
12     // TODO: 在此添加控件通知處理程序代碼
13     UpdateData();    //此處可加一個判定,如果輸入為空則提示並返回
14     m_clientsocket.Send(m_str_words,m_str_words.GetLength());
15     m_listbox.AddString("發送: "+m_str_words);    //在本端顯示發送的內容
16     m_listbox.SetTopIndex(m_listbox.GetCount()-1);    //設置為信息往下滾動
17     m_editbox.SetWindowText("");    //注意發送后清空輸入內容
18     m_editbox.SetFocus();    //發送后焦點指定在編輯欄
19 }
20 
21 void Ccase003Dlg::OnBnClickedBnRewrite()
22 {
23     // TODO: 在此添加控件通知處理程序代碼
24     m_editbox.SetWindowText("");
25     m_editbox.SetFocus();
26 }
27 
28 void Ccase003Dlg::OnBnClickedBnClear()
29 {
30     // TODO: 在此添加控件通知處理程序代碼
31     m_listbox.ResetContent();
32 }

 

4 、實現網絡事件響應函數

在執行相應按鈕操作后,系統會根據程序運行自動觸發響應。在socket實例對象中重寫相應的處理函數。客戶端系統發起連接觸發connect進行跟進,服務器端系統接收到connect請求觸發accept響應,此時建立起連接,通過receive接收程序發送的數據,最后close關閉釋放套接字。

1)客戶端:客戶端發起send在類中重寫函數OnConnect

 1 void CClientSocket::OnConnect(int nErrorCode)
 2 {
 3     // TODO: 在此添加專用代碼和/或調用基類
 4     Ccase003Dlg* plist=(Ccase003Dlg*)(AfxGetApp()->m_pMainWnd);
 5 
 6     if(nErrorCode)
 7     {
 8         AfxMessageBox("不能連接,請重試");
 9         plist->m_clientsocket.Close();    //如果不關閉套接字,則因為前一次的套接字還未釋放,故重按會失敗
10         return ;
11     }
12 
13     plist->m_listbox.AddString("連接成功!");
14     plist->m_listbox.SetTopIndex(plist->m_listbox.GetCount()-1);
15 /*
16     plist->m_edit_ip.EnableWindow(FALSE);
17     plist->m_edit_port.EnableWindow(FALSE);
18     plist->m_bn_connect.EnableWindow(FALSE);
19     plist->m_bn_disconnect.EnableWindow(TRUE);
20     plist->m_bn_clear.EnableWindow(TRUE);
21     plist->m_bn_send.EnableWindow(TRUE);
22     plist->m_bn_rewrite.EnableWindow(TRUE);
23     plist->m_editbox.EnableWindow(TRUE);
24 */
25     CAsyncSocket::OnConnect(nErrorCode);
26 }

 

2)服務器端:客戶端發起在ListenSocket類中重寫函數OnAccept。

 1 void CListenSocket::OnAccept(int nErrorCode)
 2 {
 3     // TODO: 在此添加專用代碼和/或調用基類
 4     Ccase004Dlg* plist=(Ccase004Dlg*)(AfxGetApp()->m_pMainWnd);
 5 
 6     Accept(plist->m_serversocket);        //接收連接請求,並取出第一個連接創建套接字用於通信,原始套接字依然保持打開並監聽
 7     plist->m_serversocket.AsyncSelect(FD_READ|FD_WRITE|FD_CLOSE);        //調用偵查
 8 
 9 
10     plist->m_listbox.AddString("連接成功!");
11     plist->m_listbox.SetTopIndex(plist->m_listbox.GetCount()-1);
12 /*       //如需設置權限,也可在此處獲取對話框指針后進行設置,增強程序的魯棒性
13     plist->m_edit_ip.EnableWindow(FALSE);
14     plist->m_edit_port.EnableWindow(FALSE);
15     plist->m_bn_listen.EnableWindow(FALSE);
16     plist->m_bn_stoplisten.EnableWindow(TRUE);
17     plist->m_bn_disconnect.EnableWindow(TRUE);
18     plist->m_bn_clear.EnableWindow(TRUE);
19     plist->m_bn_send.EnableWindow(TRUE);
20     plist->m_bn_rewrite.EnableWindow(TRUE);
21     plist->m_editbox.EnableWindow(TRUE);
22 */
23     CAsyncSocket::OnAccept(nErrorCode);
24 }

 

3)完成OnReceive(接收)和OnClose(關閉)函數,客戶端、監聽端與服務器端功能一樣。因需要接收信息,還需在ServerSocket類中重寫OnConnect(不貼出)。

 1 void CClientSocket::OnReceive(int nErrorCode)
 2 {
 3     // TODO: 在此添加專用代碼和/或調用基類
 4     char temp[200];
 5     int n=Receive(temp,200);
 6     temp[n]='\0';
 7     CString message;
 8     message.Format("收到: %s",temp);
 9     Ccase003Dlg* plist=(Ccase003Dlg*)(AfxGetApp()->m_pMainWnd);
10     plist->m_listbox.AddString(message);
11     plist->m_listbox.SetTopIndex(plist->m_listbox.GetCount()-1);
12 
13     CAsyncSocket::OnReceive(nErrorCode);
14 }
15 
16 void CClientSocket::OnClose(int nErrorCode)
17 {
18     // TODO: 在此添加專用代碼和/或調用基類
19     Ccase003Dlg* plist=(Ccase003Dlg*)(AfxGetApp()->m_pMainWnd);
20     plist->m_clientsocket.Close();
21     plist->m_listbox.AddString("關閉連接");
22     plist->m_listbox.SetTopIndex(plist->m_listbox.GetCount()-1);
23 
24 /*    plist->m_edit_ip.EnableWindow(TRUE);
25     plist->m_edit_port.EnableWindow(TRUE);
26     plist->m_bn_connect.EnableWindow(TRUE);
27     plist->m_bn_disconnect.EnableWindow(FALSE);
28     plist->m_bn_clear.EnableWindow(TRUE);
29     plist->m_bn_send.EnableWindow(FALSE);
30     plist->m_bn_rewrite.EnableWindow(FALSE);
31     plist->m_editbox.EnableWindow(FALSE);
32 */
33     CAsyncSocket::OnClose(nErrorCode);
34 }

 

4、大功告成。可以補充完善相應的優化控制,比如點擊連接之前不可以點擊斷開;連接之后斷開應釋放對應套接字,否則再點擊會崩潰。

 

 5、小結:會者不難,先了解原理,然后再碼代碼。

  1)對於控件控制,初始化窗口時可設置初始狀態(最好是全部初始化,這樣在函數響應時可以按流程思路進行調整,也可同時設置對話框標題);

       2)異步通信:服務器端打開監聽(Listen),觸發Accept接收請求,客戶端發出連接,觸發connect發送請求,至此可實現連接。使用發送Send,有信息觸發Receive接收顯示信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM