线程同步与异步套接字编程(三)

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

我们知道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.

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

谢谢.

原文地址:https://www.cnblogs.com/lumao1122-Milolu/p/11820387.html