对于多线程,我们已经有了综合理解。下面我们就其应用,编写一个简易的实用应用程序----网络聊天室程序的实现;
我们知道聊天室基本功能包含两个,一个是显示接收到的message,另一个就是发送自己的message。这里我们就依据之前掌握的socket来负责网络通信,采用UDP协议。
- 创建聊天室UI对话框
- 因为我们要用到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
- 接下来,我们创建套接字socket,在CChatDlg类中定义socket成员变量和socket初始化成员函数
1 class CChatDlg : public CDialog 2 { 3 private: 4 SOCKET m_sock; 5 BOOL InitSocket(); 6 .... 7 }
- 编写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.
- 该初始化函数在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 }
- 接下来我们编写实现接收功能的程序。我们知道基于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 }
- 接下来创建线程,在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函数中。
- 下面我们需要对这个线程函数进行编写接收来自网络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函数中创建线程时,运行时代码就可以直接调用该类的静态函数,从而起动线程。
- 对于该线程入口函数,我们定义如下:
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);
- 在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_)
- 下面我们为我们自定义的消息添加映像
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()
注意添加映像时不需要添加分号 ‘;’
- 最后我们将写消息响应函数,把我们接收到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.
谢谢.