• 多线程(三)


    对于多线程,我们已经有了综合理解。下面我们就其应用,编写一个简易的实用应用程序----网络聊天室程序的实现;

    我们知道聊天室基本功能包含两个,一个是显示接收到的message,另一个就是发送自己的message。这里我们就依据之前掌握的socket来负责网络通信,采用UDP协议。

    1. 创建聊天室UI对话框
    2. 因为我们要用到socket,所以在使用socket之前,需要调用套接字库。为了确保,程序必须要加载套接字库才可正常运行,所以我们需要在CWinApp类重载的InitInstance函数实例中调用socket库加载函数(AfxSocketInit):
       1 /////////////////////////////////////////////////////////////////////////////
       2 // CChatApp initialization
       3 
       4 BOOL CChatApp::InitInstance()
       5 {
       6     //load sock libary
       7     if(!AfxSocketInit())
       8     {
       9         AfxMessageBox("Failed loading socket lib.");
      10         return FALSE;
      11     }
      12     AfxEnableControlContainer();
      13 
      14     // Standard initialization
      15     // If you are not using these features and wish to reduce the size
      16     //  of your final executable, you should remove from the following
      17     //  the specific initialization routines you do not need.
      18 
      19 #ifdef _AFXDLL
      20     Enable3dControls();            // Call this when using MFC in a shared DLL
      21 #else
      22     Enable3dControlsStatic();    // Call this when linking to MFC statically
      23 #endif
      24 
      25     CChatDlg dlg;
      26     m_pMainWnd = &dlg;
      27     int nResponse = dlg.DoModal();
      28     if (nResponse == IDOK)
      29     {
      30         // TODO: Place code here to handle when the dialog is
      31         //  dismissed with OK
      32     }
      33     else if (nResponse == IDCANCEL)
      34     {
      35         // TODO: Place code here to handle when the dialog is
      36         //  dismissed with Cancel
      37     }
      38 
      39     // Since the dialog has been closed, return FALSE so that we exit the
      40     //  application, rather than start the application's message pump.
      41     return FALSE;
      42 }

      当然了,因为我们调用了AfxSocketInit函数,所以我们需要包含Afxsock.h头文件:在StdAfx.h添加Afxsock.h头文件

      // stdafx.h : include file for standard system include files,
      //  or project specific include files that are used frequently, but
      //      are changed infrequently
      //
      
      #include <Afxsock.h>
      
      #if !defined(AFX_STDAFX_H__1DDC28DA_20A6_423E_AC43_5FD4D419DB47__INCLUDED_)
      #define AFX_STDAFX_H__1DDC28DA_20A6_423E_AC43_5FD4D419DB47__INCLUDED_
      
      #if _MSC_VER > 1000
      #pragma once
      #endif // _MSC_VER > 1000
    3. 接下来,我们创建套接字socket,在CChatDlg类中定义socket成员变量和socket初始化成员函数
      1 class CChatDlg : public CDialog
      2 {
      3 private:
      4     SOCKET m_sock;
      5     BOOL InitSocket();
      6         ....
      7 } 
    4. 编写socket初始化函数定义:
       1 BOOL CChatDlg::InitSocket()
       2 {
       3     //Create sock-- upd protocol.
       4     m_sock=socket(AF_INET,SOCK_DGRAM,0);
       5     if(INVALID_SOCKET==m_sock)
       6     {
       7         MessageBox("Failed create socket.");
       8         return FALSE;
       9     }
      10     //setup address parameters
      11     sockaddr_in addr;
      12     addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
      13     addr.sin_family=AF_INET;
      14     addr.sin_port=htons(6000);
      15     int result;
      16     result=bind(m_sock,(sockaddr*)&addr,sizeof(sockaddr));
      17     if(SOCKET_ERROR==result)
      18     {
      19         closesocket(m_sock);
      20         MessageBox("Failed bind.");
      21         return FALSE;
      22     }
      23     return TRUE;
      24 }

      端口号6000,IP地址,接收来自任意地址的message,协议udp.

    5. 该初始化函数在CChatDlg类成员函数OnInitDialog中调用即可
       1 /////////////////////////////////////////////////////////////////////////////
       2 // CChatDlg message handlers
       3 
       4 BOOL CChatDlg::OnInitDialog()
       5 {
       6     CDialog::OnInitDialog();
       7 
       8     // Add "About..." menu item to system menu.
       9 
      10     // IDM_ABOUTBOX must be in the system command range.
      11     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
      12     ASSERT(IDM_ABOUTBOX < 0xF000);
      13 
      14     CMenu* pSysMenu = GetSystemMenu(FALSE);
      15     if (pSysMenu != NULL)
      16     {
      17         CString strAboutMenu;
      18         strAboutMenu.LoadString(IDS_ABOUTBOX);
      19         if (!strAboutMenu.IsEmpty())
      20         {
      21             pSysMenu->AppendMenu(MF_SEPARATOR);
      22             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
      23         }
      24     }
      25 
      26     // Set the icon for this dialog.  The framework does this automatically
      27     //  when the application's main window is not a dialog
      28     SetIcon(m_hIcon, TRUE);            // Set big icon
      29     SetIcon(m_hIcon, FALSE);        // Set small icon
      30     
      31     // TODO: Add extra initialization here
      32     InitSocket();
      33        ......
      34 }
    6. 接下来我们编写实现接收功能的程序。我们知道基于upd接收函数recvfrom,当一直无message接收时,会阻塞,导致程序停止运行。因此我们就非常有必要将接收数据的操作放置在一个单独的线程中完成,并给这个线程传递两个参数,m_sock和对话框控件句柄m_hWnd;这样,在该线程中,当接收到数据后,可以将其传回给对话框,并显示出来。首先我们将这两个参数定义成一个结构体。在CChatDlg.h文件中定义如下:
       1 struct RECVPARAM
       2 {
       3     SOCKET sock;//created socket
       4     HWND hwnd;    //dialog handle
       5 };
       6 
       7 class CChatDlg : public CDialog
       8 {
       9 private:
      10     SOCKET m_sock;
      11     BOOL InitSocket();
      12 ......
      13 }
    7. 接下来创建线程,在CChatDlg类成员函数OnInitDialog添加线程创建申明:
      BOOL CChatDlg::OnInitDialog()
      {
          CDialog::OnInitDialog();
      
          // Add "About..." menu item to system menu.
      
          // IDM_ABOUTBOX must be in the system command range.
          ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
          ASSERT(IDM_ABOUTBOX < 0xF000);
      
          CMenu* pSysMenu = GetSystemMenu(FALSE);
          if (pSysMenu != NULL)
          {
              CString strAboutMenu;
              strAboutMenu.LoadString(IDS_ABOUTBOX);
              if (!strAboutMenu.IsEmpty())
              {
                  pSysMenu->AppendMenu(MF_SEPARATOR);
                  pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
              }
          }
      
          // Set the icon for this dialog.  The framework does this automatically
          //  when the application's main window is not a dialog
          SetIcon(m_hIcon, TRUE);            // Set big icon
          SetIcon(m_hIcon, FALSE);        // Set small icon
          
          // TODO: Add extra initialization here
          InitSocket();
          RECVPARAM *pRecvParam=new RECVPARAM;
          pRecvParam->sock=m_sock;
          pRecvParam->hwnd=m_hWnd;
          //create thread
          HANDLE hThread=CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);
          CloseHandle(hThread);
          
          return TRUE;  // return TRUE  unless you set the focus to a control
      }

      先创建结构体对象,然后将该对象赋值,最后将该结构体对象以LPVOID类型参数传入线程RecvProc函数中。

    8. 下面我们需要对这个线程函数进行编写接收来自网络message。这里为了方便管理,我们将线程入口函数封装到CChatDlg类中,使之为该类的成员函数:
       1 struct RECVPARAM
       2 {
       3     SOCKET sock;//created socket
       4     HWND hwnd;    //dialog handle
       5 };
       6 
       7 class CChatDlg : public CDialog
       8 {
       9 private:
      10     SOCKET m_sock;
      11     BOOL InitSocket();
      12     static DWORD WINAPI RecvProc(LPVOID lpParameter);
      13 // Construction
      14 public:
      15     CChatDlg(CWnd* pParent = NULL);    // standard constructor
      16 
      17 // Dialog Data
      18     //{{AFX_DATA(CChatDlg)
      19     enum { IDD = IDD_CHAT_DIALOG };
      20         // NOTE: the ClassWizard will add data members here
      21     //}}AFX_DATA
      22 
      23     // ClassWizard generated virtual function overrides
      24     //{{AFX_VIRTUAL(CChatDlg)
      25     protected:
      26     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
      27     //}}AFX_VIRTUAL
      28 
      29 // Implementation
      30 protected:
      31     HICON m_hIcon;
      32 
      33     // Generated message map functions
      34     //{{AFX_MSG(CChatDlg)
      35     virtual BOOL OnInitDialog();
      36     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
      37     afx_msg void OnPaint();
      38     afx_msg HCURSOR OnQueryDragIcon();
      39     afx_msg void OnBtnSend();
      40     //}}AFX_MSG
      41     afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
      42     DECLARE_MESSAGE_MAP()
      43 };

      这里我们将线程入口函数定义为静态类型,是因为该函数不属于该类的任一个对象,它只属于类的本身。也就是在CChatDialog类的OnInitDialog函数中创建线程时,运行时代码就可以直接调用该类的静态函数,从而起动线程。

    9. 对于该线程入口函数,我们定义如下:
       1 DWORD CChatDlg::RecvProc(LPVOID lpParameter)
       2 {
       3     //get sock and dialog handle
       4     SOCKET sock=((RECVPARAM*)lpParameter)->sock;
       5     HWND hwnd=((RECVPARAM*)lpParameter)->hwnd;
       6     delete lpParameter;
       7     sockaddr_in addrFrom;
       8     int len=sizeof(sockaddr);
       9     char recvBuf[200];
      10     char tempBuf[300];
      11     int retval;
      12     while(TRUE)
      13     {
      14         //receive data
      15         retval=recvfrom(sock,recvBuf,200,0,(sockaddr*)&addrFrom,&len);
      16         if(SOCKET_ERROR==retval)
      17         {
      18             break;
      19         }
      20         sprintf(tempBuf,"%s: %s",inet_ntoa(addrFrom.sin_addr),recvBuf);
      21         //post message to dialog
      22         ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
      23     }
      24     return 0;
      25 }

      通过线程函数参数获取传递过来的sock与句柄。然后让线程一直处于循环状态去接收数据,最后将接收到数据打包后,以消息的形式(这里我们自定义消息为:WM_RECVDATA)传递给对话框 ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf); 

    10. 在CChatDialog.h为我们自定义的消息,定义其值
      1 #define WM_RECVDATA    WM_USER+1
      2 
      3 struct RECVPARAM
      4 {
      5     SOCKET sock;//created socket
      6     HWND hwnd;    //dialog handle
      7 };
      8 .....

      并在CChatDlg类中定义消息响应函数:

       1 // ChatDlg.h : header file
       2 //
       3 
       4 #if !defined(AFX_CHATDLG_H__CD52C417_6B67_4577_B97C_7A2DE53A5F9C__INCLUDED_)
       5 #define AFX_CHATDLG_H__CD52C417_6B67_4577_B97C_7A2DE53A5F9C__INCLUDED_
       6 
       7 #if _MSC_VER > 1000
       8 #pragma once
       9 #endif // _MSC_VER > 1000
      10 
      11 /////////////////////////////////////////////////////////////////////////////
      12 // CChatDlg dialog
      13 
      14 #define WM_RECVDATA    WM_USER+1
      15 
      16 struct RECVPARAM
      17 {
      18     SOCKET sock;//created socket
      19     HWND hwnd;    //dialog handle
      20 };
      21 
      22 class CChatDlg : public CDialog
      23 {
      24 private:
      25     SOCKET m_sock;
      26     BOOL InitSocket();
      27     static DWORD WINAPI RecvProc(LPVOID lpParameter);
      28 // Construction
      29 public:
      30     CChatDlg(CWnd* pParent = NULL);    // standard constructor
      31 
      32 // Dialog Data
      33     //{{AFX_DATA(CChatDlg)
      34     enum { IDD = IDD_CHAT_DIALOG };
      35         // NOTE: the ClassWizard will add data members here
      36     //}}AFX_DATA
      37 
      38     // ClassWizard generated virtual function overrides
      39     //{{AFX_VIRTUAL(CChatDlg)
      40     protected:
      41     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
      42     //}}AFX_VIRTUAL
      43 
      44 // Implementation
      45 protected:
      46     HICON m_hIcon;
      47 
      48     // Generated message map functions
      49     //{{AFX_MSG(CChatDlg)
      50     virtual BOOL OnInitDialog();
      51     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
      52     afx_msg void OnPaint();
      53     afx_msg HCURSOR OnQueryDragIcon();
      54     //}}AFX_MSG
      55     afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
      56     DECLARE_MESSAGE_MAP()
      57 };
      58 
      59 //{{AFX_INSERT_LOCATION}}
      60 // Microsoft Visual C++ will insert additional declarations immediately before the previous line.
      61 
      62 #endif // !defined(AFX_CHATDLG_H__CD52C417_6B67_4577_B97C_7A2DE53A5F9C__INCLUDED_)
    11. 下面我们为我们自定义的消息添加映像
      1 BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
      2     //{{AFX_MSG_MAP(CChatDlg)
      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(WM_RECVDATA,OnRecvData)
      9 END_MESSAGE_MAP()

      注意添加映像时不需要添加分号 ‘;’

    12. 最后我们将写消息响应函数,把我们接收到message显示到编辑框上:
       1 //receive data message function
       2 void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)
       3 {
       4     //get new receive data
       5     CString str=(char*)lParam;
       6     CString strTemp;
       7     //get the history data
       8     GetDlgItemText(IDC_EDIT1,strTemp);
       9     str+="
      ";
      10     str+=strTemp;
      11     //display all of data
      12     SetDlgItemText(IDC_EDIT1,str);
      13 }

      至此,我们的接收message处理就算是完成了。

    下面我们开始着手编写发送message的程序,为我们的按键控件添加事件处理函数,此事件作为发送message的处理函数:

     1 void CChatDlg::OnBtnSend() 
     2 {
     3     // TODO: Add your control notification handler code here
     4     //get ip address
     5     DWORD dwIP;
     6     ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
     7     sockaddr_in addrTo;
     8     addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
     9     addrTo.sin_family=AF_INET;
    10     addrTo.sin_port=htons(6000);
    11     //get message which you want to send
    12     CString strSend;
    13     GetDlgItemText(IDC_EDIT2,strSend);
    14     //send message
    15     sendto(m_sock,strSend,strSend.GetLength()+1,0,(sockaddr*)&addrTo,sizeof(sockaddr));
    16     SetDlgItemText(IDC_EDIT2,"");
    17 }

    至此,所有的代码已经完成,编译运行:

    到这里,我们对于线程的运用就算讲解完了。

    End.

    谢谢.

  • 相关阅读:
    【Mac + Appium + Java1.8学习(三)】之IOS自动化环境安装配置以及简单测试用例编写(模拟器、真机)
    【Mac + Appium + Java1.8学习(一)】之Android自动化环境安装配置以及IDEA配置(附录扩展Selenium+Java自动化)
    【Mac + Appium + Python3.6学习(六)】之安装Android模拟器(Genymotion)并运行模拟器进行自动化
    【Mac + Appium + Python3.6学习(五)】之常用的Android自动化测试API总结
    【Mac + Appium + Python3.6学习(四)】之常用的IOS自动化测试API总结
    【Mac + Appium + Python3.6学习(三)】之IOS自动化测试环境配置
    【Mac + Appium + Python3.6学习(二)】之Android自动化测试,appium-desktop配置和简易自动化测试脚本
    【Mac + Appium学习(一)】之安装Appium环境前提准备
    【Mac + Python3.6 + ATX基于facebook-wda】之IOS自动化(三):facebook-wda库--API学习以及附录:Github上对WDA的问题解答
    【转】【Mysql学习】之Mac上用终端使用mySQL
  • 原文地址:https://www.cnblogs.com/lumao1122-Milolu/p/11805323.html
Copyright © 2020-2023  润新知