多线程(三)

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

我们知道聊天室基本功能包含两个,一个是显示接收到的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+="\r\n";
    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.

谢谢.

posted on 2019-11-06 15:11  Milo_lu  阅读(215)  评论(0编辑  收藏  举报

导航