MFC中基于TCP/UDP协议的网络聊天室(ODBC链接MySQL、基于对话框、简易网络聊天室、TCP/UDP协议)
前言
这个代码是MFC中基于TCP/UDP协议的简易网络聊天室,主要实现的功能是不同客户端之间可以互相发送消息, 而且所有消息都存储在数据库中,客户端用户可以对自己所发送过的消息进行查询。服务器可以对客户端进行相应的管理。程序链接了MySQL数据库,VS和数据的链接这块儿不在赘述。
程序代码链接 https://download.csdn.net/download/eq_cyc/11966503
环境说明
编译环境:VS2012
数据库:MySQL
核心代码
登录界面
这块儿是一个简单的登录界面,UDP协议和TCP协议的基本相同,主要算法在三次登录和链接数据库。先上图:
登录代码:
void dlg::OnBnClickedlogin()
{
GetDlgItem(IDC_EDIT1)->GetWindowText(user1);
GetDlgItem(IDC_EDIT2)->GetWindowText(password);
//将两个控件里的值作为数据库查询条件
if(user1.IsEmpty()||password.IsEmpty())
{
AfxMessageBox(_T("用户名或密码不能为空!"));
return;
}
//进行数据库查询,user和password一致则执行登录操作
UpdateData();
m_num++;
CString strSQL;
strSQL.Format(_T("select * from serverlogin where name ='%s' and password = '%s'"),user1,password);
if(!recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
{
MessageBox(_T("打开数据库失败!"),_T("数据库错误"),MB_OK);
return ;
}
if(recordset.GetRecordCount()==0)
{
if(m_num == 3)
{
MessageBox(_T("密码错误3次,自动退出!"));
exit(0);
}
else{
recordset.Close();
MessageBox(_T("密码错误,请重新输入!"));
password="";
UpdateData(FALSE);
}
}
else{
//如果组合框控件选择的是TCP_Client,则登录到页面1***********************************
if (m_cb.GetCurSel()==0)
{
MessageBox(_T("登录成功TCP_Server!"));
recordset.Close();
m_dataBase.Close();
CDialog::OnOK();
ClineDlg dlg;
theApp.m_pMainWnd = &dlg;
dlg.DoModal();
} //如果组合框控件选择的是UDP_Client,则登录到页面2***********************************
else if(m_cb.GetCurSel()==1)
{
MessageBox(_T("登录成功UDP_Server!"));
recordset.Close();
m_dataBase.Close();
//关闭登录窗口
CDialog::OnOK();
UDPDlg dlg;
theApp.m_pMainWnd = &dlg;
dlg.DoModal();
}
}
}
数据库配置
注册界面
TCP服务端
TCP协议服务器端这一套必须写全了:
1.Creat()Create 方法已经包含了 Bind 方法,如果是以 Create 方法创建socket的前提下不能再调用 Bind ,要不一定出错。
2.Listen()
3.Accept()
新建一个类,继承CSocket类,类名为CServerSocket,
CServerSocket核心代码:
//添加上线用户,套接字接受到连接请求时触发该事件
//以通知侦听套接字它可以通过调用accept成员函数接受挂起的连接请求。
void CServerSocket::OnAccept(int nErrorCode)
{
m_pDlg->AddClient(); //调用对话框类的AddClient方法:添加新的连接用户
CSocket::OnAccept(nErrorCode);
}
// 删除下线用户,套接字关闭时触发该事件
void CServerSocket::OnClose(int nErrorCode)
{
m_pDlg->RemoveClient(this); //RemoveClient:移除客户端
CSocket::OnClose(nErrorCode);
}
// 接收数据,,套接字上有数据被接受时触发该事件
void CServerSocket::OnReceive(int nErrorCode)
{
m_pDlg->RecvData(this); //调用对话框类的RecvData方法:接受客户端发来的消息
CSocket::OnReceive(nErrorCode);
}
void CServerSocket::OnSend(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnSend(nErrorCode);
}
创建服务器端的套接字Creat(),并且监听Listen(),打开服务器
void ClineDlg::startServer()
{
CString cport;
//获取服务器的端口号
GetDlgItemText(IDC_SERVERPORT,cport);
//端口号的字符串转换为数字
int port = _ttoi(cport);
//如果没有填写端口号,则默认的端口号为8080
if (port == 0)
{
m_port = 8080;
}
else
{
m_port = port;
}
//如果当前服务器的状态为开启,则关闭服务器m_connect初始为false
if (m_connect)
{
//关闭服务器的socket
delete m_listenSocket;
m_listenSocket = NULL;
m_connect = false;
SetDlgItemText(IDC_BN_SERVER_START, _T("打开服务器"));
UpdateEvent(_T("系统关闭服务器."));
//关闭控件的只读状态
m_editPort.SetReadOnly(FALSE);
//设置“发送”按钮为不可用
m_buttonSend.EnableWindow(FALSE);
return;
}
//否则就打开服务器
m_listenSocket = new CServerSocket();
m_listenSocket->m_pDlg = this;
m_listenSocket->m_userName = _T("TCP服务器");
// 指定对话框为主对话框,不能少了这句
UpdateData(true);
// 创建服务器的套接字,IP地址默认本机IP
if (!m_listenSocket->Create(m_port)) //bind绑定端口
{
AfxMessageBox(_T("创建套接字错误!"));
m_listenSocket->Close();
return;
}
if (!m_listenSocket->Listen())
{
AfxMessageBox(_T("监听失败!"));
m_listenSocket->Close();
return;
}
m_connect = true;
SetDlgItemText(IDC_BN_SERVER_START, _T("关闭服务器"));
//更新聊天窗口中的消息
UpdateEvent(_T("系统打开服务器."));
//将服务器端口号的控件设置为只读状态
if(port == 0)
{
m_editPort.SetWindowTextW(_T("8080"));
}
else
{
m_editPort.SetWindowTextW(cport);
}
m_editPort.SetReadOnly(TRUE);
//设置“发送”按钮为可用
m_buttonSend.EnableWindow(TRUE);
}
接收由客户端发来的消息
void ClineDlg::RecvData(CServerSocket* pSocket)
{
char* pData = NULL;
pData = new char[1024];
memset(pData, 0, sizeof(char)* 1024);
UCHAR leng = 0;
CString str;
//Receive为CSocket中的方法,用于服务器接收消息
//pData接受数据的缓冲区
//1024:缓冲区的长度
//0:函数调用模式
if (pSocket->Receive(pData,1024,0) != SOCKET_ERROR)
{
str = pData;
//接受消息后可以将消息转发给所有客户端或者指定客户端translateMsg()转发
translateMsg(str, pSocket);
}
delete[] pData;
pData = NULL;
}
添加新的客户端,调用Accept()函数
void ClineDlg::AddClient()
{
CServerSocket *pSocket = new CServerSocket; //创建一个套接字
pSocket->m_pDlg = this;
m_listenSocket->Accept(*pSocket); //接受客户端套接字连接
//CAsyncSocket::AsyncSelect调用此成员函数以请求套接字的事件通知。
pSocket->AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE);
//AddTail将新元素的列表添加到此列表的结尾。
m_clientList.AddTail(pSocket); //将套接字添加到列表容器中
m_userCount = m_clientList.GetCount();
UpdateData(false);
}
给客户端发送消息:
void ClineDlg::SendMSG()
{
CString msg;
//获取消息框中的字符串
GetDlgItemText(IDC_EDITMSG, msg);
//获取当前选中的用户的下标(比如选中了某个人或者选中了“所有人”)
int selectIndex = m_users.GetCurSel();
if(selectIndex == -1)
{
MessageBox(_T("请在聊天室中选择发送对象!"));
return ;
}
if(msg == _T(""))
{
MessageBox(_T("请输入要发送的内容!"));
return ;
}
CString usermsg;
//获取该下标的字符串名称
m_users.GetText(selectIndex, usermsg);
if (usermsg == "所有人")
{
usermsg = _T("MEVERY") + msg;
}
else
{
usermsg = _T("M") + usermsg + _T(" ") + msg;
}
translateMsg(usermsg, this->m_listenSocket);
}
TCP客户端
TCP协议的客户端代码,重点是创建套接字Creat(),和连接服务器Connect()。然后利用Send()函数给服务器发送消息,利用Receive函数来接受消息,并且将接受到的信息进行拆分,分别在聊天室列表和聊天窗口进行显示。接受到了服务器端发送的消息后,同时会将消息进行存储。数据存储在MySQL数据库中,显示历史聊天记录是对数据库进行查询的一个操作,按用户名进行查询,将查询出来的结果显示在聊天记录窗口中。
下面是核心代码,进行讲解:
连接服务器,会进行客户端套接字的创建和连接工作。
void ClineClientDlg::ConnectServer()
{
GetDlgItemText(IDC_USERNAME, m_userName);
//如果已经连接,则断开服务器
if (m_connect)
{
//关闭并且删除客户端的socket
m_connect = false;
m_pSock->Close();
delete m_pSock; //删除套接字
m_pSock = NULL;
m_ConPC.SetWindowTextW(_T("连接服务器"));
m_buttonSend.EnableWindow(FALSE);
m_buttonShowHistory.EnableWindow(FALSE);
UpdateData(false);
return;
}
//否则连接服务器
else
{
//在这里创建新的socket
m_pSock = new CClientSocket();
//创建套接字
if (!m_pSock->Create())
{
AfxMessageBox(_T("创建套接字失败!"));
return;
}
}
//获得ip控件的ip
BYTE f1, f2, f3, f4;
((CIPAddressCtrl*)GetDlgItem(IDC_SERVERIPADDRESS))->GetAddress(f1, f2, f3, f4);
m_strServerip.Format(_T("%d.%d.%d.%d"), f1, f2, f3, f4);
m_port = GetDlgItemInt(IDC_SERVERPORT);
m_strServerip = _T("127.0.0.1");
//TCP服务器为8080
//用m_strServerip和m_port来验证是否成功连接到服务器
if (!m_pSock->Connect(m_strServerip, m_port))
{
AfxMessageBox(_T("连接服务器失败!"));
return;
}
else
{
AfxMessageBox(_T("连接服务器成功!"));
//成功连接上了服务器!!
m_connect = true;
int n = 0;
CString userName;
userName = _T("MUSERNAME") + m_userName;
//宽字节转多字节,确定要发送的字符串将占据多少个宽度的char
n = WideCharToMultiByte(CP_OEMCP, 0, userName, -1, NULL, 0, 0, FALSE);
char* pBuff = new char[n];
memset(pBuff, 0, n);
//宽字节转多字节,将要发送的字符串转化为字节流
WideCharToMultiByte(CP_OEMCP, 0, userName.GetBuffer(0), n, pBuff, n, 0, FALSE);
//通过socket发送多字节流连接
m_pSock->SendMSG(pBuff, n);
delete[] pBuff;
m_ConPC.SetWindowTextW(_T("断开服务器"));
m_buttonSend.EnableWindow(TRUE);
m_buttonShowHistory.EnableWindow(TRUE);
UpdateData(false);
}
}
给服务器发送消息:
void ClineClientDlg::SendMsg()
{
CString msg;
//获得消息框里的字符串
GetDlgItemText(IDC_EDIT_MSG, msg);
int selectIndex = m_userslist.GetCurSel();
if(selectIndex == -1)
{
MessageBox(_T("请在聊天室中选择发送对象!"));
return ;
}
if(msg == _T(""))
{
MessageBox(_T("请输入要发送的消息内容!"));
return ;
}
CString usermsg;
m_userslist.GetText(selectIndex, usermsg);
if (usermsg == "所有人")
{
usermsg = _T("MEVERY") + msg;
}
else
{
usermsg = _T("M") + usermsg + _T(" ") + msg;
}
if (!usermsg.IsEmpty())
{
//未连接服务器则不执行
if (!m_connect)
{
return;
}
UpdateData(true);
GetDlgItemText(IDC_EDIT_MSG, m_DataSend);
if (m_DataSend != "")
{
int n = 0;
m_DataSend = usermsg;
//通过将宽字节转换成多字节来获得多字节的位数
n = WideCharToMultiByte(CP_OEMCP, 0, m_DataSend, -1, NULL, 0, 0, FALSE);
char* pBuff = new char[n];
//初始化数组
memset(pBuff, 0, n);
//将宽字节转换成多字节
WideCharToMultiByte(CP_OEMCP, 0, m_DataSend.GetBuffer(0), n, pBuff, n, 0, FALSE);
//发送消息
m_pSock->SendMSG(pBuff, n);
//2019-10-26
delete[] pBuff;
}
}
}
每更改一次聊天窗口的内容,就更新数据库对应的聊天记录
void ClineClientDlg::updateDataBase(CString newStr)
{
if(m_pSock == NULL)
{
return ;
}
CTime curTime;
curTime = CTime::GetCurrentTime(); //获取系统时间
CString strCurTime;
strCurTime=curTime.Format(_T("%Y-%m-%d %H:%M:%S"));
CString str;
str.Format(_T("insert history value('%s','%s','%s')"),m_userName,newStr,strCurTime);
m_dataBase.ExecuteSQL(str);
}
更新聊天室列表
void ClineClientDlg::UpdateUsers(CString str, int index)
{
str = str.Right(str.GetLength()-strlen("MUPDATEUSERLIST"));
CStringArray *strArray = translateUsersStr(str);
int size = strArray->GetSize();
m_userslist.ResetContent();
m_userslist.AddString(_T("所有人"));
for (int i = 0; i < size; i++)
{
m_userslist.AddString(strArray->GetAt(i));
}
}
UDP服务端
UDP与TCP最大的区别是UDP是无连接的网络协议 ,因此每次发送消息的时候只需要对方的IP和端口号就可以发送过去了。UDP服务器需要创建套接字,但是并不需要监听和连接,因此UDP服务器狭义上来讲也可以是一个客户端,它主要是进行一个消息转发的功能。所有的客户端同时给这个端口为8080(8080可以自己设置)的服务器发送消息,服务器收到消息后进行转发
核心代码:
打开服务器:进行套接字的创建Create(m_port,SOCK_DGRAM,NULL),类型为SOCK_DGRAM数据报格式。
void UDPDlg::OnBnClickedBnServerStart()
{
CString cport;
//获取服务器的端口号
GetDlgItemText(IDC_SERVERPORT,cport);
//端口号的字符串转换为数字
int port = _ttoi(cport);
//如果没有填写端口号,则默认的端口号为8080
if (port == 0)
{
m_port = 8080;
}
else
{
m_port = port;
}
//如果当前服务器的状态为开启,则关闭服务器,,,m_connect初始为false
if (m_connect)
{
//关闭服务器的socket
delete m_pServerSocket;
m_pServerSocket = NULL;
m_connect = false;
SetDlgItemText(IDC_BN_SERVER_START, _T("打开服务器"));
UpdateEvent(_T("系统关闭服务器."));
//关闭控件的只读状态
m_editPort.SetReadOnly(FALSE);
//设置“发送”按钮为不可用
m_buttonSend.EnableWindow(FALSE);
return;
}
//否则就打开服务器
// 创建服务器的套接字,IP地址默认本机IP
m_pServerSocket = new UDPServerSocket();
m_pServerSocket->m_pdlg = this;
m_pServerSocket->m_userName = _T("UDP服务器");
UpdateData(true);
m_pServerSocket->Create(m_port,SOCK_DGRAM,NULL); //创建本地套接口
m_connect = true;
SetDlgItemText(IDC_BN_SERVER_START, _T("关闭服务器"));
//更新聊天窗口中的消息
UpdateEvent(_T("系统打开服务器."));
if(port == 0)
{
m_editPort.SetWindowTextW(_T("8080"));
}
else
{
m_editPort.SetWindowTextW(cport);
}
//将服务器端口号的控件设置为只读状态
m_editPort.SetReadOnly(TRUE);
//设置“发送”按钮为可用
m_buttonSend.EnableWindow(TRUE);
}
接受客户端的连接,并且对接受到的数据进行处理。用户名添加在聊天室列表中,消息更新在聊天窗口。UDP协议的接受用的是ReceiveFrom(这块儿也不同与TCP的Receive),发送消息用的是SendTo(不同于TCP协议的Send)
void UDPDlg::OnBnClickedSendmsg()
{
CString msg;
//获取消息框中的字符串
GetDlgItemText(IDC_EDITMSG, msg);
//获取当前选中的用户的下标(比如选中了某个人或者选中了“所有人”)
int selectIndex = m_users.GetCurSel();
if(selectIndex == -1)
{
MessageBox(_T("请在聊天室中选择发送对象!"));
return ;
}
if(msg == _T(""))
{
MessageBox(_T("请输入要发送的内容!"));
return ;
}
CString usermsg;
//获取该下标的字符串名称
m_users.GetText(selectIndex, usermsg);
if (usermsg == "所有人")
{
usermsg = _T("MEVERY") + msg;
}
else
{
usermsg = _T("M") + usermsg + _T(" ") + msg;
}
translateMsg(usermsg,this->m_pServerSocket, m_ClientAddr);
//发送消息
}
void UDPDlg::RecvData(UDPServerSocket* pSocket)
{
CString str;
TCHAR buff[4096]; //定义接受数据的缓冲区
int nRead;
ClientAddr sendServerAddr;
nRead = pSocket -> ReceiveFrom(buff,4096,sendServerAddr.strIPAddr,sendServerAddr.uiPort);
CString tmpstr = buff;
if (tmpstr.Left(9) == _T("MUSERNAME"))
{
//保存用户Socket,以便后来发消息时使用
m_clientList.AddTail(pSocket);
tmpstr = tmpstr.Mid(9);//去掉前缀"MUSERNAME",取出新登录的用户名
sendServerAddr.strUserName = tmpstr;
m_ClientAddrList.Add(sendServerAddr);
}
else
{
for (int i = 0; i < m_ClientAddrList.GetSize(); i++)
{
ClientAddr& tempClient = m_ClientAddrList.ElementAt(i);
if (tempClient.strIPAddr==sendServerAddr.strIPAddr && tempClient.uiPort == sendServerAddr.uiPort)
{
sendServerAddr.strUserName = tempClient.strUserName;
break;
}
}
}
if (nRead != SOCKET_ERROR)
{
str=buff;
translateMsg(str, pSocket, sendServerAddr);
}
if(nRead == SOCKET_ERROR){
return;
}
}
其余部分都和TCP协议的服务器端代码类似,这块儿不在给出。
UDP客户端
UDP客户端和服务端的代码类似,也是进行套接字的创建,消息的发送。具体代码如下:
连接服务器
void UDPClientDlg::OnBnClickedLnetoserver()
{
//获得ip控件的ip
BYTE f1, f2, f3, f4;
((CIPAddressCtrl*)GetDlgItem(IDC_SERVERIPADDRESS))->GetAddress(f1, f2, f3, f4);
m_strServerip.Format(_T("%d.%d.%d.%d"), f1, f2, f3, f4);
m_port = GetDlgItemInt(IDC_SERVERPORT);
GetDlgItemText(IDC_USERNAME, m_userName);
//如果已经连接,则断开服务器
if (m_connect)
{
//关闭并且删除客户端的socket
m_connect = false;
m_pClientSock->Close();
delete m_pClientSock; //删除套接字
m_pClientSock = NULL;
m_ConPC.SetWindowTextW(_T("连接服务器"));
m_buttonSend.EnableWindow(FALSE);
m_buttonShowHistory.EnableWindow(FALSE);
UpdateData(false);
return;
}
//否则连接服务器
else
{
CString m1,m2,m3;
GetDlgItemText(IDC_SERVERIPADDRESS,m1);
GetDlgItemText(IDC_USERNAME,m2);
GetDlgItemText(IDC_SERVERPORT,m3);
if (m1.IsEmpty()||m2.IsEmpty()||m3.IsEmpty())
{
AfxMessageBox(_T("请输入IP和Port"));
}
else
{
//创建套接字
m_pClientSock = new UDPClientSocket(this); //初始化套接字的成员变量
//创建套接字
if (!m_pClientSock -> Create(m_port,SOCK_DGRAM)) //创建套接字,SOCK_DGRAM:数据报套接字
{
AfxMessageBox(_T("创建套接字失败!"));
return;
}
AfxMessageBox(_T("创建套接字成功!!!"));
SocketAddr myAddr;
AfxMessageBox(_T("连接服务器成功!"));
//成功连接上了服务器!!
m_connect = true;
UpdateData(TRUE);
//*******************************************************************************************
//通过socket发送数据报连接
CString userName;
userName = _T("MUSERNAME") + m_userName;
m_pClientSock->SendTo((LPCTSTR)userName, userName.GetLength()*8, m_ServerAddr.uiPort, m_ServerAddr.strIPAddr);
//*******************************************************************************************
m_ConPC.SetWindowText(_T("断开服务器"));
m_buttonSend.EnableWindow(TRUE);
m_buttonShowHistory.EnableWindow(TRUE);
UpdateData(false);
}
}
}
发送消息
void UDPClientDlg::OnBnClickedSendmsg()
{
CString msg;
//获得消息框里的字符串
GetDlgItemText(IDC_EDIT_MSG, msg);
int selectIndex = m_userslist.GetCurSel();
if(selectIndex == -1)
{
MessageBox(_T("请在聊天室中选择发送对象!"));
return ;
}
if(msg == _T(""))
{
MessageBox(_T("请输入要发送的消息内容!"));
return ;
}
CString usermsg;
m_userslist.GetText(selectIndex, usermsg);
if (usermsg == "所有人")
{
usermsg = _T("MEVERY") + msg;
}
else
{
usermsg = _T("M") + usermsg + _T(" ") + msg;
}
if (!usermsg.IsEmpty())
{
UpdateData(true);
GetDlgItemText(IDC_EDIT_MSG, m_DataSend);
if (m_DataSend != "")
{
int n = 0;
m_DataSend = usermsg;
//通过将宽字节转换成多字节来获得多字节的位数
n = WideCharToMultiByte(CP_OEMCP, 0, m_DataSend, -1, NULL, 0, 0, FALSE);
char* pBuff = new char[n];
//初始化数组
memset(pBuff, 0, n);
//将宽字节转换成多字节
WideCharToMultiByte(CP_OEMCP, 0, m_DataSend.GetBuffer(0), n, pBuff, n, 0, FALSE);
//发送消息
m_pClientSock->SendTo(
(LPCTSTR)m_DataSend,
m_DataSend.GetLength()*8,
m_ServerAddr.uiPort,
m_ServerAddr.strIPAddr);
delete[] pBuff;
}
}
}
接受消息
void UDPClientDlg::ReceiveText()
{
TCHAR buff[4096]; //定义接受数据的缓冲区
int nRead;
SocketAddr sendClientAddr;
nRead = m_pClientSock -> ReceiveFrom(buff,4096,sendClientAddr.strIPAddr,sendClientAddr.uiPort);
if(nRead == SOCKET_ERROR){
return;
}
buff[nRead] = _T('\0'); //设置结束标志
CString str(buff);
//如果服务器通知更新聊天室列表
if (-1 != strUpdate(str))
{
((UDPClientDlg *)AfxGetMainWnd())->UpdateUsers(str, strUpdate(str));
}
//否则就更新聊天窗口内容
else
{
//向聊天窗口添加接受到的信息
CStringW strTemp(buff);
CStringW chartMsg;
this->GetDlgItemTextW(IDC_EDIT_RECIEVEMSG,chartMsg);
chartMsg += strTemp + _T("\r\n");
this->SetDlgItemTextW(IDC_EDIT_RECIEVEMSG,chartMsg);
}
}
运行结果
TCP协议运行结果
UDP运行结果
数据库数据截图
总结
第一次写博文,难免有错误之处,忘批评指正。本文TCP协议部分代码借鉴了MFC小程序 简易网络聊天室 博文,共同学习进步。