目录
1、UI的布局
2、代码的实现:
(1)、自定义的子类CServerSocket
(2)、重写OnReceive事件
(3)、在CUdpServerDlg类中处理
(4)、在OnInitDialog函数中
(5)、实现自定义函数ProcessPendingRead()处理接收到的数据
(6)、加入新客户的消息先进性判断是否位enter
(7)、加入的新消息如果是leave
(8)、普通信息
添加一个CServerSocket类继承于CSocket
所有的显示应该显示在框架中,在构造函数传入一个框架的指针然后进行初始化
class CUdpServerDlg;//声明一下dlg类
public:
CServerSocket(CUdpServerDlg* pdlg);//所有的操作显示在dlg上面
~CServerSocket();
private:
CUdpServerDlg *m_pMainDlg;//指针来接收
CServerSocket::CServerSocket(CUdpServerDlg* pdlg)//传递的消息统一放在对话框中处理,所以初始化传递一个对话框指针
{
this->m_pMainDlg = pdlg;
}
(父类的OnReceive是纯虚函数,如果有数据可读就会调用该方法)
protected:
virtual void OnReceive(int nErrorCode);//重写onReceive()方法,如果有数据可读就会调用该方法
//在WINSOCKET父类中有这个纯虚函数来接收数据
//有数据可读就会调用这个纯虚函数
void CServerSocket::OnReceive(int nErrorCode)
{
CSocket::OnReceive(nErrorCode);
/*该行代码的作用是确保底层的数据接收和处理机制正常运行,并在此基础上执行自定义的数据处理逻辑。*/
m_pMainDlg->ProcessPendingRead();//消息处理统一放在对话框
}
struct ClientAddr//自定义结构体存放IP和端口号
{
CString strIP;
UINT inPort;
};
CServerSocket *m_pServerSocket;
CArraym_ClientAddList;//客户端发送的所有消息
/*,m_ClientAddList是一个包含ClientAddr类型对象的数组,可以使用Add()方法向其中添加元素,使用GetAt()方法访问已添加的元素。这个数组被用来存储客户端连接的地址信息等数据。*/
void ProcessPendingRead();
m_pServerSocket = new CServerSocket(this);在当前对话框中需要使用到通信(SOCKET),所以在堆区创建一个CServerSocket的对象,并且还要这个对象和当前窗口相关联,然后用一个m_pServerSocket指针指向新建的对象。
m_pServerSocket->Create(8080, SOCK_DGRAM);//用于创建一个 UDP 套接字,并将其绑定到本地 IP 地址和指定的端口号(这里是 8080)上。
初始化接收数据数组和客户端结构体对象
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是接收到的消息,消息又分为三种,加入新客户,删除客户,还有就是普通消息
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后面
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);//更新消息
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();
}