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接收显示信息。

优质内容筛选与推荐>>
1、设计模式的适用场景
2、最简单的springmvc
3、Asp.net多层架构中的变量引用与传递
4、Java内存溢出详解
5、深度了解Android 7.0 ,你准备好了吗?


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn