zoukankan      html  css  js  c++  java
  • 线程同步与异步套接字编程(三)

    今天讲解一下基于消息的异步套接字编程的情况。

    我们知道windows套接字有两种模式执行I/O操作:阻塞和非阻塞模式:

    • 阻塞模式,会阻塞程序运行,从而导致调用线程暂停运行;
    • 非阻塞模式,winsock函数无论如何都会立即返回,在该函数执行的操作完成之后,系统会采用某种方式将操作结果通知给调用线程,后者根据通知消息判断操作执行度。

    windows socket的异步选择函数WSAAsyncSelect 提供了消息机制的网络事件选择,当通过它登记网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与该事件相关的一些信息。该函数原型:

    1 WSAAsyncSelect(
    2     SOCKET s,
    3     HWND hWnd,
    4     u_int wMsg,
    5     long lEvent
    6     );
    1. 套接字描述符;
    2. 事件发生时接收消息的窗口句柄;
    3. 网络事件发生时窗口将接收到的消息;
    4. 网络事件,其定义如下:
    5.  1 /*
       2  * WinSock 2 extension -- bit values and indices for FD_XXX network events
       3  */
       4 #define FD_READ_BIT      0
       5 #define FD_READ          (1 << FD_READ_BIT)
       6 
       7 #define FD_WRITE_BIT     1
       8 #define FD_WRITE         (1 << FD_WRITE_BIT)
       9 
      10 #define FD_OOB_BIT       2
      11 #define FD_OOB           (1 << FD_OOB_BIT)
      12 
      13 #define FD_ACCEPT_BIT    3
      14 #define FD_ACCEPT        (1 << FD_ACCEPT_BIT)
      15 
      16 #define FD_CONNECT_BIT   4
      17 #define FD_CONNECT       (1 << FD_CONNECT_BIT)
      18 
      19 #define FD_CLOSE_BIT     5
      20 #define FD_CLOSE         (1 << FD_CLOSE_BIT)
      21 
      22 #define FD_QOS_BIT       6
      23 #define FD_QOS           (1 << FD_QOS_BIT)
      24 
      25 #define FD_GROUP_QOS_BIT 7
      26 #define FD_GROUP_QOS     (1 << FD_GROUP_QOS_BIT)
      27 
      28 #define FD_ROUTING_INTERFACE_CHANGE_BIT 8
      29 #define FD_ROUTING_INTERFACE_CHANGE     (1 << FD_ROUTING_INTERFACE_CHANGE_BIT)
      30 
      31 #define FD_ADDRESS_LIST_CHANGE_BIT 9
      32 #define FD_ADDRESS_LIST_CHANGE     (1 << FD_ADDRESS_LIST_CHANGE_BIT)
      33 
      34 #define FD_MAX_EVENTS    10
      35 #define FD_ALL_EVENTS    ((1 << FD_MAX_EVENTS) - 1)
    6. WSSocket函数是winsock库中创建套接字扩展函数,和socket函数一样;
    7. WSARecvFrom函数,也是同上扩展函数。值得一提的是参数lpBuffer,是一个指向WSABUF结构体素质指针,该结构体包含两个成员变量,buffer长度变量和指向buffer的指针变量;
    8. SWASendTo函数,意义同上,也是扩展函数.
    • 接下来,我们开始创建对话框,并添加相应的控件:

    • 同样的,因为要用到socket,所以就要在程序运行时,先调用套接字库,在APP类成员函数初始化实例前,加载套接字库:
       1 BOOL CChat_ws2App::InitInstance()
       2 {
       3     //load winsock2 libary
       4     WORD wVersionRequested;
       5     WSADATA wsData;
       6     int err;
       7     wVersionRequested=MAKEWORD(2,2);
       8     err = WSAStartup(wVersionRequested,&wsData);
       9     if(err!=0)
      10     {
      11         MessageBox(NULL,"error: wsa start failed.","ERROR",NULL);
      12         return FALSE;
      13     }
      14     if(LOBYTE(wsData.wVersion)!=2 ||HIBYTE(wsData.wVersion)!=2)
      15     {
      16         MessageBox(NULL,"error: version incorrect.","ERROR",NULL);
      17         WSACleanup();
      18         return FALSE;
      19     }
      20     AfxEnableControlContainer();
      21 
      22     // Standard initialization
      23     // If you are not using these features and wish to reduce the size
      24     //  of your final executable, you should remove from the following
      25     //  the specific initialization routines you do not need.
      26 
      27 #ifdef _AFXDLL
      28     Enable3dControls();            // Call this when using MFC in a shared DLL
      29 #else
      30     Enable3dControlsStatic();    // Call this when linking to MFC statically
      31 #endif
      32 
      33     CChat_ws2Dlg dlg;
      34     m_pMainWnd = &dlg;
      35     int nResponse = dlg.DoModal();
      36     if (nResponse == IDOK)
      37     {
      38         // TODO: Place code here to handle when the dialog is
      39         //  dismissed with OK
      40     }
      41     else if (nResponse == IDCANCEL)
      42     {
      43         // TODO: Place code here to handle when the dialog is
      44         //  dismissed with Cancel
      45     }
      46 
      47     // Since the dialog has been closed, return FALSE so that we exit the
      48     //  application, rather than start the application's message pump.
      49     return FALSE;
      50 }

      如果加载失败,直接返回false,结束程序。这里我们需要包含我们用到的socket库头文件 #include<winsock2.h> 注意,我们是用winsock2.2版本,所以别忘了将 ws2_32.lib 添加到工程里。

    • 创建,并初始化套接字,这里我们同样把它们封装到dialog类里
      1 class CChat_ws2Dlg : public CDialog
      2 {
      3 private: SOCKET m_sock;
      4          BOOL InitSocket();
      5 ...
      6 }
    • 初始化套接字
       1 // Initialize socket
       2 BOOL CChat_ws2Dlg::InitSocket()
       3 {
       4     m_sock = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
       5     if(m_sock==INVALID_SOCKET)
       6     {
       7         MessageBox("error: failed to create socket.");
       8         return FALSE;
       9     }
      10     sockaddr_in addr;
      11     addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
      12     addr.sin_family=AF_INET;
      13     addr.sin_port=htons(6000);
      14     if(SOCKET_ERROR==bind(m_sock,(sockaddr*)&addr,sizeof(sockaddr)))
      15     {
      16         MessageBox("error: failed binding.");
      17         return FALSE;
      18     }
      19     //WSAAsyncSelect->require windows socket dll to send
      20     //a message to winHandle.(it will also config socket as Non Blonking Mode.)
      21     if(SOCKET_ERROR == WSAAsyncSelect(
      22         m_sock,        //Notified socket
      23         m_hWnd,        //window handle
      24         UM_SOCK,    //(User define)The message to be sent is indicated by the UM_SOCK parameter
      25         FD_READ        //Read Event
      26         ))
      27     {
      28         MessageBox("WSAAsyncSelect failed.");
      29         return FALSE;
      30     }
      31     return TRUE;
      32 }

      在初始化过程中,我们同时通过windows sockets的异步选择函数选择了相应的消息机制网络选择;

    • 接着我们定义一下自定义的消息 UM_SOCK ,在dialog库文件定义如下:
      #define UM_SOCK WM_USER+1

      将初始化函数在对话框初始化时执行:

       1 /////////////////////////////////////////////////////////////////////////////
       2 // CChat_ws2Dlg message handlers
       3 
       4 BOOL CChat_ws2Dlg::OnInitDialog()
       5 {
       6 .....
       7     // TODO: Add extra initialization here
       8     InitSocket();
       9 .....
      10 }
    • 接着为消息机制定义消息事件映像进程函数(接收函数)
      1 BEGIN_MESSAGE_MAP(CChat_ws2Dlg, CDialog)
      2     //{{AFX_MSG_MAP(CChat_ws2Dlg)
      3     ON_WM_SYSCOMMAND()
      4     ON_WM_PAINT()
      5     ON_WM_QUERYDRAGICON()
      6     ON_BN_CLICKED(IDC_BUTTON1, OnBtnSend)
      7     //}}AFX_MSG_MAP
      8     ON_MESSAGE(UM_SOCK,OnSock)//Message Map
      9 END_MESSAGE_MAP()

      其中 Onsock 为消息事件映像进程函数。因为在注册的事件发生后,操作系统向调用进程发送相应的消息时,还会将该事件相应的信息一起传递给调用进程,这些信息是通过消息的两个参数传递的。所以我们定义的 UM_SOCK 消息响应函数时应带有 wParam 和 lParam 这两个参数。

    • 申明消息事件映像响应函数
       1 /////////////////////////////////////////////////////////////////////////////
       2 // CChat_ws2Dlg dialog
       3 
       4 class CChat_ws2Dlg : public CDialog
       5 {
       6 ...
       7 // Implementation
       8 protected:
       9     HICON m_hIcon;
      10 
      11     // Generated message map functions
      12     //{{AFX_MSG(CChat_ws2Dlg)
      13     virtual BOOL OnInitDialog();
      14     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
      15     afx_msg void OnPaint();
      16     afx_msg HCURSOR OnQueryDragIcon();
      17     afx_msg void OnBtnSend();
      18     //}}AFX_MSG
      19     afx_msg void OnSock(WPARAM,LPARAM);//UM_SOCK message response function
      20     DECLARE_MESSAGE_MAP()
      21 };

      实现响应函数:

       1 /// Implementation of message response function
       2 void CChat_ws2Dlg::OnSock(WPARAM wParam,LPARAM lParam)
       3 {
       4     switch(LOWORD(lParam))
       5     {
       6     case FD_READ:
       7         {
       8             WSABUF wsabuf;
       9             wsabuf.buf=new char[200];
      10             wsabuf.len=200;
      11             DWORD dwRead;
      12             DWORD dwFlag=0;
      13             sockaddr_in addrFrom;
      14             int len=sizeof(sockaddr);
      15             CString str;
      16             CString strTemp;
      17             if(SOCKET_ERROR == WSARecvFrom(m_sock,&wsabuf,1,&dwRead,&dwFlag,
      18                 (sockaddr*)&addrFrom,&len,NULL,NULL))
      19             {
      20                 MessageBox("error: failed receive data.");
      21                 delete[] wsabuf.buf;
      22                 return;
      23             }
      24             str.Format("%s: %s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
      25             str+="
      ";
      26             GetDlgItemText(IDC_EDIT1,strTemp);
      27             str+=strTemp;
      28             SetDlgItemText(IDC_EDIT1,str);
      29             delete[] wsabuf.buf;
      30             break;
      31         }
      32     default:
      33         {
      34             break;
      35         }
      36     }
      37 }
    • 紧接着,我们实现发送函数---按键消息事件实现发送数据:
       1 void CChat_ws2Dlg::OnBtnSend() 
       2 {
       3     // TODO: Add your control notification handler code here
       4     DWORD dwIP;
       5     CString strSend;
       6     WSABUF wsabuf;
       7     DWORD dwSend;
       8     int len;
       9     sockaddr_in addrTo;
      10     //get the server ip address from IDC_IPADDRESS1
      11     ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
      12     addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
      13     addrTo.sin_family=AF_INET;
      14     addrTo.sin_port=htons(6000);
      15     //get data which need to be sent
      16     GetDlgItemText(IDC_EDIT2,strSend);
      17     len=strSend.GetLength();
      18     //write in to wsabuf
      19     wsabuf.buf=strSend.GetBuffer(len);
      20     wsabuf.len=len+1;
      21     //send data to socket
      22     if(SOCKET_ERROR == WSASendTo(m_sock,&wsabuf,1,&dwSend,0,
      23         (sockaddr*)&addrTo,sizeof(sockaddr),NULL,NULL))
      24     {
      25         MessageBox("error: transmit failed.");
      26         return;
      27     }
      28 }
    • 最后,我们需要在退出程序时,释放所占用的资源,别在app类和dialog类中创建析构函数,并在析构函数中释放socket所占用的资源:
       1 CChat_ws2App::~CChat_ws2App()
       2 {
       3     // TODO: add construction code here,
       4     // Place all significant initialization in InitInstance
       5     WSACleanup();
       6 }
       7 
       8 CChat_ws2Dlg::~CChat_ws2Dlg()
       9 {
      10     // TODO: add construction code here,
      11     // Place all significant initialization in InitInstance
      12     if(m_sock)
      13     {
      14         closesocket(m_sock);
      15     }
      16 }

    编译,运行:

    End.

    至此,线程同步异步套接字编程,我们就算讲解完了,希望对大家有帮助。

    谢谢.

  • 相关阅读:
    08.Linux系统-Fastdfs分布式文件系统-互为主从配置搭建部署
    07.Linux系统-GitLab版本控制服务安装部署
    06.Linux系统-WCP知识共享平台安装部署(旗舰版)
    01.Linux-CentOS系统清理缓存脚本
    15.Linux-CentOS系统重启网卡ping不通问题(云环境)
    14.Linux-CentOS系统proc文件系统丢失
    设置环境变量遇到的难题,cmd管理员方式与普通方式的区别,通过C#代码设置环境变量
    DataGridView 行数据验证:当输入数据无效时不出现红色感叹号的Bug
    VS2017新建项目的模板之配置
    禅道安装
  • 原文地址:https://www.cnblogs.com/lumao1122-Milolu/p/11820387.html
Copyright © 2011-2022 走看看