基于vs2008的一个简单的多线程聊天程序(有界面)

来源:互联网 发布:三峡大学网络系统 编辑:程序博客网 时间:2024/06/08 08:36

这个是看了孙鑫老师的视频之后,敲出来的代码,虽然老师的视频讲解的已经很好了,但是在vs里运行起来,会碰到一些令人头疼的问题,终于靠着强大的网络,将这些问题一个一个都解决了。

这个基于线程、socket和对话框的小的聊天程序,目的是在于理解多线程,熟悉socket,将其放在对话框中去练习。

后来,我又给对话框添加了响应回车键的事件,就是按回车键,即发送消息,就是像qq那样的,这个看起来很简单,但是实行起来,还得小费一番功夫的。

具体的代码和代码中比较重要的地方,我都做了注释。

源代码如下:

// ChatDlg.h : 头文件//#pragma once#define WM_RECVDATA    WM_USER+1#define WM_RETURNDOWN   WM_USER+2//自定义了一个消息,回车消息struct RECVPARAM{SOCKET sock;HWND hWnd;};// CChatDlg 对话框class CChatDlg : public CDialog{// 构造public:CChatDlg(CWnd* pParent = NULL);// 标准构造函数// 对话框数据enum { IDD = IDD_CHAT_DIALOG };protected:virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV 支持// 实现protected:HICON m_hIcon;// 生成的消息映射函数virtual BOOL OnInitDialog();afx_msg void OnSysCommand(UINT nID, LPARAM lParam);afx_msg void OnPaint();afx_msg HCURSOR OnQueryDragIcon();afx_msg LRESULT OnRecvData(WPARAM wParam,LPARAM lParam);afx_msg LRESULT OnReturnDown(WPARAM wParam,LPARAM lParam);//声明了一个消息响应函数DECLARE_MESSAGE_MAP()public:bool InitSocket(void);private:SOCKET m_socket;public:static DWORD WINAPI RecvProc(LPVOID lpParameter);afx_msg void OnBnClickedBtnSend();BOOL PreTranslateMessage(MSG *pMsg);};

