### 项目名称: Happy_Chat
### 项目作者: 楚东方
1. 引言
1.1 项目综述
对项目进行简要介绍,并说明编写此项目的目的。
该项目为聊天室,主要为了实现聊天,文件传输,方便linux环境下的,交流与聊天。
实现功能:
1.好友管理:
(1)添加好友
(2)删除好友
2.群管理
(1)创建群
(2)加群
(3)退群
(4)解散群
3.文件传送
实现了上传和下载的断点续传
4.聊天界面分屏
利用光标的移动对输入和屏幕聊天记录输出的分屏
5.MD5加密
利用MD5算法,对用户的密码进行加密,对原来的MD5加密进稍微的改动,把字符串类型加密到unsigned int 类型
6.离线传输
对私聊,群聊,传送文件都添加了离线功能,对方上线即时传送
7.即时状态显示
好友列表会及时更新,并用彩色显示当前好友的状态
8.数据库
利用mysql C语言的API函数,应用mysql数据库对聊天记录进行存储,保证聊天记录的有效性
9.多路复用
epoll实现服务端的多路复用,保证服务端连接客户端数据的稳定性
10.容错处理
对于输入内容进行判别,防止在不正规的操作下程序崩溃。
1.2 术语表
定义在本文档中出现的专有词汇,以方便读者进行无障碍阅读。
序号 |
术语或缩略语 |
说明性定义 |
1 |
C/S |
Client/Server模型 |
2 |
API |
应用程序编程接口 |
3 |
多路复用 |
一个信道同时传输多路信号 |
1.3 参考资料
列出在项目开发过程中,所参考、查阅有关资料的名称、作者、来源等。参考资料包括:
a.项目相关技术论文;
b.网络上相类似项目;
c.相关技术文档、个人博客。
资料名称 |
作者 |
资料地址(URL) |
LinuxC编程实战 |
|
|
Csdn博文 |
|
|
博客园博文 |
|
|
1.4 项目开发环境、流程图绘制工具
开发环境:Ubuntu 16.04 STL 6 gcc编译器
绘图工具:prosesson
程序运行环境:
1.局域网/外网
2.安装mysql数据库 sudo apt-get install mysql-server mysql-client
3.安装mysql数据库开发包 sudo apt-get instal libmysqlclient-dev
4.数据库 Database :happychat
Table :message_tbl
表中变量:recv_name varchar(1024) send_name varchar(1024) mes varchar(2048)
2. 数据结构说明
本章说明本程序系统中使用的数据结构。
包括数据结构名称,功能说明,具体数据结构说明(定义、注释、取值)等。
本程序主要采用数组做为数据结构,包括数组元素的删除,添加,遍历。
数组作为一种线性结构,以包的形式进行定义。
3. 模块设计
3.1 程序函数调用图及模块化分
Client总图:
模块划分图:
Server总图:
模块划分:
3.2 功能设计说明
3.2.1 模块1
功能描述
简要描述模块1的功能。
实现文件的断点发送和断点接受
算法和流程图
详细描述根据输入数据产生输出数据的算法和流程
函数说明
具体说明本程序中的各个函数,包括函数名称及其所在文件,功能,格式,参数,全局变量,局部变量,返回值,算法说明,使用约束等。
发送文件断点续传:
1.
void send_file()
功能: 向服务端请求发送文件,并将文件的大小发送给服务端
参数: 无
返回值: 无
算法说明: 对文件大小进行字符串解析
2.
void file_recv_begin(PACK *recv_pack)
功能: * 跟据收到包的内容,客户端请求发送文件
* 首先判断是否已经有该文件信息
* 如果有,返回该文件的大小
* 否则,建立文件信息,并返回文件大小为0
参数: 接送到的包指针
返回值: 无
算法说明: 对文件大小进行字符串解析,添加数组元素
3.
void * pthread_send_file(void *mes_t)
功能: 当接收到允许发送的消息时,开启线程发送文件
参数: 结构体指针
返回值: 无
算法说明: 对文件大小进行字符串解析
void send_file_send(int begin_location,char *file_path)
功能: 从起始位置向服务端发送文件
参数: int begin_location :文件起始发送位置
char *file_path :文件名
返回值: 无
算法说明: 打开文件,while循环发送文件
4.
void *file_send_send(void *file_send_begin_t)
功能: 在发送过程中,不断监测接收端的状态
* 若接收端下线,则向接收端发送提醒消息
参数: 结构体指针
返回值: 无
算法说明: 对文件大小进行字符串解析,while发送文件,并不断检测发送者 信息
5.
void mes_sendfile_fail(int id)
功能: 处理文件上传失败,并询问是否重发,进行断点续传
参数: 消息数组中消息ID
返回值: 无
算法说明: 询问是否重发
void * pthread_send_file(void *mes_t)
功能: 当接收到允许发送的消息时,开启线程发送文件
参数: 结构体指针
返回值: 无
算法说明: 对文件大小进行字符串解析
void send_file_send(int begin_location,char *file_path)
功能: 从起始位置向服务端发送文件
参数: int begin_location :文件起始发送位置
char *file_path :文件名
返回值: 无
算法说明: 打开文件,while循环发送文件
接受文件断点续传:
1.
void *pthread_check_file(void *arg)
功能: 不断检测文件状态,当文件传输失败,发送提醒给客户端
当文件传输成功时,发送提醒给客户端接受
当文件传输完成,删除服务端缓存文件,和对应文件数组信息
参数: 无
返回值: 无
算法说明: while循环不断遍历 对文件大小进行字符串解析
2.
void mes_recv_requir(int id)
功能: 处理下载文件请求,询问是否接收文件,发送信息给服务端,开启 线程写文件
参数: 消息数组中消息ID
返回值: 无
算法说明: 对文件大小进行字符串解析 询问是否接受文件 开启线程写文件
void *pthread_recv_file(void *par_t)
功能: 接收文件线程,从存储接收包的地方检索到信息
并写入文件,当文件写入完成,向服务端发送信息,关闭线程
参数: 无
返回值: 无
算法说明: while循环写文件
3.
void file_send_begin(PACK *recv_pack)
功能: 客户端请求接收文件,解析出客户端已经
接收的文件字节数,然后从该字节数开始发送
参数: 接受的包的指针
返回值: 无
算法说明: 对文件大小进行字符串解析
void *file_send_send(void *file_send_begin_t)
功能: * 根据文件的起始位置,开始发送,
参数: 结构体指针
返回值: 无
算法说明: 对文件大小进行字符串解析 while循环发送文件
4.
void *pthread_check_file(void *arg)
功能: 不断检测文件状态,当文件传输失败,发送提醒给客户端
当文件传输成功时,发送提醒给客户端接受
当文件传输完成,删除服务端缓存文件,和对应文件数组信息
参数: 无
返回值: 无
算法说明: while循环不断遍历 对文件大小进行字符串解析
5.
void mes_recvfile_fail(int id)
功能: 处理接收文件中断信息,并询问是否继续接收
参数: 消息数组中消息ID
返回值: 无
算法说明: 对文件大小进行字符串解析 询问是否继续接受文件
开启线程写文件
6.
void file_send_begin(PACK *recv_pack)
功能: 客户端请求接收文件,解析出客户端已经
接收的文件字节数,然后从该字节数开始发送
参数: 接受的包的指针
返回值: 无
算法说明: 对文件大小进行字符串解析
void *file_send_send(void *file_send_begin_t)
功能: * 根据文件的起始位置,开始发送,
参数: 结构体指针
返回值: 无
算法说明: 对文件大小进行字符串解析 while循环发送文件
7.
void *pthread_recv_file(void *par_t)
功能: 接收文件线程,从存储接收包的地方检索到信息
并写入文件,当文件写入完成,向服务端发送信息,关闭线程
参数: 无
返回值: 无
算法说明: while循环写文件
void file_send_finish(PACK *recv_pack)
功能: * 客户端接收完,会发送确认信息,
* 然后把文件状态改为:已经发送完毕
* 其他函数对该文件进行相应处理
参数: 接受包的指针
返回值: 无
算法说明: 改变文件状态
void *pthread_check_file(void *arg)
功能: 不断检测文件状态,当文件传输失败,发送提醒给客户端
当文件传输成功时,发送提醒给客户端接受
当文件传输完成,删除服务端缓存文件,和对应文件数组信息
参数: 无
返回值: 无
算法说明: while循环不断遍历 对文件大小进行字符串解析
其他说明
3.2.2 模块2
功能描述
简要描述模块1的功能。
对聊天界面进行优化,在聊天时保证了信息流动显示,输入与显示分屏,
提升界面友好性
算法和流程图
详细描述根据输入数据产生输出数据的算法和流程。
函数说明
具体说明本程序中的各个函数,包括函数名称及其所在文件,功能,格式,参数,全局变量,局部变量,返回值,算法说明,使用约束等。
1.
void send_mes_to_one()
功能: 私聊,开启线程显示信息,发送信息到服务端
参数: 无
返回值: 无
算法说明: 无
void send_mes_to_group()
功能: 群聊,开启线程显示信息,发送信息到服务端
参数: 无
返回值: 无
算法说明: 无
void *show_mes(void *username)
功能: 在聊天的同时启动,启动线程读取,存储区域的
消息,并显示出来
参数: 消息发送者姓名
返回值: 无
算法说明: 遍历数组
void print_mes(int id)
功能: 根据寻找到的包把信息输出
参数: 包数组ID
返回值: 无
算法说明: 读取数组元素
void show_mes_smart(char *name ,char *mes)
功能: 分屏显示聊天信息
参数: char *name:信息发送者姓名
char *mes:信息内容
全局变量: m_print_mes[] 缓存数组
m_print_mes_num 缓存数组中消息的个数
返回值: 无
算法说明: 更新缓存数组:数组依次往后覆盖,把数组第一个元素覆盖,把留 出的最后一个数组位置给新的信息
利用光标显示缓存数组信息
程序简略代码:
void show_mes_smart(char *name ,char *mes) //该程序连续显示6条信息
{
number = 6;
if(m_print_mes_num == number) { //如果缓存数已满,覆盖第一个元素,更 新最后一个元素
for(int i=1;i<=5 ;i++)
m_print_mes[i] = m_print_mes[i+1];
strcpy(m_print_mes[m_print_mes_num].name,name);
strcpy(m_print_mes[m_print_mes_num].mes,mes);
}
else{ //未满,直接添加
strcpy(m_print_mes[++m_print_mes_num].name,name);
strcpy(m_print_mes[m_print_mes_num].mes,mes);
}
/***********步骤1**************/
//记录光标位置信息
printf("\33[s");
fflush(stdout);
//光标上移到顶端
printf("\33[25A\n");
/***********步骤2**************/
//清空信息显示区域
for(int i=1;i<=20;i++)
printf("\33[K\n");
/***********步骤3**************/
//光标上移到顶端
printf("\33[21A\n");
/***********步骤4**************/
//输出信息
for(int i=1;i<=m_print_mes_num;i++)
prinf()
//如果信息不到6个,则输出换行符占位
for(int i=1;i<=6- m_print_mes_num ;i++)
printf("\n\n\n");
/***********步骤5**************/
//光标还原到原来位置
printf("\33[u");
fflush(stdout);
}
其他说明
4. 文件说明
文件名 |
功能描述 |
mian.cpp |
socket启动及链接,开启线程 |
logic.cpp |
功能函数:对应了登陆与注册,私聊,群聊,文件传输,好友和群组管理函数 |
tools.cpp |
工具函数:求文件大小,发送包,和一些判断函数 |
view.cpp |
显示的界面函数:菜单显示函数,聊天界面函数,信息查看函数 |
client.h |
结构体声明,函数声明,全局变量声明 |
|
|
mian.cpp : |
socket启动及链接,开启线程,开启多路复用 |
server.cpp |
对接收的包按类型进行处理,并把处理结果返回客户端 |
tools.cpp |
对包处理时用到的一些功能函数,对存储信息的数组的一些操作 |
Data_access.cpp |
用户信息文件的读取与保存,数据库的链接,插入,查询,断开链接 |
md5.cpp |
主要是关于md5加密的一些功能函数 |
debug.cpp |
调试用时的输出函数 |
Server.h |
结构体声明,函数声明,全局变量声明 |
5. 异常、错误处理
程序支持对何种错误输入、异常进行识别与处理,以及处理的方案与结果
1.输入接受问题:
1.程序在选择功能的时候只支持数字,如果进行随意输入,程序不会进行下一步,知道输入符合条件的选项。
2.在聊天的时候,程序不支持发送空信息,如果直接按回车键,程序将不会继续执行
2.IP地址绑定问题:
服务端只需自动绑定当前IP即可,而客户端则绑定腾讯云的公网IP
这里把代码做了修改:
1. addr.sin_addr.s_addr = htonl(INADDR_ANY);
3.包传递问题
由于该聊天室全部采用包传递的方式,利用结构体进行传递,而结构体内容如下:
1. typedef struct datas{
2. char send_name[MAX_CHAR];
3. char recv_name[MAX_CHAR];
4. int send_fd;
5. int recv_fd;
6. time_t time;
7. char mes[MAX_CHAR*2];
8. }DATA;
9.
10. typedef struct package{
11. int type;
12. DATA data;
13. }PACK;
这里recv()和send()都传输sizeof(PACK)个字符
问题:在传输时发现传输总是乱码
原因:在32位操作系统和64位操作系统下long 的字节数不相同,导致sizeof(PACK)不同,导致乱码(time_t实质就是long类型)
解决:后来我把time_t这一项去掉,得到正确结果
注意:32位机和64位机 字节数的区别
总结如下:
常用数据类型对应字节数
可用如sizeof(char),sizeof(char*)等得出
32位编译器:
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
以上占用字节数其实是针对c/c++语言而言的,对于Java来说由于其JVM具有跨平台性因此java在32位和64位机下基本数据类型占字节数是一致的(这样才能达到跨平台通信)。
4.包的重复接受问题
客户端发送一个包,客户端会接受到多个包。
问题原因:在使用epoll多路复用时,使用了while循环,在一个包接受完还没来到及清除缓冲区的情况下,进入第二次循环,就导致了一个包被recv多次,这里猜测,epoll判断客户端有消息是根据缓冲区是否为空判断的。
解决方法:这里我想,没有及时清除缓冲区,那我就在循环中加上usleep,等待其清空完,结果成功解决。 但由于这样太耗费时间,我猜想是因为包太大的缘故,导致清空速达减慢,后来我把包空间减小,果然解决了问题。
注意:在使用epoll时一定要注意其是否即使清空缓冲区,避免多次接受。
5.创建线程过多:
问题:在传输文件时,由于每接受一个包就要创建一个线程进行处理,所以这里会出现线程创建过多的情况。
解决方法:为了扩大最大创建线程量,我使用了ulimit -s 命令扩大了线程最多创建数。
在网上查了下,原因如下,Linux系统中每个线程都拥有独立的栈空间,而我的系统上调用ulimit -a看到的结果如下:
ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 20
file size (blocks, -f) unlimited
pending signals (-i) 16382
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
可以看到stack size是8M, 400个线程就需要8*400=3200M,虚拟内存不够用。
解决办法有两种:
1.使用ulimit -s 1024*1024命令,将线程栈大小临时设置成1M,经过试验能同时创建2000个线程了。
2.使用pthread_attr_setstacksize在程序中改变线程栈大小。
6. 已知存在的问题及改善方案
1.文件传输速度较慢,而且受网速的影响。
2.服务端建立线程数量有时在发送文件时会超过最大限制数量
....
可优化处:文件传输速率
渐变色,心跳包