项目---基于TCP的高并发聊天系统

news2024/10/5 19:19:12

目录

服务端

 服务端视角下的流程图

一、数据库管理模块

1.1 数据库表的创建

1.2 .对于数据库的操作

1.2.1首先得连接数据库

1.2.2执行数据库语句

 1.2.3 返回数据库中存放的所有用户的信息

 1.2.4返回数据库中存放的所有用户的好友信息

 二、用户管理模块

2.1、UserInfo类:描述单个用户的信息

 2.2UserMana类:组织用户的信息

2.2.1初始化

 剩下的就是各类业务的接口

三、自定义消息格式

3.1了解Json对象

 3.2、Json序列化和反序列化

3.3、模拟注册请求和发送请求

四、网络通信模块&业务模块

4.1、InitChatSvr函数

4.2 StartChatSvr函数

客户端

客户端视角下的流程图

1、注册

2、登录

3、发送消息

4、添加好友


服务端

 服务端视角下的流程图

一、数据库管理模块

数据库模块是项目的最底层,主要就是负责与数据库进行打交道,

具体功能有:连接数据库、将用户信息存入数据库中、从数据库中获取信息、操作数据库中的表

1.1 数据库表的创建

这里创建了两张表:user,friendinfo。分别用来存放用户信息,用户好友信息。

1.2 .对于数据库的操作

1.2.1首先得连接数据库

1.2.2执行数据库语句

 1.2.3 返回数据库中存放的所有用户的信息

用户管理模块刚初始化的时候就需要一个工作:从数据库当中获取所有用户的信息,还有每个用户的好友信息

 1.2.4返回数据库中存放的所有用户的好友信息

 二、用户管理模块

数据库模块是整个项目的最低层,而用户管理模块是倒数第二层,是建立在数据库模块之上的

2.1、UserInfo类:描述单个用户的信息

 2.2UserMana类:组织用户的信息

2.2.1初始化

用户管理模块刚初始化的时候就需要一个工作:从数据库当中获取所有用户的信息,还有每个用户的好友信息,并将这些信息放于user_map当中去,方便后续的查询

 剩下的就是各类业务的接口

三、自定义消息格式

3.1了解Json对象

只有当服务端和客户端发送的消息格式统一的时候,才能正常的进行通信,就像网络当中的各种协议一样,只有双方都遵守的时候,才能正常的进行数据交换。

我们这里采用的是Json数据格式

Json就是一个key  :value的东西,他可以包含多动类型

简单介绍:

json 数据类型:对象,数组,字符串,数字
对象:使⽤花括号 {} 括起来的表⽰⼀个对象。

示例:

{
        " 姓名" : " ⼩张 " ,
        " 年龄" : 20,
         "成绩 " : [ 99 , 89 , 66 ]
}

这就是一个Json对象,Json还支持嵌套,可以有无数种格式

数组:使⽤中括号 [] 括起来的表⽰⼀个数组。
这个就是一个数组的形式,一个元素的类型就是一个Json对象
[
        {
                " 姓名" : " ⼩张 " ,
                " 年龄" : 20,
                 "成绩 " : [ 99 , 89 , 66 ]
        },
        
        {
                " 姓名" : " ⼩王 " ,
                " 年龄" : 22,
                 "成绩 " : [ 77 , 88 , 66 ]
        },
]
字符串:使⽤常规双引号 "" 括起来的表⽰⼀个字符串
数字:包括整形和浮点型,直接使⽤
jsoncpp库,已经帮助我们封装了json对象,数组,以及序列化和反序列化的接口,我们在这里只需要会用就行了。

我们来测试一下:

 能够看到,json对象用起来是非常方便的。

 我们再来测试看一下Json的NB之处:

 我们来运行一下看能打印出什么:

 3.2、Json序列化和反序列化

为什么需要序列化呢?

在tcpsocket编程的时候,我们学习过,内存不连续的结构体是不能直接用send发送的,需要使用序列化,将内存组合起来,放到一块连续的内存当中去,然后再发送。反序列化就是反过来,具体可以去看我之前写的博客:网络基础2--HTTP协议详解_Flying clouds的博客-CSDN博客

序列化:

 反序列化:

3.3、模拟注册请求和发送请求

注册请求:

 注册响应:

四、网络通信模块&业务模块

