0


MFC网络通信-Udp服务端

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都相等的tempClient,break
}
}

一旦找到用户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();

}

标签: mfc c++

本文转载自: https://blog.csdn.net/qq_59328991/article/details/134133175
版权归原作者 1天道酬勤1 所有, 如有侵权,请联系我们删除。

“MFC网络通信-Udp服务端”的评论:

还没有评论