MFC网络通信-Udp服务端-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

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;
    CArray<ClientAddr,ClientAddr&>m_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<m_ClientAddList.GetSize();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<m_ClientAddList.GetSize();i++)
{
            ClientAddr& tempClient = m_ClientAddList.ElementAt(i);//遍历每一个
    if (tempClient.inPort == clietAddr.inPort&&tempClient.strIP.Compare(clietAddr.strIP) == 0)
   {
                break;
                //遍历找到了要移除的端口号和IP都相等的tempClientbreak
   }
  }

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

if (i<m_ClientAddList.GetSize())//如果遍历没完就说明找到了。
        {
            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();
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6