0


[MFC]MFC实现UDP客户端和服务端信息交互

前言

最近继续在学习MFC的相关操作,本次博客以实现一个UDP协议的服务端和客户端之间的信息交互,本博客将具体进行以下分析.

一.预期实现效果

二.UDP服务器端

UDP服务器端和UDP客户端一致,都是基于CSocket类进行实现.在首先创建MFC新项目时,需要在高级功能里选中Windows 套接字选项栏.

并在类视图中,添加一个CServerSocket类,继承CSocket类,并配置相应的头文件和源文件.

1.初始界面

2.ServerSocket.h

#pragma once
#include <afxsock.h>

class CUdpServerDlg;
class CServerSocket :
    public CSocket
{
public:
    CServerSocket(CUdpServerDlg* pdlg);
    virtual ~CServerSocket();
private:
    CUdpServerDlg* m_pMainDlg;
protected:
    virtual void OnReceive(int nErrorCode);//重写一个方法
};

3.ServerSocket.cpp

#include"UdpServerDlg.h"
CServerSocket::CServerSocket(CUdpServerDlg* pdlg)
{
    this->m_pMainDlg = pdlg;
}

CServerSocket::~CServerSocket()
{

}

void CServerSocket::OnReceive(int nErrorCode)
{
    //接受函数信息
    CSocket::OnReceive(nErrorCode);
    m_pMainDlg->ProcessPendingRead();//主要的信息传输的功能函数
}

在源文件中,添加CServerSocket的构造函数和析构函数,并添加相应的虚函数OnReceive,用于数据的传输与接受

整个服务端要实现以下功能:显示客户端的进入和离开,显示当前客服端的数量以及客户端之间的信息交互.在头文件中声明相关变量:

public:
    CServerSocket* m_pServerSocket;
    CArray<ClientAddr,ClientAddr&>m_ClientAddrList;
    void ProcessPendingRead();

在源文件的初始化函数BOOL CUdpServerDlg::OnInitDialog()函数中,进行初始化操作:

    //初始化
    m_pServerSocket = new CServerSocket(this);//初始化,新创建一个对话框Socket
    m_pServerSocket->Create(8080, SOCK_DGRAM);//创建UDP连接

因为UDP和TCP协议都是继承的CSocket基类,这里主要的不同是主变量m_pServerSocket类中的创建类型,UDP协议就是SOCK_DGRAM.

4.信息交互函数ProcessPendingRead()

void CUdpServerDlg::ProcessPendingRead()
{
    TCHAR buff[4096];
    ClientAddr clientAddr;
    int nRead = m_pServerSocket->ReceiveFrom(buff, 4096, clientAddr.strIP, clientAddr.uiPort);

    if (nRead == SOCKET_ERROR)
    {
        return;
    }
    //很明显,这里的含义是C语言和C++中的字符串以'\0'结尾

    buff[nRead] = _T('\0');
    CString strTemp(buff);
    int i;

    //1.进入房间enter 2.离开房间 leave 3.聊天信息
    if (strTemp.CompareNoCase(_T("enter")) == 0)
    {
        //新的客户端
        //1.添加新的客户端IP地址
        m_ClientAddrList.Add(clientAddr);

        //2.通知其他客户端,有新的用户加入房间
        CString strEnterMsg;

        //格式化相关的消息
        strEnterMsg.Format(_T("系统消息:%s(%d) 进入了房间"), clientAddr.strIP, clientAddr.uiPort);
        for (i = 0; i < m_ClientAddrList.GetSize(); i++)
        {
            ClientAddr& tempClient = m_ClientAddrList.ElementAt(i);
            m_pServerSocket->SendTo(strEnterMsg, strEnterMsg.GetLength() + 1000, tempClient.uiPort, tempClient.strIP);

        }

        //3.更新界面
        SetDlgItemInt(IDC_EDIT_CLIENT_NUMBER, m_ClientAddrList.GetSize());
        CString allMsg;
        GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg);//提取原来控件上的信息
        SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg + _T("\r\n") + strEnterMsg);//界面更新

    }
    //离开房间
    if(strTemp.CompareNoCase(_T("leave")) == 0)
    {
        //1.移除用户
        //遍历列表
        for (i = 0; i < m_ClientAddrList.GetSize(); i++)
        {
            //遍历每一个Addr的参数类型
            ClientAddr& tempCLient = m_ClientAddrList.ElementAt(i);

            if (tempCLient.uiPort == clientAddr.uiPort && tempCLient.strIP.Compare(clientAddr.strIP) == 0)
            {
                break;
            }
        }
        if (i < m_ClientAddrList.GetSize())
        {
            m_ClientAddrList.RemoveAt(i);
        }
        //2.通知其他客户端,有人离开了

        CString strLeaveMsg;
        strLeaveMsg.Format(_T("系统消息:%s(%d)离开了房间!"), clientAddr.strIP, clientAddr.uiPort);
        //通知大家有人离开了
        for (i = 0; i < m_ClientAddrList.GetSize(); i++)
        {
            ClientAddr& tempClient = m_ClientAddrList.ElementAt(i);
            m_pServerSocket->SendTo(strLeaveMsg, strLeaveMsg.GetLength() + 1000, tempClient.uiPort, tempClient.strIP);

        }

        //3.更新界面

        SetDlgItemInt(IDC_EDIT_CLIENT_NUMBER, m_ClientAddrList.GetSize());
        CString allMsg;
        GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg);
        SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg + _T("\r\n") + strLeaveMsg);
    }
    else
    {
        //转发客户端消息
        CString strMsg;
        //转发消息
        strMsg.Format(_T("%s(%d):%s"), clientAddr.strIP, clientAddr.uiPort, strTemp);
        for (i = 0; i < m_ClientAddrList.GetSize(); i++)
        {
            ClientAddr& tempClient = m_ClientAddrList.ElementAt(i);
            m_pServerSocket->SendTo(strMsg, strMsg.GetLength() + 1000, tempClient.uiPort, tempClient.strIP);
        }
        //2.更新界面
        CString allMsg;
        GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg);
        SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg + _T("\r\n") + strMsg);
    }

}

