如何创建一个利用UDP实现消息收发功能的MFC程序

来源:互联网 发布:mac 远程拷贝文件夹 编辑:程序博客网 时间:2024/06/06 08:29

首先我们先来开一下所要实现的对话框是什么样子的以及它所要实现的功能;


如图所示,我们的程序需要实现的功能是向客户机发送数据并接受来自客户机的数据,同时能够统计发送到的和接收到的字节数,并能够在完成任务后清空计数以及发送区和接收区的数据。

由于使用的是UDP通讯方式,首先我们需要将主机的IP地址进行绑定,在绑定后,绑定按钮会变成已绑定,再次点击就可以解绑。
正式进入编程,我们首先要编写的是初始化程序:
m_LocalPort.SetWindowText("8001");m_isBindUDP = false;g_receNum = 0;g_sendNum = 0;

m_LocalPort是本地端口号控件绑定的控件变量,SetWindoeText函数的作用是可以在控件上显示所需要显示的变量,注意该变量必须是字符串类型的。这里我们设置本地的默认端口号为8001。m_isBindUDP是一个BOOL型变量,作用是判断本地IP和端口是否绑定,因为初始化时未绑定,所以赋值为false。g_receNum和g_sendNum是两个整型变量,用作存储发送和接收字符的长度,在初始化中清零是为了防止在之后的程序中出现误操作。在此,需要强调一个关于定义变量的问题:

头文件


源文件

