• MFC网络通信-Udp服务端


    目录

    1、UI的布局

    2、代码的实现:

    (1)、自定义的子类CServerSocket

    (2)、重写OnReceive事件

    (3)、在CUdpServerDlg类中处理

    (4)、在OnInitDialog函数中

    (5)、实现自定义函数ProcessPendingRead()处理接收到的数据

    (6)、加入新客户的消息先进性判断是否位enter

    (7)、加入的新消息如果是leave

    (8)、普通信息


    1、UI的布局

    添加一个CServerSocket类继承于CSocket

    2、代码的实现:

    (1)、自定义的子类CServerSocket

    所有的显示应该显示在框架中,在构造函数传入一个框架的指针然后进行初始化

    class CUdpServerDlg;//声明一下dlg类

    public:
        CServerSocket(CUdpServerDlg* pdlg);//所有的操作显示在dlg上面
        ~CServerSocket();
    private:
        CUdpServerDlg *m_pMainDlg;//指针来接收

    CServerSocket::CServerSocket(CUdpServerDlg* pdlg)//传递的消息统一放在对话框中处理,所以初始化传递一个对话框指针
    {    
        this->m_pMainDlg = pdlg;
    }

    (2)、重写OnReceive事件

    (父类的OnReceive是纯虚函数,如果有数据可读就会调用该方法)

    protected:
        virtual void OnReceive(int nErrorCode);//重写onReceive()方法,如果有数据可读就会调用该方法
        //在WINSOCKET父类中有这个纯虚函数来接收数据
     

    //有数据可读就会调用这个纯虚函数
    void CServerSocket::OnReceive(int nErrorCode)
    {
        CSocket::OnReceive(nErrorCode);
        /*该行代码的作用是确保底层的数据接收和处理机制正常运行,并在此基础上执行自定义的数据处理逻辑。*/
        m_pMainDlg->ProcessPendingRead();//消息处理统一放在对话框

        
    }

    (3)、在CUdpServerDlg类中处理

    struct ClientAddr//自定义结构体存放IP和端口号
    {
        CString strIP;
        UINT inPort;
    };

        CServerSocket *m_pServerSocket;
        CArraym_ClientAddList;//客户端发送的所有消息
        /*,m_ClientAddList是一个包含ClientAddr类型对象的数组,可以使用Add()方法向其中添加元素,使用GetAt()方法访问已添加的元素。这个数组被用来存储客户端连接的地址信息等数据。*/
        void ProcessPendingRead();

    (4)、在OnInitDialog函数


        m_pServerSocket = new CServerSocket(this);

    在当前对话框中需要使用到通信(SOCKET),所以在堆区创建一个CServerSocket的对象,并且还要这个对象和当前窗口相关联,然后用一个m_pServerSocket指针指向新建的对象。
        m_pServerSocket->Create(8080, SOCK_DGRAM);//用于创建一个 UDP 套接字,并将其绑定到本地 IP 地址和指定的端口号(这里是 8080)上。

    (5)、实现自定义函数ProcessPendingRead()处理接收到的数据

    初始化接收数据数组和客户端结构体对象

        TCHAR buffer[4096];//接收数据的数组
        ClientAddr clietAddr;//客户端结构体对象

    判断读取的内容的是否有效

    int nRead = m_pServerSocket->ReceiveFrom(buffer, 4096, clietAddr.strIP, clietAddr.inPort);//接收数据的字节数nRead
        //缓冲区地址,接收数据大小,客户端的IP,客户端的端口
        if (nRead == SOCKET_ERROR)//如果读出错
        {
            return;
        }

    如果内容有效字符串结尾加上\0,并且转换类型位CString类型

    buffer[nRead] = L'\0';

    //在接收到的数据后面加上结束符(索引0开始,所以nRead代表最后一位加1)
        CString strTemp(buffer);

    //char *类型的buffer转成CString类型的strTemp,代表接收到的内容。

    strTemp是接收到的消息,消息又分为三种,加入新客户,删除客户,还有就是普通消息

    (6)、加入新客户的消息先进性判断是否位enter

    if (strTemp.CompareNoCase(_T("enter"))==0)

    /*比较 strTemp 和 "enter" 是否相等,不区分大小写。如果相等,则返回 0,否则返回一个非零值*/

    在条件下我们将客户加入到列表中

    /把新的客户加入到列表中
            m_ClientAddList.Add(clietAddr);

    通知其它客户端有用户加入

    首先加入的信息需要规范一下

    CString strEnterMsg;
      strEnterMsg.Format(_T("系统消息:%s(%d)进入了房间"), clietAddr.strIP, clietAddr.inPort);

    通知其他客户端

    for (i = 0;i         {
                ClientAddr& tempClient = m_ClientAddList.ElementAt(i);//获取所有的客户端
                m_pServerSocket->SendTo(strEnterMsg, strEnterMsg.GetLength() + 1000, tempClient.inPort, tempClient.strIP);//发送消息
            }

    控件上的显示更新(人数的更新还有内容的更新)

    SetDlgItemInt(IDC_EDIT_NUMBER, m_ClientAddList.GetSize());//当前人数在文本上设置
            //之前的消息可能存在,需要拿出来放在alMsg的后面
            
            CString alMsg;
            GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, alMsg);//取消消息到alMsg
            SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, alMsg + _T("\r\n") + strEnterMsg);//将新的消息叠加到alMsg后面

    (7)、加入的新消息如果是leave

    if (strTemp.CompareNoCase(_T("leave")) == 0) 

    //离开房间

    从列表中遍历寻找要删除的用户

        //列表中移除用户
            for (i = 0;i {
                ClientAddr& tempClient = m_ClientAddList.ElementAt(i);//遍历每一个
        if (tempClient.inPort == clietAddr.inPort&&tempClient.strIP.Compare(clietAddr.strIP) == 0)
       {
                    break;
                    //遍历找到了要移除的端口号和IP都相等的tempClient,break
       }
      }

    一旦找到用户break,然后删除用户

    if (i         {
                m_ClientAddList.RemoveAt(i);//移除
            }

    格式化发送的消息

    CString strLeaveMsg;
            strLeaveMsg.Format(_T("通知消息:%s(%d)离开了房间"), clietAddr.strIP, clietAddr.inPort);//通知的内容
            

    遍历发送所有的客户端

        for (i = 0; i < m_ClientAddList.GetSize(); i++)//通知每一个客户端有用户离开了
            {
                ClientAddr& tempClient = m_ClientAddList.ElementAt(i);
                m_pServerSocket->SendTo(strLeaveMsg, strLeaveMsg.GetLength() + 1000, tempClient.inPort, tempClient.strIP);
            }

    更新控件上的内容

            SetDlgItemInt(IDC_EDIT_NUMBER, m_ClientAddList.GetSize());//更新当前人数
            CString alMsg;
            GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, alMsg);
            SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, alMsg + _T("\r\n") + strLeaveMsg);//更新消息

    (8)、普通信息

    else//普通的聊天信息
        {
            CString strMsg;
            strMsg.Format(_T("%s(%d):%s"), clietAddr.strIP, clietAddr.inPort,strTemp);//格式化普通聊天信息的内容
            
            for (i = 0; i < m_ClientAddList.GetSize(); i++)
            {    //转发所有的人消息
                ClientAddr& tempClient = m_ClientAddList.ElementAt(i);
                m_pServerSocket->SendTo(strMsg, strMsg.GetLength() + 1000, tempClient.inPort, tempClient.strIP);
            }
            
            CString alMsg;
            GetDlgItemText(IDC_EDIT_CHAT_MESSAGE, alMsg);
            SetDlgItemText(IDC_EDIT_CHAT_MESSAGE, alMsg + _T("\r\n") + strMsg);//更新聊天框的内容
        }

    (3)、优化处理

    初始化默认服务端的端口和IP在框架的入口函数

    //设置服务端默认的端口和IP
        SetDlgItemText(IDC_EDIT_SERVER_IP, L"127.0.0.1");
        SetDlgItemText(IDC_EDIT_SERVER_PORT, L"8080");

    初始化按钮的一些状态

    没有加入房间,发送按钮和退出按钮都不能点击

    //设置按钮初始化状态
        GetDlgItem(IDC_BUTTON_OUT)->EnableWindow(FALSE);
        GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(FALSE);

    初始化发送消息的EDIT只读,服务端IP和PORT可以修改

    GetDlgItem(IDC_EDIT_SEND_MESSAGE)->EnableWindow(FALSE);
        GetDlgItem(IDC_EDIT_SERVER_IP)->EnableWindow(TRUE);
        GetDlgItem(IDC_EDIT_SERVER_PORT)->EnableWindow(TRUE);

    加入房间之后重新设置按钮的状态

    //设置按钮文本框的状态
        //设置按钮初始化状态
        GetDlgItem(IDC_BUTTON_OUT)->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);

    离开房间重新设置按钮的状态

    GetDlgItem(IDC_BUTTON_OUT)->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);

    重写框架类的关闭窗口函数

    实现关闭窗口之后调用离开房间按钮事件。


    BOOL CUdpClientDlg::DestroyWindow()
    {
        // TODO: 在此添加专用代码和/或调用基类
        if (m_bEnterRoom)
        {
            OnBnClickedButtonOut();
        }

        return CDialogEx::DestroyWindow();
    }

  • 相关阅读:
    PWM杂项
    python代码:VOC to cityscapes标注文件转换
    带支付的客服系统2.0源码|多语言客服|Saas客服|多商户
    Laravel 6 - 第十五章 验证器
    使用canvas给图片添加水印
    windows下app爬虫环境搭建:python + fiddler + Appium + 夜神模拟器
    兆易创新GD32 (四)FreeRTOS 移植 与 CMSIS OS2
    Himall验证帮助类判断当前时间是否在指定的时间段内、判断一个ip是否在另一个ip内
    倍福使用AdsRemote组件实现和C#的ADS通讯
    14:00面试,14:06就出来了,问的问题有点变态。。。
  • 原文地址:https://blog.csdn.net/qq_59328991/article/details/134133175