三.UDP客户端

客户端的基本配置和服务器端基本一致,也是继承于CSocket基类,在头文件中声明以下变量,对应客户端之间要实现进入、离开、发送信息、以及最后的程序关闭操作.

public:
    CClientSocket* m_pClientSocket;
    BOOL m_bEnterRoom;//用来表示客户端是否进入了房间
    void ProcessPendingRead();

    afx_msg void OnBnClickedButtonEnter();
    afx_msg void OnBnClickedButtonExit();
    afx_msg void OnBnClickedButtonSend();
    virtual BOOL DestroyWindow();

1.初始界面

2.OnInitDialog()

    // TODO: 在此添加额外的初始化代码

    //1.创建UDPSocket
    m_pClientSocket = new CClientSocket(this);
    m_pClientSocket->Create(0, SOCK_DGRAM);//使用数据包Socket类型,也就是UDP类型

    //2.获取Socket绑定的ip和端口
    //获取本机的IP和端口号
    CString strIp;
    UINT uiPort;
    //获取本地的服务号和端口号
    m_pClientSocket->GetSockName(strIp, uiPort);
    //显示本地的端口号和IP号
    SetDlgItemText(IDC_EDIT_LOCAL_IP, strIp);
    SetDlgItemInt(IDC_EDIT_LOCAL_PORT, uiPort);

    //3.连接到服务器端的端口号
    SetDlgItemText(IDC_EDIT_SERVER_IP, _T("127.0.0.1"));
    SetDlgItemInt(IDC_EDIT_SERVER_PORT, 8080);//连接到服务器到的PORT

    //4.设置按钮的初始化状态
    GetDlgItem(IDC_BUTTON_EXIT)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(FALSE);
     

    //设置只读控件
    ((CEdit*)GetDlgItem(IDC_EDIT_SEND_MESSAGE))->SetReadOnly(TRUE);
    ((CEdit*)GetDlgItem(IDC_EDIT_SERVER_IP))->SetReadOnly(FALSE);
    ((CEdit*)GetDlgItem(IDC_EDIT_SERVER_PORT))->SetReadOnly(FALSE);

    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE

在初始化函数中,包括对客户端程序界面的一些细节设计,例如在刚进入到程序界面时,本地IP和本地端口设置为只读,不可修改,客户端IP和服务端端口可以进行修改,发送信息控制和发送按钮也设置成只读,不可进行修改.

客户端和服务端之间的连接,我们在设置客户端的参数时,创建的端口号为8080,在进行连接客户端端口号时,也是直接连接8080端口,连接客户端IP时,这里直接连接127.0.0.1.

3.客户端信息交互函数ProcessPendingRead()

//客户端在此处理接受到的数据
void CUdpClientDlg::ProcessPendingRead()
{
    //处理接受到的数据
    TCHAR buff[4096];

    //读取数据
    int nRead = m_pClientSocket->ReceiveFrom(buff, 4096, NULL, NULL);
    //判断一下
    if (nRead == SOCKET_ERROR)
    {
        return;
    }
    //字符串结束的末尾
    buff[nRead] = _T('\0');
    CString strMsg(buff);
    CString allMsg;

    //获取现有数据
    GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg);
    //添加数据
    SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, allMsg + _T("\r\n") + strMsg);
}