我们在定义一个全局变量时最好在头文件中定义,并且在定义前加一个extern,在源文件中要引用时,要在源文件开头再声明一遍。根据匈牙利命名法,在定义全局变量时最好在变量前加g_,不容易将变量弄混。
初始化结束后,要进行的就是绑定本地IP和端口:
void CfykudpDlg::OnBnClickedBind(){//setp1 获取本地IP protCString strTmp;int localPort;m_LocalPort.GetWindowText(strTmp);localPort = atoi(strTmp);//绑定与解绑if (FALSE == m_isBindUDP){//进入函数,没有绑定//绑定udpg_UDP.InitSocket(localPort);//更新ui和数据strTmp.Format("%s",g_UDP.m_HostIP);m_LocalIP.SetWindowText(strTmp);m_isBindUDP = TRUE;m_btn_Bind.SetWindowText("已绑定");m_LocalPort.EnableWindow(FALSE);//临时设置remote IP Portm_RemoteIP.SetWindowText(strTmp);m_RemotePort.SetWindowText("8002");//开 接收线程g_isRunThread = TRUE;//用于while中g_thread = AfxBeginThread( UDP_Rece_ThreadProc,(LPVOID)this, THREAD_PRIORITY_NORMAL,0,0,NULL);} else{//解除udp绑定 g_UDP.DeletSocket();//更新ui和数据m_LocalIP.SetWindowText("-.-.-.-");m_isBindUDP = FALSE;m_btn_Bind.SetWindowText("绑定");m_LocalPort.EnableWindow(TRUE);//关闭线程g_isRunThread = FALSE;}}
第一步是获取本地IP和端口号,先定义一个CString类型的字符串变量strTemp,用途是作为存储输入信息的中间变量。GetWindowText函数的作用是获取控件中的输入信息,用这个函数本地端口号绑定的控件变量就可以获得输入的端口号信息了。我们获得的该端口号是字符串类型的,而我们需要的是整型的,因此还需要atoi()函数进行转换,该函数名的全称就是ascii to int,顾名思义,该函数的作用就是实现字符串向整型的转换。首先获取端口号是因为建立UDP通讯的第一步是初始化套接字,而该函数的入口参数
就是端口号,因此我们需要先获取端口号。
在编写该程序时用到了师兄所编写的一个UDP类,该类包含了GetHostIP,InitSocket,DeletSocket,UDP_Send,UDP_Rece等几个函数。在使用类之前必须先定义一
个类的实体,即对象,因此本程序中定义了该类的对象为g_UDP。
由于我们需要判断是否绑定成功,并且在成功后需要改变按钮所显示的文字,因此在这里要用到一个if函数。在未绑定成功时,先初始化套接字,然后用GetHostIP函数获取
本地主机的IP地址,再用SetWindowText函数显示该IP地址,并将m_BindUDP置为TRUE这样的话下次进入if函数的时候就可以跳出函数了。绑定完成后,将绑定按钮上的文字
设为已绑定,同时将端口的编辑框设为不可编辑的,用到的是EnableWindow函数。
一个程序相当于一个进程,当在一个进程中需要同时执行多个不同任务时就需要开启多个线程,一个线程相当于执行一个任务。在本程序中由于收发是需要同时进行的,因
此接收数据就要开启一个新的进程。由于在接下来编写的接收线程函数中需要一个while循环来确定何时结束线程,所以定义一个BOOL型变量g_isRunThread当作标志位。MFC
中把线程分为两类,一类为界面线程,一类为工作线程。两者的区别在于前都能够处理消息响应,而后者则不能。在这里,我们建立的是一个工作线程。AfxBeginThread函数中
除前面两个参数外,后面的都是默认参数,可以省略。而必须有的这两个参数,一个是线程函数的指针,一个是传递给这个函数的参数。实际中我们经常这样用 AfxBeginThread
(ThreadProc,this);//把this传过去,就可以调用类的成员了。这样线程函数就可以使用和操作类的成员了。千万要注意线程函数是静态类函数成员或者是全局函数。
线程创建后需要对它进行暂停或者恢复。我们在创建线程的时候获得了一个CWinThread的指针,这是一个指向线程对象的指针,CWinThread类里面就有暂停与恢复的函
数。
工作者线程的AfxBeginThread的原型如下:
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,  LPVOID lParam,  int nPriority = THREAD_PRIORITY_NORMAL,  UINT nStackSize = 0,  DWORD dwCreateFlags = 0,  LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL


依照函数原型我们依次填入函数的各个参数,其中第一个参数是线程函数的函数名,第二个参数是传递入线程的参数,我们这里是将this指针强制类型转化为LPVOID类型,以操作类的成员。
在开启线程函数后,我们需要编写具体的接收线程函数。该函数为一个全局函数:
UINT UDP_Rece_ThreadProc(LPVOID pParam){CfykudpDlg* pdlg = (CfykudpDlg*)g_pDlg;CString strTmp,strOld;memset(g_receBuff,0,sizeof(g_receBuff));int receNum;while( g_isRunThread ){//rece_num = UDP_Rece(g_receBuff,sizeof(g_receBuff),0);//MessageBox("ddd");receNum = g_UDP.UDP_Rece( g_receBuff, sizeof(g_receBuff) );g_receNum += receNum;//更新UIstrTmp.Format("%d",g_receNum);pdlg->m_recv_num.SetWindowText(strTmp);//更新计数strTmp.Format("%s",g_receBuff);pdlg->m_edit_Recv.GetWindowText(strOld);strOld += strTmp;pdlg->m_edit_Recv.SetWindowText(strOld);}return 0;}

在MFC中使用全局函数的一个难点就是如何在全局函数中调用MFC自带类库中的成员函数,这是因为这些成员函数只能在其派生类中使用,而全局函数则不派生于任何基类。本程序是用如下方法解决该问题的:首先,先定义一个全局的void类型的指针:
然后再在初始化函数中将指向类的this指针赋给该全局指针:
最后将该指针强制类型转化为指向MFC对话框类派生类的指针:
g_receBuff是一个全局数组,作用是用来存放接收区所获取到的数据。UDP_rece函数的返回值是接收到的数据的长度,利用该返回值可以获得接收到的字符数,让其显示到接收计数区。
strOld存储的是之前接收区接收到的还未被清除的数据,strOld+=strTmp是将旧的数据和新的数据同时显示在接收区,以免旧的数据在新数据发送时被覆盖。
发送的消息响应函数如下:
void CfykudpDlg::OnBnClickedSend(){CString strSend, remoteIP;int remotePort;//send ip,portm_RemotePort.GetWindowText(strSend);remotePort = atoi(strSend);//portm_RemoteIP.GetWindowText(remoteIP);//获取数据 发送的数据m_edit_Send.GetWindowText(strSend);//发送if (TRUE == m_isBindUDP){g_UDP.UDP_Send( (LPSTR)(LPCSTR)remoteIP,remotePort,(LPSTR)(LPCSTR)strSend,strSend.GetLength());g_sendNum += strSend.GetLength();strSend.Format("%d",g_sendNum);m_send_Num.SetWindowText(strSend);}}
其中需要注意的是(LPSTR)(LPCSTR)是将字符串强制类型转换为char型指针。
至此,程序已经能够完成数据的收发,最后所需要的只是将接收发送区以及计数清空,比较简单,代码如下:
void CfykudpDlg::OnBnClickedButton5(){// TODO: 在此添加控件通知处理程序代码m_send_Num.SetWindowText("0");m_recv_num.SetWindowText("0");}void CfykudpDlg::OnBnClickedButton1(){// TODO: 在此添加控件通知处理程序代码m_strRecvdData = "";UpdateData(FALSE);}void CfykudpDlg::OnBnClickedButton2(){// TODO: 在此添加控件通知处理程序代码m_strSendData = "";UpdateData(FALSE);}







0 0