基于UDP的MFC局域网聊天程序
开发环境VS2010
这个小程序用到了UDP通信和多线程的知识。
已知问题:不能显示中文。发送内容不能过长。滚动条没有自动滚到最后一行。
基本思路:在初始化对话框的时候就创建一个线程,在这个线程里进行套接字初始化,然后一直阻塞到接受到消息。
总结:接受消息显示采用的是往CString 变量里面追加东西,然后再分行显示。这样做个人感觉肯定不是最优,但是我目前只能想到这个办法了。至于不能显示中文的问题,我知道发生在接收消息阶段。整个消息发送接收过程是这样处理的:从控件上获取到CString的,然后转换格式成wchar_t,再格式化成char* 通过sendto函数发送过去。接受端接收了char*也是转成wchar_t再转成CString显示的。这里面格式转换是存在问题的,希望有人能指点。
关键代码存档
在头文件里添加成员变量static DWORD WINAPI ThreadProc1(LPVOID lpParameter); 下面和也是。
View Code
1 /*变量声明*/ 2 HANDLE hThread1; 3 HANDLE hThread2; 4 CString m_Edit; //发送消息的编辑控件的成员变量 5 CIPAddressCtrl m_ip; //ip控件的成员变量 6 CString m_msg; // 显示消息控件的成员变量,没用到 7 8 void OnOK(); /*隐藏默认的OnOK函数,就是按程序运行时下回车时调用的函数,这里我们要重新定义一下*/
View Code
1 /*在OnInitDialog()中添加额外的初始化代码*/ 2 hThread1 = ::CreateThread(NULL,0,ThreadProc1,this,0,NULL); /*在初始化对话框的时候就创建线程*/ 3 4 5 /*点击发送按钮时的代码*/ 6 void CMFCUDPDlg::OnBnClickedButton1() 7 { 8 9 CSocket socket; 10 if( !socket.Create(0, SOCK_DGRAM, NULL) ) 11 { 12 MessageBox(_T("创建套接字失败")); 13 } 14 //UpdateData(TRUE); 15 16 wchar_t *sendMsg; 17 GetDlgItemTextW(IDC_EDIT2,m_Edit); 18 19 sendMsg=m_Edit.GetBuffer(m_Edit.GetLength()); 20 m_Edit.ReleaseBuffer(); 21 22 /*把char类型的sendMsg转化为wchar_t类型的WStr。 23 size_t len = strlen(sendMsg) + 1; 24 size_t converted = 0; 25 wchar_t *WStr; 26 WStr=(wchar_t*)malloc(len*sizeof(wchar_t)); 27 mbstowcs_s(&converted, WStr, len, sendMsg, _TRUNCATE); 28 */ 29 30 /*测试类型转换以后是否有乱码 ,我现在觉得下面这种格式化,还是从wchar_t变成CString方便 31 m_Edit.Format(_T("%s"),sendMsg); 32 AfxMessageBox(m_Edit); 33 */ 34 35 //此时的sendMsg已经变成了wchar_t类型的,接下来要把它变成char*型的 36 size_t len = wcslen(sendMsg) + 1; 37 size_t converted = 0; 38 char *message; 39 message=(char*)malloc(len*sizeof(char)); 40 wcstombs_s(&converted, message, len, sendMsg, _TRUNCATE); 41 42 /*获取ip空间里的ip地址*/ 43 BYTE f0, f1, f2, f3; 44 m_ip.GetAddress(f0, f1, f2, f3); 45 CString m_addr; 46 m_addr.Format(_T("%d%s%d%s%d%s%d"), f0, ".", f1, ".", f2, ".", f3); 47 48 int length = socket.SendTo(message, strlen(message), 9000, m_addr, 0); 49 socket.Close(); 50 51 52 } 53 54 /*因为线程函数是静态的,不能调用非静态成员,在此创建一个类类型的指针*/ 55 CMFCUDPDlg* p; 56 /*初始化对话框时,创建线程做的事情*/ 57 DWORD WINAPI CMFCUDPDlg::ThreadProc1(LPVOID lpParameter) 58 { 59 60 CMFCUDPDlg *p=(CMFCUDPDlg*)lpParameter; 61 CSocket Server; 62 63 // 创建用于接收数据报的套接字 64 if (Server.Create(9000, SOCK_DGRAM, NULL)== 0) 65 { 66 AfxMessageBox(_T("创建Socket失败")); 67 return -1; 68 } 69 70 while(TRUE) 71 { 72 SOCKADDR_IN ClntAddr; 73 int clntAddrLen = sizeof(ClntAddr); 74 char echoBuffer[1024]=""; 75 CString buffer; 76 static CString save; 77 78 //一直阻塞到有客户端发来数据 79 int recvMsgSize = Server.ReceiveFrom(echoBuffer,sizeof(echoBuffer), (SOCKADDR*)&ClntAddr, &clntAddrLen, 0); 80 81 if (recvMsgSize < 0) 82 { 83 AfxMessageBox(_T("接受数据异常")); 84 } 85 else 86 { 87 //AfxMessageBox(_T("有消息")); 88 } 89 90 //把echoBuffer转格式成wchar_t 91 size_t len = strlen(echoBuffer) + 1; 92 size_t converted = 0; 93 wchar_t *WStr; 94 WStr=(wchar_t*)malloc(len*sizeof(wchar_t)); 95 mbstowcs_s(&converted, WStr, len, echoBuffer, _TRUNCATE); 96 97 //获取对方ip 98 char *addr = inet_ntoa(ClntAddr.sin_addr); 99 //把addr转格式成wchar_t 100 len = strlen(addr) + 1; 101 wchar_t *address; 102 address=(wchar_t*)malloc(len*sizeof(wchar_t)); 103 mbstowcs_s(&converted, address, len, addr, _TRUNCATE); 104 105 /*把发送端的ip地址和消息还有换行都存放在buffer里,然后一直往save里追加*/ 106 buffer.Format(_T("【%s】说 :%s\r\n"),address,WStr); 107 save = save+buffer; 108 109 p->SetDlgItemTextW(IDC_EDIT1,save); 110 p->SetDlgItemTextW(IDC_EDIT2,NULL); 111 //p->m_msg = buffer; 112 //p->UpdateData(false); 113 } 114 Server.Close(); 115 116 return 0; 117 118 }
View Code
void CMFCUDPDlg::OnOK() { /*当编辑好发送内容后,直接按回车,就相当于点击了发送按钮*/ OnBnClickedButton1(); }
提示:如果没有换行,记得将编辑控件的属性里的Multiline属性设置为true,然后记得添加滚动条,需要的话设置成readonly。
感想:我承认里面做了很多烦琐而低效的类型转换。希望有人能指点。化繁为简。