4.OnBnClickedButtonEnter()

void CUdpClientDlg::OnBnClickedButtonEnter()
{
    // TODO: 在此添加控件通知处理程序代码
    //1.获取服务端IP和端口
    CString strIp;
    UINT uiPort;
    
    GetDlgItemText(IDC_EDIT_SERVER_IP, strIp);
    uiPort = GetDlgItemInt(IDC_EDIT_SERVER_PORT);

    //2.发送enter消息
    CString strEnterMsg("enter");
    int nSend = m_pClientSocket->SendTo(strEnterMsg, strEnterMsg.GetLength() + 100, uiPort, strIp);
    if (nSend == SOCKET_ERROR)
    {
        MessageBox(_T("进入聊天室失败,可能是服务端尚未运行"));
        return;
    }

    //3.设置按钮、文本框的状态
    //4.设置按钮的初始化状态
    GetDlgItem(IDC_BUTTON_EXIT)->EnableWindow(TRUE);
    GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(TRUE);

    ((CEdit*)GetDlgItem(IDC_EDIT_SEND_MESSAGE))->SetReadOnly(FALSE);
    ((CEdit*)GetDlgItem(IDC_EDIT_SERVER_IP))->SetReadOnly(TRUE);
    ((CEdit*)GetDlgItem(IDC_EDIT_SERVER_PORT))->SetReadOnly(TRUE);

    //设置标志位
    m_bEnterRoom = true;

}

客户端进入之后, 服务端IP和服务端端口更改为只读属性,离家房间按钮和发送信息按钮,以及发送信息Edit_Control控件可以进行修改.

5.OnBnClickedButtonExit()

void CUdpClientDlg::OnBnClickedButtonExit()
{
    // TODO: 在此添加控件通知处理程序代码
    //1.获取服务端IP和端口
    CString strIp;
    UINT uiPort;
    GetDlgItemText(IDC_EDIT_SERVER_IP, strIp);
    uiPort = GetDlgItemInt(IDC_EDIT_SERVER_PORT);

    //2.发送leave消息
    CString strLeaveMsg("leave");
    int nSend = m_pClientSocket->SendTo(strLeaveMsg, strLeaveMsg.GetLength() + 100, uiPort, strIp);
    if (nSend == SOCKET_ERROR)
    {
        MessageBox(_T("退出聊天室失败"));
        return;
    }

    GetDlgItem(IDC_BUTTON_EXIT)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(FALSE);

    ((CEdit*)GetDlgItem(IDC_EDIT_SEND_MESSAGE))->SetReadOnly(TRUE);
    ((CEdit*)GetDlgItem(IDC_EDIT_SERVER_IP))->SetReadOnly(FALSE);
    ((CEdit*)GetDlgItem(IDC_EDIT_SERVER_PORT))->SetReadOnly(FALSE);

    //3.设置标志
    m_bEnterRoom = false;

}

6.OnBnClickedButtonSend()

void CUdpClientDlg::OnBnClickedButtonSend()
{
    // TODO: 在此添加控件通知处理程序代码
    //1.获取服务端的地址
    CString strIp;
    UINT uiPort;
    GetDlgItemText(IDC_EDIT_SERVER_IP, strIp);
    uiPort = GetDlgItemInt(IDC_EDIT_SERVER_PORT);

    //2.发送数据
    CString strMsg;
    GetDlgItemText(IDC_EDIT_SEND_MESSAGE, strMsg);
    m_pClientSocket->SendTo(strMsg, strMsg.GetLength() + 100, uiPort, strIp);

    //将原有的那个EDIT_control控件直接清空掉
    SetDlgItemText(IDC_EDIT_SEND_MESSAGE, _T(""));
}

7.程序关闭DestroyWindow()

BOOL CUdpClientDlg::DestroyWindow()
{
    // TODO: 在此添加专用代码或调用基类
    if (m_bEnterRoom)
    {
        OnBnClickedButtonExit();
    }
    //在销毁之前需要调用一下离开房间的操作
    return CDialogEx::DestroyWindow();
}

这步操作的主要目的,是当关闭客户端程序时,关联到服务端,如果客户端仍在房间内,则调用离开房间函数程序,还原程序操作.

总结

接下来可能会接着学习MFC的相关操作,并结合Modbus相关的串口连接助手进行调试,持续关注不迷路! 谢谢各位!

标签: udp mfc

本文转载自: https://blog.csdn.net/Hxbupaixu/article/details/126702045
版权归原作者 胡须不排序H 所有, 如有侵权,请联系我们删除。

“[MFC]MFC实现UDP客户端和服务端信息交互”的评论:

还没有评论