目录
2.3.3根据用户ID查询当前用户好友的ID并用fri向量储存
一、TCP聊天系统的初步介绍
1.TCP聊天系统
一款基于TCP的聊天系统,实现客户端与客户端点对点通信。
2.实现目标
注册、登录、聊天和添加好友四大功能。
3、各个功能的消息流传图
客⼾端登录注册消息流转图
客⼾端添加好友信息流转图
客⼾端聊天消息流转图
服务端处理请求消息流转图
二、服务端介绍
准备工作
安装Jsoncpp;(应该是0.10.5版本)
yum install -y jsoncpp
yum install -y jsoncpp-devel
1、服务端模块划分
2、数据库模块的设计
数据库模块是与数据库打交道,处于最底层,负责维护我们所需要的数据库
功能:连接数据库,存储用户信息,查找用户信息
2.1数据库表的设计
一共有两张表:
user表:存放用户信息(用户ID(主键)、昵称、学校、电话、密码)
Create Table: CREATE TABLE `user` (
`userid` int(11) NOT NULL,
`nickname` varchar(20) NOT NULL,
`school` varchar(20) NOT NULL,
`telnum` char(11) NOT NULL,
`passwd` varchar(100) NOT NULL,
`m_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`userid`),
UNIQUE KEY `telnum` (`telnum`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
friendinfo表:存放用户好友信息(用户ID(外键)和好友ID)
Create Table: CREATE TABLE `friendinfo` (
`userid` int(11) NOT NULL,
`friend` int(11) NOT NULL,
KEY `userid` (`userid`),
KEY `friend` (`friend`),
CONSTRAINT `friendinfo_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `user` (`userid`),
CONSTRAINT `friendinfo_ibfk_2` FOREIGN KEY (`friend`) REFERENCES `user` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2MSQL-CAPI
初始化mysql操作句柄:
MYSQL *mysql_init(MYSQL *mysql);
函数含义:分配或初始化与mysql_real_connect()相适应的MYSQL对象。如果mysql是NULL指 针,该函数将分配、初始化、并返回新对象。否则,将初始化对象,并返回对象的地址。
连接mysql服务端:
MYSQL *
mysql_real_connect(MYSQL *mysql, //mysql操作句柄
const char *host, //服务端IP地址
const char *user, //⽤⼾名
const char *passwd, //密码
const char *db, //数据库
unsigned int port, //端⼝
const char *unix_socket, //是否使⽤本地域套接字
unsigned long client_flag //数据库标志位, 通常为0, 采⽤默认属性
)
函数含义:连接mysql服务端,如果连接成功,在返回的是MYSQL操作句柄, 失败返回NULL
设置连接对应的字符集:
int
mysql_set_character_set(MYSQL *mysql,
const char *csname)
函数含义:⽤于设置当前连接的默认字符集;
执⾏sql语句:
int
mysql_query(MYSQL *mysql, //mysql操作句柄
const char *stmt_str //执⾏的sql语句
)
函数含义:执⾏sql语句。 成功返回0, 失败返回⾮0;
获取结果集:
MYSQL_RES *
mysql_store_result(
MYSQL *mysql // mysql操作句柄
)
函数含义:获取查询的结果, 称之为结果集。成功, 返回MYSQL_RES的指针。 失败返回NULL。 注意事项:需要调⽤
获取结果集⾏数:
my_ulonglong
mysql_num_rows(MYSQL_RES *result)
函数含义:获取结果集的⾏数
获取结果集的下⼀⾏:
MYSQL_ROW
mysql_fetch_row(MYSQL_RES *result)
函数含义:获取结果集的下⼀⾏内容
释放结果集内存:
void
mysql_free_result(MYSQL_RES *result)
释放操作句柄:
void
mysql_close(MYSQL *mysql)
注意:编译的时候要加上链接库
头⽂件包含:#include <mysql/mysql.h>
链接时 : -L /usr/lib64/mysql -lmysqlclient
2.3数据库代码设计
2.3.1返回数据库中存放的所有用户的信息
2.3.2插入新的用户信息(注册)
2.3.3根据用户ID查询当前用户好友的ID并用fri向量储存
2.3.4根据用户ID查询好友信息,并用Json结构储存
3、用户管理模块的设计
功能:用于维护和管理每个用户信息
3.1描述用户信息类:UserInfo
3.2管理用户信息类:组织用户的信息
用户管理模块初始化的时候就需要从数据库当中获取所有用户的信息,还有每个用户的好友信息,并将这些信息放于user_map当中去,方便后续的查询
3、3业务接口:
处理注册请求、登录请求、发送消息、添加好友请求
4、线程安全的队列
消息池当中使⽤vector来保存消息,vector这个容器并不是线程安全, STL当中的容器都是线程不安全 *保证线程安全的机制: 互斥锁+条件变量
5、网络通信模块的设计
功能:接受新连接、监听文件描述符、接收数据和发送应答
初始化TCP通信:
主线程接受连接 & 往epoll当中添加多个⽂件新连接的⽂件描述符
接收线程:
发送线程:
6、自定义消息
什么是自定义消息:只有当服务端和客户端发送的消息格式统一的时候,才能正常的进行通信。消息格式不统一的话,就会导致,驴头不对马嘴,导致错误。
6.1Json Value 对象的认识:
Json就是一个key :value键值对的一个东西,包含多动类型
json 数据类型:对象,数组,字符串,数字
对象:使⽤花括号 {} 括起来的表⽰⼀个对象。
数组:使⽤中括号 [] 括起来的表⽰⼀个数组。
字符串:使⽤常规双引号 "" 括起来的表⽰⼀个字符串
数字:包括整形和浮点型,直接使⽤。
为了更好的理解Json cpp,有以下测试demo:
6.2Json序列化和反序列化
TCPSocket编程的时候,内存不连续的结构体是不能直接用send发送的,需要使用序列化,将内存组合起来,放到一块连续的内存当中去,然后再发送。反序列化就是将连续内存中的数据解析出来,变为原有的结构。
6.2使⽤Json数据格式封装⾃定义消息
7、业务处理模块的设计
功能:处理注册、登录、添加好友、发送消息的请求
三、客户端介绍
准备工作
MFC环境搭建:
Visual Studio 2019安装MFC(在VS已安装完成的情况下)_visual studio 安装mfc_小虎~的博客-CSDN博客
jsoncpp编译:
jsoncpp win下的编译与使用_jsoncpp windows_houxian1103的博客-CSDN博客
这里要注意的是:
我们服务端用的Jsoncpp版本是0.10.5,所以我们客户端应该也用该版本,当版本不一致时,会出现错误。
1、win-tcp封装
实现细节可以去看源码Git链接;
2、消息队列
3、MFC基于对话框编程(VS2019)
3.1注册界⾯ 及功能实现
void CRegisterDlg::OnBnClickedCommit(){
// TODO: 在此添加控件通知处理程序代码
/* 1.获取输入框的内容 */
UpdateData(true);
if (m_nickname_.IsEmpty() || m_school_.IsEmpty() || m_tel_.IsEmpty() || m_passwd_.IsEmpty()) {
MessageBox(TEXT("please enter information"));
return;
}
std::string nickname = CT2A(m_nickname_.GetString());
std::string school = CT2A(m_school_.GetString());
std::string telnum = CT2A(m_tel_.GetString());
std::string passwd = CT2A(m_passwd_.GetString());
/* 2. 组织ChatMsg数据 */
ChatMsg cm;
cm.msg_type_ = Register;
cm.json_msg_["nickname"] = nickname.c_str();
cm.json_msg_["school"] = school.c_str();
cm.json_msg_["telnum"] = telnum.c_str();
cm.json_msg_["passwd"] = passwd.c_str();
/* 3.获取Tcp的实例化指针*/
TcpSvr* ts = TcpSvr::GetInstance();
/* 4.发送消息*/
std::string msg;
cm.GetMsg(&msg);
ts->Send(msg);
/* 5.获取消息队列的实例化指针*/
MsgQueue* mq = MsgQueue::GetInstance();
if (mq == NULL) {
exit(1);
}
/* 6.按照消息类型 获取注册应答*/
mq->Pop(Register_Resp, &msg);
/*
7.解析应答,判断应答结果
注册成功==> 跳转到登录界面
注册失败==> 清空输入框,并且提示失败原因
*/
cm.clear();
cm.PraseMsg(-1, msg);
if (cm.reply_status_ == REGISTER_FAILED) {
MessageBox(TEXT("telnum repeat...,please chack"));
}
else {
MessageBox(TEXT("register success..."));
}
/* */
MessageBox(TEXT("OnBnClickedCommit"));
}
3.2登录界⾯ 及功能实现
void CchatDlg::OnBnClickedLogin()
{
// TODO: 在此添加控件通知处理程序代码
/* 1.获取输入框的内容 */
UpdateData(true);
if (m_tel_.IsEmpty()|| m_passwd_.IsEmpty()) {
MessageBox(TEXT("输入的内容不能为空..."));
return;
}
std::string telnum = CT2A(m_tel_.GetString());
std::string passwd = CT2A(m_passwd_.GetString());
/* 2. 组织ChatMsg数据 */
/*
{
msg_type:Login
json_msg_:{
telnum:xx;
passwd:xx
}
}
*/
ChatMsg cm;
cm.msg_type_ = Login;
cm.json_msg_["telnum"] = telnum.c_str();
cm.json_msg_["passwd"] = passwd.c_str();
/* 3.获取Tcp的实例化指针*/
TcpSvr* ts = TcpSvr::GetInstance();
/* 4.发送消息*/
std::string msg;
cm.GetMsg(&msg);
ts->Send(msg);
/* 5.获取消息队列的实例化指针*/
MsgQueue* mq = MsgQueue::GetInstance();
if (mq == NULL) {
exit(1);
}
/* 6.按照消息类型 获取注册应答*/
mq->Pop(Login_Resp, &msg);
/*
7.解析应答,判断应答结果
注册成功==> 跳转到登录界面
注册失败==> 清空输入框,并且提示失败原因
*/
cm.clear();
cm.PraseMsg(-1, msg);
if (cm.reply_status_ == LOGIN_FAILED) {
MessageBox(TEXT("请你检查 ,用户名或者密码是否输入错误"));
}
else {
CDialog::OnCancel();
CChatDlg ccd(cm.user_id_);
ccd.DoModal();
}
}
3.3 添加好友 及功能实现
UpdateData(true);
if (m_fritel.IsEmpty()) {
MessageBox(TEXT("输入内容不能为空..."));
return;
}
std::string input = CT2A(m_fritel.GetString());
ChatMsg cm;
cm.msg_type_ = AddFriend;
cm.user_id_ = userid_;
cm.json_msg_["fri_telnum"] = input.c_str();
/* 3. 获取TCP的实例化指针 */
TcpSvr* ts = TcpSvr::GetInstance();
/* 4. 发送消息 */
std::string msg;
cm.GetMsg(&msg);
ts->Send(msg);
CDialog::OnCancel();
3.4 聊天界⾯及功能实现
void CChatDlg::OnBnClickedSend()
{
UpdateData(true);
if (m_input_.IsEmpty()) {
MessageBox(TEXT("输入的内容不能为空..."));
return;
}
std::string input = CT2A(m_input_.GetString());
ChatMsg cm;
cm.msg_type_ = SendMsg;
cm.user_id_ = user_id_;
cm.json_msg_["msg"] = input.c_str();
cm.json_msg_["recv_userid"] = recv_userid_;
/* 3.获取Tcp的实例化指针*/
TcpSvr* ts = TcpSvr::GetInstance();
/* 4.发送消息*/
std::string msg;
cm.GetMsg(&msg);
ts->Send(msg);
/* 5.获取消息队列的实例化指针*/
MsgQueue* mq = MsgQueue::GetInstance();
if (mq == NULL) {
exit(1);
}
/* 6.从队列当中获取“好友请求的应答”*/
msg.clear();
mq->Pop(SendMsg_Resp, &msg);
/* 7.反序列化,获取状态及好友信息*/
cm.clear();
cm.PraseMsg(-1, msg);
/* 5.更新对话框*/
/* 6.记录在当前用户的history_msg当中*/
for (int i = 0; i < fris_info_.size(); ++i) {
if (fris_info_[i].user_id_ == recv_userid_) {
std::string tmp;
tmp += "我:";
tmp += input.c_str();
if (cm.reply_status_ == SENDMSG_FAILED) {
tmp += "(send failed)";
}
else {
tmp += "(send success)";
}
fris_info_[i].histroy_msg_.push_back(tmp);
m_output_.InsertString(m_output_.GetCount(), tmp.c_str());
}
}
/* 7.清空输入框*/
m_input_.Empty();
//
m_input_edit_.SetWindowTextA(0);
}