1、该模块负责TCP的socket编程,来负责网络通信。用epoll来监控多个文件描述符,从而实现具有高并发的基础

2、该模块还负责处理各种业务如:注册、登录、添加好友、聊天的具体实现

4.1、InitChatSvr函数

这个函数的作用是初始化资源,获取到需要的所有资源,比如用户管理模块的实例化指针、发送队列和接收队列、提供TCP服务的实例化指针、epoll操作句柄。

 bool InitChatSvr(int wtc = 4){
            /* 1. 初始化用户管理类的指针 */
            um_ = UserMana::GetInstance();
            if(um_ == NULL){
                std::cout << "初始化用户管理失败了, 请检查..." << std::endl;
                return false;
            }
            /* 2. 初始化队列资源 */
            recv_que_ = new MsgPool<ChatMsg>();
            if(recv_que_ == NULL){
                std::cout << "init MsgPool failed" << std::endl;
                return false;
            }

            send_que_ = new MsgPool<ChatMsg>();
            if(send_que_ == NULL){
                std::cout << "init MsgPool failed" << std::endl;
                return false;
            }

            work_thread_count_ = wtc;

            /* 3. 初始化tcp通信的内容 */
            sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(sockfd_ < 0){
                perror("socket");
                return false;
            }
            int opt = 1;
            // sockfd为需要端口复用的套接字
            setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));

            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port_);
            addr.sin_addr.s_addr = inet_addr("0.0.0.0");
            int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
            if(ret < 0){
                perror("bind");
                return false;
            }

            ret = listen(sockfd_, 10);
            if(ret < 0){
                perror("listen");
                return false;
            }

            /* 4. 初始化epoll */
            ep_fd_ = epoll_create(5);
            if(ep_fd_ < 0){
                perror("epoll_create");
                return false;
            }

            return true;
        }

4.2 StartChatSvr函数

这个函数相当于是开始工作的函数,调用这个函数之后,主线程就会 启动接收线程、发送线程、工作线程。

启动完别的线程之后,我们给主线程也找了一个活干:一直在accept,并将接收到的新连接套接字放到epoll当中去,让epoll帮我们去监控这些个文件描述符的状态

