作为一个大学汪,课多,作业也还真是多啊(( >﹏<。)),这不又来了一次MFC小作业(老师们是有多爱MFC。。),作业要求我们用MFC实现连接数据库和网络socket通信功能,本来是分开来的,我把它们弄到一起了,这回的小程序中用到的数据库的功能比较简单,就只是运用到聊天记录中而已,如果各位同学朋友感兴趣自己再摸索探究吧~
此次小程序的主要功能及知识点:ODBC连接MySql数据库、利用MFC的CSocket类实现网络通信,程序运行界面如下:
服务器、客户端之间可以任意发送消息
客户端拥有简单的聊天记录
于是乎,我滚过来和大家讲解下大概的思路和代码
1、网络通信
在MFC中要实现网络通信,不需要特地去配置相应的开发环境,只需要使用一个名为CSocket的类就可以实现
什么是Socket?
Socket也成为套接字,套接字是支持TCP/IP网络通信的基本操作单元。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。
简单说来就是想要实现网络通信功能,首先需要一个服务器提供网络服务,其次需要若干个客户端来与服务器交换信息,客户端与客户端之间的通信要先发送消息给服务器,然后经过服务器转发消息,这样对方客户端才能收到消息;而服务器可以直接发送消息给任意一个连上它的客户端。点击F12或者去MSDN可以查看CSocket类的构成以及核心方法,它的核心方法有OnAccept、Send等。
我们首先新建一个基于对话框(或者单文档都可以)的服务器工程,新建一个类,这里就起名为CServerSocket,继承自CSocket,这样CServerSocket就具备了网络通信能力,但是还得手动去使用和重写它的方法。
ServerSocket.h
#pragma once
#include "afxsock.h"
#include "lineDlg.h"
class ClineDlg;
class CServerSocket :
public CSocket
{
public:
CServerSocket();
//服务器的对话框
ClineDlg *m_pDlg;
virtual ~CServerSocket();
CString m_userName;
//重写socket的回调
void OnAccept(int nErrorCode);
//重写socket的回调
void OnClose(int nErrorCode);
//重写socket的回调
void OnReceive(int nErrorCode);
};
ServerSocket.cpp
#include "stdafx.h"
#include "ServerSocket.h"
CServerSocket::CServerSocket()
{
}
CServerSocket::~CServerSocket()
{
}
//添加上线用户
void CServerSocket::OnAccept(int nErrorCode)
{
//AddClient是自己在主对话框的类中写的方法,用来新建与客户端的连接
m_pDlg->AddClient();
CSocket::OnAccept(nErrorCode);
}
// 删除下线用户
void CServerSocket::OnClose(int nErrorCode)
{
//RemoveClient是自己在主对话框的类中写的方法,用来删除下线用户
m_pDlg->RemoveClient(this);
CSocket::OnClose(nErrorCode);
}
// 接收数据
void CServerSocket::OnReceive(int nErrorCode)
{
//RecvData是自己在主对话框的类中写的方法,用来接收数据
m_pDlg->RecvData(this);
CSocket::OnReceive(nErrorCode);
}
需要一提的是,回调函数不需要我们手动执行,它会在特定场合自动触发,大家如果没接触过回调函数相关知识的可以把回调理解成游戏中的被动技能,也就是当某些条件成熟时自动触发。重写CSocket的回调函数可以直接在代码上写上,也可以转到类视图,点击属性,找到顶部的“重写”,然后找到需要重写的函数,点击小三角然后点击<Add> 函数名 即可:
接下来我们在主对话框的类中添加方法,在这里主要讲解接收和发送,由于服务器接收消息后有两种情况,一种是更新聊天室成员列表,一种是更新聊天窗口的聊天消息,所以在这里我在客户端中定义发送消息的规则为发送消息之前将要发送消息的字符串进行打包(加首部),当消息传送到服务器端的时候服务器解包(去首部)就可以得到消息,然后再将消息广播给自己和所有的客户端,比如客户端发送过来的消息是“小白你好,我是007,我们交个朋友吧!”,先将这条消息打包加首部,就变成了“M小白 小白你好,我是007,我们交个朋友吧!”,这里增加了一个“M”,一个对方客户端的名称,还有一个空格,当服务器接收到消息的时候就可以根据自己自定义的规则进行对字符串的拆分从而得到消息,其他的同理。
服务器接收的消息的大小可以自己定义(比如1024),CSocket发送接收消息是以一个个char数据类型的宽字节为单位的,一般是需要将多字节转换成宽字节以便发送和接收,类似于这种
服务器接收消息:
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中的方法,用于服务器接收消息
if (pSocket->Receive(pData, 1024, 0) != SOCKET_ERROR)
{
str = pData;
translateMsg(str, pSocket);
}
delete pData;
pData = NULL;
}
然后发送的时候需要宽字节转多字节并且打包:
//服务器发送消息给指定的一个客户端
void ClineDlg::SendMSGToOne(CString str,CString userSendName)
{
CString userName;
bool flag = false;
CServerSocket* pTemp = NULL;
CServerSocket* pSendUser = NULL;
int cone = str.Find(_T(" "));
userName = str.Left(cone);
str = str.Right(str.GetLength() - cone);
str = userSendName + _T("对你说:") + str;
int n;
//宽字节转多字节
n = WideCharToMultiByte(CP_OEMCP, 0, str, -1, NULL, 0, 0, FALSE);
char *pSend = new char[n];
memset(pSend, 0, n*sizeof(char));
//宽字节转多字节
WideCharToMultiByte(CP_OEMCP, 0, str.GetBuffer(0), n, pSend, n , 0, FALSE);
POSITION nPos = m_clientList.GetHeadPosition();
//查找目标客户端
while (nPos)
{
pTemp = (CServerSocket*)m_clientList.GetNext(nPos);
//如果找到了目标客户端(接收方)
if (userName == pTemp->m_userName)
{
flag = true;
pTemp->Send(pSend, n);
}
//如果找到了发送方
if (userSendName == pTemp->m_userName)
{
pSendUser = pTemp;
}
}
//如果没有找到发送方,则默认是服务器给一个指定的客户端发送消息
if (!flag)
{
pSendUser->Send(m_strNoUser, strlen(m_strNoUser));
}
delete pSend;
}
服务器的代码框架搭得差不多的时候,就可以去写客户端了,客户端同样也需要一个继承自CSocket的类,只需要重写接收的方法就可以了,详细代码在文章后可以在源工程中看到。
2、ODBC连接MySql数据库
C++中调用MySql并且执行Sql语句相对于用ODBC数据源连接MySql然后执行内置的函数会复杂得多,而且容易出错,因此这里选择使用ODBC连接并且使用MySql数据库。
想要使用ODBC连接MySql数据库,首先确保电脑上得安装有MySql数据库,如果安装MySql数据库的时候选择的是完全安装,那么就省去了安装MySql的ODBC驱动的麻烦,否则还得去网上搜一个MySql的ODBC驱动来安装。然后我们可以使用Navicat fot MySql这个可视化工具来新建一个数据库,比如这里我新建了一个服务器名为“dataBase”,在这个服务器中新建了一个数据库名为“oda”,在这个数据库中新建了一个表名为“history”,这个表就是存放各个客户端的历史记录的表,不需要主键(因为会冲突)。
建好表之后,我们回到VS,找到“服务器资源管理器”:
添加连接:
选择ODBC数据源:
指定数据源:
这时候可能会提醒一句,如果没法继续的话,就得退出VS,然后右键VS->以管理员的身份运行,如果可以继续,则可以无视这个警告
接下来:
找到MySQL ODBC Driver,选上,如果没有MySQL ODBC Driver,说明电脑上的MySql的ODBC驱动没装好,按照上面所说的去搜索一个来安装吧:
点击“下一步”然后到“完成”:
点击测试,如果出现连接成功,这样配置数据源就算完毕了。
在VS中添加如下代码:
//DSN就是自己新建的数据源的名称,UID就是数据库的用户名,PWD就是数据库的密码
m_dataBase.Open(NULL,
false,
false,
_T("ODBC;server=127.0.0.1;DSN=source1;UID=root;PWD=123456")
);
if (!m_dataBase.IsOpen())
{
AfxMessageBox(_T("DB open failed!"));
return false;
}
如果这段代码运行起来没出什么错,那么恭喜你,连接数据库成功了,接下来就可以对数据库表中的内容进行增删改查了,查的时候可以添加过滤条件,例如客户端中“显示聊天记录”的按钮的响应函数:
void ClineClientDlg::OnBnClickedButtonShowHistory()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
//查询数据库
m_historyStr = _T("");
CRecordset record(&m_dataBase);
int count = 0;
CString str2;
CString userNameFilter;
//record.m_strFilter = _T("name='小白'");
//设置过滤条件
userNameFilter.Format(_T("name='%s'"),m_userName);
record.m_strFilter = userNameFilter;
//my_record.m_strSort = _T("cust_id desc");
record.Open(CRecordset::snapshot , _T("select * from history"));
record.MoveFirst();
//如果数据库为空,则返回
//int num = record.GetODBCFieldCount();
if(record.IsBOF())
{
return ;
}
CDBVariant varValue;
//获得表中全部数据
while(!record.IsEOF())
{
record.GetFieldValue(2, str2);
m_historyStr += str2;
m_historyStr +=_T("\r\n");
record.GetFieldValue((short)0, str2);
m_historyStr += str2;
record.GetFieldValue(1, str2);
m_historyStr += str2;
m_historyStr +=_T("\r\n");
record.MoveNext();
count++;
}
UpdateData(FALSE);
}
好了,整个程序的大致代码流程就是这样了,详细的请看源工程的代码(使用聊天记录的功能请更改代码把用户名和密码换成自己电脑上的数据库的用户名和密码,同时数据源的连接也要自己新建):
欢迎大家来交流和探讨~