一 任务管理系统功能简述
该系统基于C/S架构的TCP协议开发,利用MySQL数据库存放客户数据。客户端通过服务器从数据库中实现对
数据的增删改查,支持多用户同时在线操作。
二 开发思路
因为选择的是TCP协议的开发,因此首先要确定协议,即客户端先发送命令号,在发送数据,服务器则接受命令号,再根据命令号选择不同的处理函数处理。在此介绍一下协议的开发。
1 在客户端跟服务器的工程里都需要包含相同的命令号,例:
enum cmd
{
INFO_LOGIN=1,
INFO_EXIT,
INFO_REGIST,
INFO_NEW,
INFO_REFRESH,
INFO_SEARCH,
INFO_Edit,
INFO_DELETE
};
2 将需要发送/接受的数据放入结构体中比较方便,因此双方都需要包含相同类型的结构体,例:
struct task{
char creator[10];
char taskname[50];
char taskcontent[1000];
char taskstarttime[15];
char taskendtime[15];
char taskcreattime[20];
};
char creator[10];
char taskname[50];
char taskcontent[1000];
char taskstarttime[15];
char taskendtime[15];
char taskcreattime[20];
};
以上声明最好放在stdafx.h中共享。
3 客户端
CSocket m_sock;
int nCmd = NFO_EXIT;
task sendf;
int nRec;
m_sock.Send(&nCmd,sizeof(nCmd));
m_sock.Send(&sendf,sizeof(sendf));
m_sock.Recvive(&nRec,sizeof(nRec);//接受结果
服务器端
CSocketL m_sock;
int nCmd;
task recvf;
m_sock.Recvive(&nCmd,sizeof(nCmd));
m_sock.Recvive(&recvf,sizeof(recvf));
int nRet = deal();//数据处理完之后
m_sock.Send(&nRet,sizeof(nRet));//发送结果,返回值的类型根据需要定
当然在发送命令号和数据之间也可以收发一下反馈数据
4 服务器在接受到数据后转入相应的处理函数,在处理函数中与数据库交换数据,从而返回结果。
5 流程图:
三 开发环境及所需知识储备
1 开发工具:VC++6.0 、Mysql 5.6
2 开发环境的配置:
(1)到MySQL官网(Oracle)上下载 MySQL-connector-C文件,选择32位的,解压之后将include文件夹和lib文件夹
放到工程文件夹里(推荐放,不放也行,只要记住文件夹路径即可)
(2)打开VC -> 工具 -> 选项 -> 目录 下的 目录\include files: //以我的文件路径为例
加上include文件夹路径,例:C:\test\Server\include
打开VC -> 工具 -> 选项 -> 目录 下的 目录\Library files:
加上lib文件夹路径,例:C:\test\Server\lib
(3)新建工程后,在StdAfx.h文件中添加以下代码:
#include <winsock2.h> //若用 winsock.h会报错
#include <mysql.h>
#pragma comment (lib,"ws2_32.lib")
#pragma comment(lib,"libmysql.lib")
(4)将下载的lib文件夹中的libmysql.dll文件放入自己Debug/release文件夹
(5)若运行时显示无法连接至数据库,可能是用户没有远程登入权限:
解决远程数据库登入失败的问题://给root用户在IP为192.168.1.171的客户机上远程连接并操作所有数据的权限,791745为密码
grant all privileges on *.* to 'root' @'192.168.1.171' identified by '791745' with grant option;
#使修改生效
grant all privileges on *.* to 'root' @'192.168.1.171' identified by '791745' with grant option;
grant all privileges on *.* to 'root' @'192.168.1.171' identified by '791745' with grant option;
至此,该工程已经具备了和MySQL数据库连接的前提条件。
3 所需必备知识:
(1)MFC框架基础控件的运用,包括编辑框、CList列表、日期、按钮、静态文本等控件
a CList控件:
首先设置属性为report型,不需要排列,关联value型变量m_list
//在列表里插入标签
m_list.InsertColumn(0,"创建者",LVCFMT_CENTER,50);//插入第一个标签,名为创建者,居中显示,长度为50
m_list.InsertColumn(1,"任务名称",LVCFMT_CENTER,80);
//在列表中插入数据
m_list.InsertItem(int nItem,LPCTSTR lpszItem );//插入第nItem行第一列的数据
SetItemText(int nItem,int nSubItem,LPCTSTR lpszText ); //插入第nItem行第nSubItem(>1)列的数据
//获取鼠标选中的行号,从0开始
POSITION pos = pDlg->m_list.GetFirstSelectedItemPosition();
int nItem = pDlg->m_list.GetNextSelectedItem(pos);
//在列表里插入标签
m_list.InsertColumn(0,"创建者",LVCFMT_CENTER,50);//插入第一个标签,名为创建者,居中显示,长度为50
m_list.InsertColumn(1,"任务名称",LVCFMT_CENTER,80);
//在列表中插入数据
m_list.InsertItem(int nItem,LPCTSTR lpszItem );//插入第nItem行第一列的数据
SetItemText(int nItem,int nSubItem,LPCTSTR lpszText ); //插入第nItem行第nSubItem(>1)列的数据
//获取鼠标选中的行号,从0开始
POSITION pos = pDlg->m_list.GetFirstSelectedItemPosition();
int nItem = pDlg->m_list.GetNextSelectedItem(pos);
b 日期控件:
//控件日期(CTime类)转CString字符串:首先关联该空间变量m_time,
CString str;
str.Format("%d-%d-%d",m_time.GetYear(),m_time.GetMonth(),m_time.GetDay());
//CString字符串转成控件日期(COleDateTime)以便初始化时显示在该控件上
COleDateTime temp
CString str;
temp.ParseDateTime(str,VAR_DATEVALUEONLY);//第二个参数表示只显示日期
m_time.SetDate(temp.GetYear(),temp.GetMonth(),temp.GetDay());
c 需要静态文本控件响应点击的消息
打开属性,选中样式中的"通知"项,并更改ID默认的ID号,双击建立响应函数
d 新建的子对话框一般是没有初始化函数的,若需要的话,添加该子对话框类的window message handle函数WM_INITDIALG
e 需要在子类中运用父类的变量和方法:
CClientDlg *pDlg = (CClientDlg *)theApp.m_pMainWnd;
pDlg->SetWindowText(str);//例如设置主对话框的标题
f
//Char数组转CString对象:
CString str;
char szBuff[10];
str.Format( "%s ",szBuff);
//CString对象转char数组:
char *szBuff;
CString str = "Hello";
szBuff=str.GetBuffer(0);
str.ReleaseBuffer();//别忘释放内存
//Char数组转CString对象:
CString str;
char szBuff[10];
str.Format( "%s ",szBuff);
//CString对象转char数组:
char *szBuff;
CString str = "Hello";
szBuff=str.GetBuffer(0);
str.ReleaseBuffer();//别忘释放内存
g 对于控件关联变量,别忘了在代码中刷新值
UpdateData(true);//将关联变量的值刷新在空间上
UpdateData(false);//将值刷入关联变量
(2)MySQL基本的查询语句
#SELETE:指定两个字段的条件,查找其他字段中含有特定字符串的数据,括号不能忘
SELECT * FROM task WHERE (TaskName LIKE '%s' OR TaskContent LIKE '%s' )AND (CreatorTime BETWEEN '%s' AND '%s') AND (Creator = '%s');
#UPDATE:更新特定某一行的值
UPDATE task SET Creator = 'new',TaskName = 'new' WHERE Creator = 'old' AND TaskName = 'old';
#INSERT:插入一行新数据
INSERT INTO task(Creator,TaskName,TaskContent,TaskStartTime,TaskEndTime,CreatorTime)
VALUE('%s','%s','%s','%s','%s','%s');
#若查询语句中有中文,则要进行字符编码的转换,在操作数据之前设定
mysql_query(&mysql_rcs,"set names 'gbk'");
#补充:要查询数据库的字符编码,则用命令:SHOW VARIABLES LIKE 'character%';
(2)C++的基础知识
//利用vector类代替数组储存结构体,并作为返回值以及参数,因为vector的大小可以动态的改变
//实例解析:这是一个刷新数据的函数,即将登入用户的所有任务数据取出来
std::vector<task> RefreshTask(LPCSTR szName);//函数声明
std::vector<task> CConnectDB::RefreshTask(LPCSTR szName)//定义一个刷新数据的函数,返回一个结构体task类型的模板
{
using namespace std;//vector包含在名称空间std中
MYSQL_RES *result;//定义一个MySQL语句查询的结果集,用来存放从数据库取来的数据
MYSQL_ROW row;//MYSQL_ROW 类型表示是结果集里的一条数据,例如数据表里有10个字段,row[0]~row[9]里面保存的就是这10个字段的内容。
vector<task> recvfromDB;//定义一个容器
result = NULL;//初始化结果集指针为NULL
char querystr[MAX_SQL_QUERY_LEN];//定义一个字符串用来存放查询语句
memset(querystr, 0, MAX_SQL_QUERY_LEN);//将该数组清零,
sprintf(querystr,"SELECT * FROM task WHERE Creator = '%s'",szName);//将查询语句放入数组
mysql_query(&mysql_rcs,"set names 'gbk'");//因为查询语句中有中文,因此要转码
if(!mysql_query(&mysql_rcs, querystr))//该语句返回0表示查询成功(不代表有数据返回),返回其他则查询出错
{
result = mysql_store_result(&mysql_rcs);//将查询结果集存入result
if(result)//对于SELECT,需要有结果返回,因此result不为null则表示有结果集返回,此时
{//可以通过result->row_count这个成员变量查看结果集有几条数据,而对于UPDATE,DELETE,INSERT语句不需要返回数据,因此result为空表示操作成功
int nCol = mysql_num_fields(result);//结果集的列
int nRow = mysql_num_rows(result);//结果集的行
while(row = mysql_fetch_row(result))//循环将每一条(行)数据放到结构体中
{
task recv;//每次循环新建一个结构体,把数据库中一行数据存进去
sprintf(recv.creator,"%s",row[0]);
sprintf(recv.taskname,"%s",row[1]);
sprintf(recv.taskcontent,"%s",row[2]);
sprintf(recv.taskstarttime,"%s",row[3]);
sprintf(recv.taskendtime,"%s",row[4]);
sprintf(recv.taskcreattime,"%s",row[5]);
recvfromDB.push_back(recv);//将存好数据的结构体插入容器尾部
}
//int nSize = recvfromDB.size();计算容器的容量,nSize个结构体
}
}
return recvfromDB;//返回该容器
}
//下面调用这个函数
void CServeC::OnRefresh()
{
std::vector<task> Recv; //定义一个模板
Recv = cnt.RefreshTask(recv.user);//根据用户发来的信息查询,返回一个模板到Recv
std::vector<task>::iterator pd = Recv.begin();//获取容器中第一个数据的指针,iterator为迭代器名(广义上的指针)
int nSize = Recv.size();//获取容器大小
this->Send(&nSize,sizeof(nSize));//向客户端发送检索到的数据条数
for(pd;pd != Recv.end();pd++)//遍历发送至客户端,这是通过结构体传送,不能直接传递vector
{
this->Send(pd,sizeof(*pd));
}
}
//使用全局变量的时候,为了防止互相包含头文件导致的重复定义,在工程中应该
//在StdAfx.cpp中定义,在其他要使用的文件中声明
//例如:Bool nFlags 在StdAfx.cpp
// extern nFlags 在$.cpp
//对于函数的默认参数的使用
//函数原型:对于多个默认值的函数,应该从右向左定义
void OnReceive(LPCSTR szName = NULL,int nFlags );//错,应从右开始定义,必须调换一下参数的顺序
void OnReceive(LPCSTR szName = NULL,int nFlags = 7);//否则只能给另一个参数定义默认参数
void OnReceive(LPCSTR szName ,int nFlags = 7,float fTest = 3.0);//正确
//定义时不需要加默认参数值
void OnReceive(LPCSTR szName ,int nFlags,float fTest);//正确
//有一些函数和变量需要共享,以便在任何地方都能轻松调用
//可以将需要共享的函数和参数定义成app类的公有成员函数或参数
//然后在需要调用的地方通过theApp这个全局变量调用
//例:
#include "SocketC.h""
class CClientApp: public CWinApp
{
public:
CClientApp();//类构造函数
CSocketC m_sock;//需要共享的参数和函数
void OnRefresh();
……
}
//在需要使用的文件中
extern CClientApp theApp;
theApp.m_sock.Recvive();
theApp.OnRefresh();
//有一些函数和变量需要共享,以便在任何地方都能轻松调用
//可以将需要共享的函数和参数定义成app类的公有成员函数或参数
//然后在需要调用的地方通过theApp这个全局变量调用
//例:
#include "SocketC.h""
class CClientApp: public CWinApp
{
public:
CClientApp();//类构造函数
CSocketC m_sock;//需要共享的参数和函数
void OnRefresh();
……
}
//在需要使用的文件中
extern CClientApp theApp;
theApp.m_sock.Recvive();
theApp.OnRefresh();