// ChatDlg.cpp : 实现文件//#include "stdafx.h"#include "Chat.h"#include "ChatDlg.h"#ifdef _DEBUG#define new DEBUG_NEW#endif// 用于应用程序“关于”菜单项的 CAboutDlg 对话框class CAboutDlg : public CDialog{public:CAboutDlg();// 对话框数据enum { IDD = IDD_ABOUTBOX };protected:virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持// 实现protected:DECLARE_MESSAGE_MAP()};CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD){}void CAboutDlg::DoDataExchange(CDataExchange* pDX){CDialog::DoDataExchange(pDX);}BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)END_MESSAGE_MAP()// CChatDlg 对话框CChatDlg::CChatDlg(CWnd* pParent /*=NULL*/): CDialog(CChatDlg::IDD, pParent){m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);}void CChatDlg::DoDataExchange(CDataExchange* pDX){CDialog::DoDataExchange(pDX);}BEGIN_MESSAGE_MAP(CChatDlg, CDialog)ON_WM_SYSCOMMAND()ON_WM_PAINT()ON_WM_QUERYDRAGICON()//}}AFX_MSG_MAPON_MESSAGE(WM_RECVDATA,OnRecvData)ON_MESSAGE(WM_RETURNDOWN,OnReturnDown)//进行消息和消息响应函数的关联ON_BN_CLICKED(IDC_BTN_SEND, &CChatDlg::OnBnClickedBtnSend)END_MESSAGE_MAP()// CChatDlg 消息处理程序BOOL CChatDlg::OnInitDialog(){CDialog::OnInitDialog();// 将“关于...”菜单项添加到系统菜单中。// IDM_ABOUTBOX 必须在系统命令范围内。ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);ASSERT(IDM_ABOUTBOX < 0xF000);CMenu* pSysMenu = GetSystemMenu(FALSE);if (pSysMenu != NULL){BOOL bNameValid;CString strAboutMenu;bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);ASSERT(bNameValid);if (!strAboutMenu.IsEmpty()){pSysMenu->AppendMenu(MF_SEPARATOR);pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);}}// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动//  执行此操作SetIcon(m_hIcon, TRUE);// 设置大图标SetIcon(m_hIcon, FALSE);// 设置小图标// TODO: 在此添加额外的初始化代码InitSocket();//由于作为接收端程序,用recvfrom会处于阻塞状态,因为在这里要用线程//在线程中要用到两个变量,一个是SOCKET,一个是窗口的句柄(或是编辑框的句柄)//SOCKET用来接收消息,句柄用来将接收到的消息,进行格式转换后,显示在界面上//由于CreateThread()函数只能接收一个变量,且是个指针型的变量,所以这里要用结构体//结构体定义在"ChatDlg.h"中RECVPARAM *pRecvParam=new RECVPARAM;pRecvParam->sock=m_socket;pRecvParam->hWnd=m_hWnd;HANDLE hThread=CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);//RecvProc方法要声明成静态的,因为此时对象还没有创建CloseHandle(hThread);SetDlgItemText(IDC_IPADDRESS1,_T("127.0.0.1"));return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE}void CChatDlg::OnSysCommand(UINT nID, LPARAM lParam){if ((nID & 0xFFF0) == IDM_ABOUTBOX){CAboutDlg dlgAbout;dlgAbout.DoModal();}else{CDialog::OnSysCommand(nID, lParam);}}// 如果向对话框添加最小化按钮,则需要下面的代码//  来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,//  这将由框架自动完成。void CChatDlg::OnPaint(){if (IsIconic()){CPaintDC dc(this); // 用于绘制的设备上下文SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);// 使图标在工作区矩形中居中int cxIcon = GetSystemMetrics(SM_CXICON);int cyIcon = GetSystemMetrics(SM_CYICON);CRect rect;GetClientRect(&rect);int x = (rect.Width() - cxIcon + 1) / 2;int y = (rect.Height() - cyIcon + 1) / 2;// 绘制图标dc.DrawIcon(x, y, m_hIcon);}else{CDialog::OnPaint();}}//当用户拖动最小化窗口时系统调用此函数取得光标//显示。HCURSOR CChatDlg::OnQueryDragIcon(){return static_cast<HCURSOR>(m_hIcon);}bool CChatDlg::InitSocket(void)//初始化套接字,进行套接字的绑定{m_socket=socket(AF_INET,SOCK_DGRAM,0);if(INVALID_SOCKET == m_socket){AfxMessageBox("套接字创建失败!");return false;}SOCKADDR_IN addrSock;addrSock.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");addrSock.sin_family=AF_INET;addrSock.sin_port=htons(9527);int retval=bind(m_socket,(sockaddr*)&addrSock,sizeof(sockaddr));if(retval==SOCKET_ERROR){AfxMessageBox("绑定失败");return false;closesocket(m_socket);}return false;}DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)//线程函数,定义的是静态的{SOCKET sock=((RECVPARAM *)lpParameter)->sock;HWND hWnd=((RECVPARAM *)lpParameter)->hWnd;SOCKADDR_IN addrFrom;int len=sizeof(SOCKADDR);char recvBuf[100];char tempBuf[200];int retval;while(true){retval=recvfrom(sock,recvBuf,100,0,(sockaddr *)&addrFrom,&len);if(retval==SOCKET_ERROR)break;sprintf_s(tempBuf,200,"%s 说:%s",inet_ntoa(addrFrom.sin_addr),recvBuf);::PostMessage(hWnd,WM_RECVDATA,0,(LPARAM)tempBuf);//将tempBuf通过自定义的WM_RECVDATA消息传递给hWnd//在此处可以充分理解windows的消息映射机制}return 0;}LRESULT CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)//自定义的消息映射函数{CString strNew=(char *)lParam;CString strOld;GetDlgItemText(IDC_EDIT_RECV,strOld);strNew+="\r\n";strOld+=strNew;SetDlgItemText(IDC_EDIT_RECV,strOld);return 0;}void CChatDlg::OnBnClickedBtnSend(){// TODO: Add your control notification handler code hereDWORD dwIP;((CIPAddressCtrl *)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);//得到ip控件里的ip地址SOCKADDR_IN addrTo;addrTo.sin_addr.S_un.S_addr=htonl(dwIP);addrTo.sin_family=AF_INET;addrTo.sin_port=htons(9527);CString strSend;GetDlgItemText(IDC_EDIT_SEND,strSend);sendto(m_socket,strSend,strSend.GetLength()+1,0,(sockaddr *)&addrTo,sizeof(SOCKADDR));GetDlgItem(IDC_EDIT_SEND)->SetFocus();//设置焦点SetDlgItemText(IDC_EDIT_SEND,NULL);}BOOL CChatDlg::PreTranslateMessage(MSG *pMsg)//拦截消息,判断是否为回车键{if(pMsg->message>=WM_KEYFIRST && pMsg->message<=WM_KEYLAST){if(pMsg->wParam==VK_RETURN && pMsg->message==WM_KEYDOWN)OnReturnDown(0,0);}return CDialog::PreTranslateMessage(pMsg);}LRESULT CChatDlg::OnReturnDown(WPARAM wParam,LPARAM lParam)//具体的消息响应函数{HWND hwnd=::GetFocus();//获得当前焦点所在的控件的句柄int ID=::GetDlgCtrlID(hwnd);//获得该控件句柄的IDif(ID==IDC_EDIT_SEND){OnBnClickedBtnSend();}return 0;}

通过写这个小程序,我学会了以下一些东西:

1、线程的创建和在对话框中的使用,以及socket的练习和其在对话框中的使用,以及两者结合到一起到的使用。关键是在哪开线程呢?关键是在接收信息的时候开线程。那从什么时候开始接受消息呢?当然是在窗口创建好就开始接收信息了。所以要在OnInitDialog()函数里,创建线程,并且立即启动线程,开始接收。但由于此时窗口还没有创建好,还处于初始化的阶段,所以不能在此时调用线程函数,故只能将线程函数定义成静态的了。

2、自定义一个消息响应函数的流程:

      1)先在头文件中定义一个消息,并且声明一个函数,在vs中,自定义的函数只能是下面这一种类型:

            afx_msg LRESULT FunctionName(WPARAM wParam,LPARAM lParam); 

      2)在.cpp文件,即实现文件中,对消息和函数进行关联:ON_MESSAGE(WM_RETURNDOWN,FunctionName)

      3)然后,就是实现具体的消息响应函数了

3、windows中消息的传递机制,说直白一点,就是以消息来驱动函数的执行,就像第2点说的,将一个消息和一个函数进行关联后,若这个消息产生,则会驱动与之相关联的函数的执行。

4、在对话框中,对编辑框中回车键的响应:

     1)定义一个回车键的消息和一个与之相关联的响应函数。

     2)通过覆盖对话框的PreTranslateMessage()方法,来拦截消息,对消息进行判断,如果是回车键消息,则引发与之相关联的响应函数,作出相应的操做



原创粉丝点击