bool StartChatSvr(){

            /* 1. 启动接收线程 */
            pthread_t tid;
            int ret = pthread_create(&tid, NULL, RecvStart, (void*)this);
            if(ret < 0){
                std::cout << "create thread failed\n";
                return false;
            }

            /* 2. 启动发送线程 */
            ret = pthread_create(&tid, NULL, SendStart, (void*)this);
            if(ret < 0){
                std::cout << "create thread failed\n";
                return false;
            }
            /* 3. 启动工作线程 */
            for(int i = 0; i < work_thread_count_; i++){
                ret = pthread_create(&tid, NULL, WorkerStart, (void*)this);
                if(ret < 0){
                    std::cout << "create thread failed\n";
                    return false;
                }
            }
            /* 4. 主线程accept */
            /* 5. 将新连接套接字放到epoll当中 */

            while(1){
                int new_sockfd = accept(sockfd_, NULL, NULL);
                if(new_sockfd < 0){
                    continue;
                }
                /* 添加到epoll */
                struct epoll_event ee;
                ee.events = EPOLLIN;
                ee.data.fd = new_sockfd;
                epoll_ctl(ep_fd_, EPOLL_CTL_ADD, new_sockfd, &ee);
            }
        

客户端

客户端视角下的流程图

 

客户端我们这里选取了MFC框架来进行,我们这里只是用到了MFC最基本的使用方法,想要熟练掌握MFC的同学可以去看b站上面的视频,上面的讲解是非常清晰的,我们这里只是简单来用。

我们在客户端主要是实现了四个基本的业务:注册,登录,添加好友,给好友发送消息

我们先来看看四个窗口:

 

1、注册

我们首先要插入一个Dialog,然后去对其进行操作,具体做法就是:插入一个Dialog,然后点击这个Dialog,我们就能看到一个窗口,然后再对窗口添加button。。。啥的各种内容。

在窗口当中对 : 姓名、学校、电话、密码添加变量,用来保存之后输入的值。

我们右键去对其添加变量、控件什么的,双击就是会给我们自动生成代码。

我们这个注册窗口是需要点击提交之后,成功的话就直接跳转到登录的界面,我们这里选择的是双击提交,生成代码,然后在这个函数里面实现我们想要的功能

void CRegisterdlg::OnBnClickedCommit()
{
	// TODO: 在此添加控件通知处理程序代码
	/*1.获取输入框的内容*/
	UpdateData(true);
	if (m_nickname_.IsEmpty()||m_school_.IsEmpty()||
		m_tel_.IsEmpty()||m_passwd_.IsEmpty()) {
		MessageBox(TEXT("请输入完整的注册内容!"));
		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.按照消息类型获取注册应答*/
	msg.clear();
	mq->Pop(Register_Resp,&msg);
	/*
		7.解析应答,判断应答结果
		   注册成功 ==》跳转到登 录界面
		   注册失败 ==》情空注册框,提示失败了(电话号码重复了)
		*/
	cm.clear();
	cm.PraseMsg(-1,msg);
	if (cm.reply_status_ == REGISTER_FAILED) {
		MessageBox(TEXT("电话号码重复了,请检查输入。。。"));
	}
	else {
		MessageBox(TEXT("注册成功!"));
	}
	
}

2、登录

登录的时候,需要做到是点击登录按钮之后能够跳转到我们的聊天界面,需要注意的是,在跳转到聊天界面之前,我们需要提前吧聊天界面的内容给加载好,这样一条转到界面我们就能进行操作的,跳转界面我们已经知道了是调用domodel,用模态的方式打开界面,但是怎么加载数据呢?

我们这里可以找到方法:在调用domodel函数的时候,会调用一个函数,这个函数是重写父类的虚函数

BOOL CChatDlg::OnInitDialog()

在这个函数里面,我们可以能够实现获取聊天界面内容的功能,我们要获取的内容就两个:好友列表好友的消息记录。

知道了这些之后,我就只剩下开工了:

如何展示:

 保存好友信息到本地

因为好友信息是动态变化的,我们如果只是在获取好友信息的时候就在列表当中展示一次的话,那么当添加好友或者删除好友的时候,我们只能获取到新的好友信息,原来的好友信息就不知道从哪里再去获取了,方便起见,我们就直接保存在客户端,用vector来进行保存。

3、发送消息

我们想要发送消息,第一步肯定是要知道消息到底要发送给谁,就像微信一样,我们在好友列表当中点击谁,就代表要给哪个好友发送消息了,我们为了能实现这个功能:需要做到的就是能够时刻识别出来我们点击了好友列表,并且要知道到底点击了谁。

这个功能我们用一个MFC的函数来实现 : GetText,这个函数能够知道我们点击了哪个好友

/*
	当用户点击了 frilist,
		1. 要及时的更新 recv_userid
		2. 更新对话框
	*/
	/* 1. 获取点击的内容 */

	CString str;
	m_frilist_.GetText(m_frilist_.GetCurSel(), str);

	/* 2. 根据点击的内容进行比对*/
	/* 3. 更新 recv_userid_ */
	int i = 0;
	for (; i < fris_info_.size(); i++) {
		// eg: zs-bite    zs-bite:10
		// zs ls
		std::string tmp = CT2A(str.GetString());
		if (strstr((tmp.c_str()), fris_info_[i].nickname_.c_str()) != NULL) {
			recv_userid_ = fris_info_[i].user_id_;
			break;
		}
	}

当我们切换了好友之后,我们也要同步更新聊天框的内容

我们这里的做法也很简单粗暴:将现在所有的消息全部清空,然后再将当前好友的历史记录依次展示上去

/* 4. 刷新聊天界面 */
	for (int i = m_output_.GetCount(); i >= 0; i--) {
		m_output_.DeleteString(i);
	}

	std::vector<std::string> h_m = fris_info_[i].history_msg_;
	for (int i = 0; i < h_m.size(); i++) {
		m_output_.InsertString(m_output_.GetCount(), h_m[i].c_str());
	}
	fris_info_[i].unread_msg_ = 0;

	/* 5. 清空输入框 */
	m_input_.Empty();
	m_input_edit_.SetWindowTextA(0);

	/* 6. 刷新UserList */
	ReferFriList();

4、添加好友

我们想要做到的是输入一个好友的电话号码,然后给服务端发送一个添加好友的请求,在服务端这里进行判断,如果有这个电话号码是一个注册用户的话,我们就给这个用户推送一个添加好友的请求,如果不是的话服务端就忽略这条消息。

当用户收到添加好友请求的时候,可以选择同意或者拒绝,如果拒绝,就当作无事发生就行;如果点击了同意,就需要给服务端回一个同意添加好友的请求,然后服务端进行处理,将两个人的关系设置为好友,并再次给客户端推送应答,让客户端的好友列表进行更新,将新添加的好友展示上去。

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();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/421200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习和人工智能之间是什么样的关系?

深度学习与人工智能概念的潜在联系&#xff0c;我们依然借助维恩图来说明&#xff0c;如图4.1所示。 1、人工智能 “人工智能”这个概念新鲜时髦但又含混模糊&#xff0c;同时包罗万象。尽管如此,我们仍尝试对 人工智能进行定义:用一台机器处理来自其周围环境的信息,然后将这些…

学习系统编程No.10【文件描述符】

引言&#xff1a; 北京时间&#xff1a;2023/3/25&#xff0c;昨天摆烂一天&#xff0c;今天再次坐牢7小时&#xff0c;难受尽在不言中&#xff0c;并且对于笔试题&#xff0c;还是非常的困难&#xff0c;可能是我做题不够多&#xff0c;也可能是没有好好的总结之前做过的一些…

15.transformer全解

欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 文章目录1.基础介绍2.网络结构2.1 Input/Output Embedding2.2 自注意力机制 self-attention2.3 point-wise全连接层2.4 位置编码 Position Encoding3.输入处理过程示例4.代码实现1.基础介绍…

论文阅读和分析:Hybrid Mathematical Symbol Recognition using Support Vector Machines

HMER论文系列 1、论文阅读和分析&#xff1a;When Counting Meets HMER Counting-Aware Network for HMER_KPer_Yang的博客-CSDN博客 2、论文阅读和分析&#xff1a;Syntax-Aware Network for Handwritten Mathematical Expression Recognition_KPer_Yang的博客-CSDN博客 3、论…

自然语言处理(七): Deep Learning for NLP: Recurrent Networks

目录 1. N-gram Language Models 2. Recurrent Neural Networks 2.1 RNN Unrolled 2.2 RNN Training 2.3 (Simple) RNN for Language Model 2.4 RNN Language Model: Training 2.5 RNN Language Model: Generation 3. Long Short-term Memory Networks 3.1 Language M…

论文阅读【14】HDLTex: Hierarchical Deep Learning for Text Classification

论文十问十答&#xff1a; Q1论文试图解决什么问题&#xff1f; 多标签文本分类问题 Q2这是否是一个新的问题&#xff1f; 不是 Q3这篇文章要验证一个什么科学假设&#xff1f; 因为文本标签越多&#xff0c;分类就越难&#xff0c;所以就将文本类型进行分层分类&#xff0c;这…

【人工智能与深度学习】判别性循环稀疏自编码器和群体稀疏性

【人工智能与深度学习】判别性循环稀疏自编码器和群体稀疏性 判别类循环稀疏自编码器 (DrSAE)组稀疏组稀疏自编码器的问与答图像级别训练,无权重分享(weight sharing)的局域过滤器 (local filters)判别类循环稀疏自编码器 (DrSAE) DrSAE的设计结合了稀疏编码(稀疏自编码器)…

数据库并发控制基本概念和基本技术

并发控制与基本技术一、并发控制1. 概述2. 并发访问可能出现的问题二、并发控制的主要技术1、基本技术2、封锁及锁的类型2.1、什么是封锁2.2、基本封锁类型2.2.1、排它锁&#xff08;Exclusive Locks&#xff0c;简记为 X 锁&#xff09;2.2.2、共享锁&#xff08;Share Locks&…

基于ArkUI框架开发-ImageKnife渲染层重构

ImageKnife是一款图像加载缓存库&#xff0c;主要功能特性如下&#xff1a; ●支持内存缓存&#xff0c;使用LRUCache算法&#xff0c;对图片数据进行内存缓存。 ●支持磁盘缓存&#xff0c;对于下载图片会保存一份至磁盘当中。 ●支持进行图片变换&#xff1a;支持图像像素源图…

【SSconv:全色锐化:显式频谱-空间卷积】

SSconv: Explicit Spectral-to-Spatial Convolution for Pansharpening &#xff08;SSconv&#xff1a;用于全色锐化的显式频谱-空间卷积&#xff09; 全色锐化的目的是融合高空间分辨率的全色&#xff08;PAN&#xff09;图像和低分辨率的多光谱&#xff08;LR-MS&#xff…

【微服务】6、一篇文章学会使用 SpringCloud 的网关

目录一、网关作用二、网关的技术实现三、简单使用四、predicates(1) 网关路由可配置的内容(2) 路由断言工厂&#xff08;Route Predicate Factory&#xff09;五、filters(1) GatewayFilter(2) 给全部进入 userservice 的请求添加请求头(3) 全局过滤器 —— GlobalFilter(4) 过…

PX4从放弃到精通(二十七):固定翼姿态控制

文章目录前言一、roll/pitch姿态/角速率控制二、偏航角速率控制三、主程序前言 固件版本 PX4 1.13.2 欢迎交流学习&#xff0c;可加左侧名片 一、roll/pitch姿态/角速率控制 roll/pitch的姿态控制类似&#xff0c;这里只介绍roll姿态控制&#xff0c; 代码位置&#xff1a; …

如何确定NetApp FAS存储系统是否正常识别到了boot device?

近期处理了几个NetApp FAS存储控制器宕机的案例&#xff0c;其中部分有代表性的就是其实控制器并没有物理故障&#xff0c;问题是控制器里面的boot device的SSD盘出现了问题。这里给大家share一下如何确定系统是否成功识别到了boot device设备。 对于很多非专业人士来说&#…

mongodb使用docker搭建replicaSet集群与变更监听

在mongodb如果需要启用变更监听功能(watch)&#xff0c;mongodb需要在replicaSet或者cluster方式下运行。 replicaSet和cluster从部署难度相比&#xff0c;replicaSet要简单许多。如果所存储的数据量规模不算太大的情况下&#xff0c;那么使用replicaSet方式部署mongodb是一个…

凹凸/法线/移位贴图的区别

你是否在掌握 3D 资产纹理的道路上遇到过障碍&#xff1f; 不要难过&#xff01; 许多刚接触纹理或 3D 的艺术家在第一次遇到凹凸贴图&#xff08;Bump Map&#xff09;、法线贴图&#xff08;Normal Map&#xff09;和移位贴图&#xff08;Displacement Map&#xff09;时通常…

Linux Redis主从复制 | 哨兵监控模式 | 集群搭建 | 超详细

Linux Redis主从复制 | 哨兵监控模式 | 集群搭建 | 超详细一 Redis的主从复制二 主从复制的作用三 主从复制的流程四 主从复制实验4.1 环境部署4.2 安装Redis&#xff08;主从服务器&#xff09;4.3 修改Master节点Redis配置文件 (192.168.163.100)4.4 修改Slave节点Redis配置文…

MySQL-用户与权限

目录 &#x1f341;DB权限表 &#x1f341;新建普通用户 &#x1f342;创建新用户(create user) &#x1f342;创建新用户(grant) &#x1f341;删除普通用户 &#x1f341;修改用户密码 &#x1f342;Root用户修改自己的密码 &#x1f342;Root用户修改普通用户密码 &#x1f…

区块链概论

目录 1.概述 2.密码学原理 2.1.hash函数 2.2.签名 3.数据结构 3.1.区块结构 3.2.hash pointer 3.3.merkle tree 3.3.1.概述 3.3.2.证明数据存在 3.3.3.证明数据不存在 4.比特币的共识协议 4.1.概述 4.2.验证有效性 4.2.1.验证交易有效性 4.2.2.验证节点有效性 …

~~~~~不得不会的账号与权限管理小知识

目录一.用户账号和组账号概述二. useradd添加用户账号三. passwd 修改密码四. 修改用户账户的属性五 . userdel 删除用户账号六. 用户账号的初始配置文件七. 组账号文件八 . 文件/目录的权限及归属8.1设置文件和目录的权限chmod8.2 设置文件和目录的归属chown命令8.3 补充扩展:…

JAVA本地监听与远程端口扫描的设计与开发

随着Internet的不断发展&#xff0c;信息技术已成为社会进步的巨大推动力。不管是存储于服务器里还是流通于Internet上的信息都已成为一个关系事业成败的关键&#xff0c;这就使保证信息的安全变得格外重要。本地监听与远程端口扫描程序就是在基于Internet的端口扫描的基础上&a…