zoukankan      html  css  js  c++  java
  • 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]='';
     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接收显示信息。

  • 相关阅读:
    java.net.BindException: Cannot assign requested address: bind
    Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'name': was expecting ('true', 'false' or 'null')
    springMVC上传错误StandardMultipartHttpServletRequest
    常用软件
    虚拟机修改静态ip
    linux下脚本做成服务
    Unable to resolve persistence unit root URL
    MyBatis对不同数据库的主键生成策略
    MyBatis定义复合主键
    MongoDB安装,启动,注册为windows系统服务
  • 原文地址:https://www.cnblogs.com/maxonzou/p/10573105.html
Copyright © 2011-2022 走看看