基于阿里云服务器+libevent+Qt+mysql+Json等实现仿qq聊天软件


前言

基于阿里云服务器+libevent+qt+mysql+Json等实现仿qq聊天软件,主要功能包括注册、登录、添加好友、聊天(私聊和群聊)、文件传输、建群、加群、好友上下线提醒。
主要分为两大部分:

第一,在linux上实现服务器端chat_server开发

第二,在qt上实现客户端chat_client开发


一、在linux上实现服务器端chat_server开发

在linux上实现服务器端chat_server开发示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

1.1 配置服务器端开发环境

购买阿里云服务器,配置环境,详见文件——在阿里云服务器上搭建开发环境.txt

liunx系统为Ubuntu,修改主机名iZ2vc5mxaf6rjabll9dmt2Z为liuzhensen————主要是为了看着舒服。
root@iZ2vc5mxaf6rjabll9dmt2Z:~# hostnamectl set-hostname liuzhensen
阿里云服务器公网ip
47.108.225.245
设置安全组
自定义TCP,端口范围1/65535,源0.0.0.0/0——所有主机都可访问
将服务器源码chat_server.tar.gz上传到阿里云服务器上测试
apt-get update 更新已安装得软件
apt install lrzsz lrzsz在windows和linux之间传输文件
直接将本地文件拖入到Xshell窗口中,先cd到想到保存的文件中
或者使用下载FileZille客户端——先连接阿里云服务器,可视化Linux文件目录,将本地文件直接拖入到FileZille
解压缩源码
tar -xzf chat_server.tar.gz 得到解压文件夹chat_sever
安装mysql数据库
//apt-get update 更新已安装得软件
apt-get install mysql-client mysql-server libmysqlclient-dev
//可能提示输入密码mysql密码,建议设置为root
密码默认为root
安装libevent
apt-get install libevent-dev
传输数据使用json格式
apt-get install libjsoncpp-dev
在chat_server中建立两个数据库
mysql -u root -p
提示mysql登录输入密码,前面已经设置为root
create database chatgroup;
create database user;
quit或者exit退出数据库
查看阿里云服务器内网地址
ifconfig
inet addr:后面得地址 172.19.18.114
打开server.h文件,修改内网地址定义
vim server.h
测试之前,把原来得编译文件main删除掉
rm -rf main
编译,会生成编译文件main
make
运行
./main
显示: 初始化链表成功
服务器初始化成功 开始监听客户端
以上服务器配置完毕——————————————————————————————
将qt-source放到无中文路径下
删除qt-source中原来的配置文件chat_soft.pro.user
打开qt工程文件chat_soft.pro,修改阿里云服务器的公网地址————47.108.225.245
widget.cpp 中第10行
recvthread.cpp 中第23行
sendthread.cpp 中的第13行
运行qt项目
若服务器显示接受客户端连接,则成功

1.2 创建服务器与客户端接口文档

首先创建一个满足Json数据格式的服务器与客户端传输信息或者交互接口文档,方便查看重要信息且有助于接下来实现各种功能。详见文件——服务器与客户端接口文档.txt

接口文档,Json格式
1、注册
客户端发送:{“cmd”:“register”,“user”:“小明”,“password”:“11111”};
成功服务器返回:{“cmd”:“register_reply”,“result”:“success”};
失败服务器返回:{“cmd”:“register_reply”,“result”:“failure”};
2、登录
客户端发送:{“cmd”:“login”,“user”:“小明”,“password”:“11111”};
登录失败,服务器回复 {“cmd”:“login_reply”,“result”:“user_not_exist”};
登录失败,服务器回复 {“cmd”:“login_reply”,“result”:“password_error”};
登录成功,服务器回复 {“cmd”:“login_reply”,“result”:“success”};
登录成功,回复好友上线提醒 {“cmd”:“friend_login”, “friend”:“小明”}; // 只发送给登录用户“小明”的在线好友,自己上线了
3、添加好友
客户端发送:{“cmd”:“add”, “user”:“小明”, “friend”:“小华”};
如果好友不存在,服务器回复:{“cmd”:“add_reply”, “result”:“user_not_exist”};
如果已经是好友,服务器回复:{“cmd”:“add_reply”, “result”:“already_friend”};
添加成功,服务器回复自己:{“cmd”:“add_reply”, “result”:“success”, “friend”:“小华”};
添加成功,回复对方:{“cmd”:“add_friend_reply”, “result”:“小明”};
4、创建群聊
客户端发送:{“cmd”:“create_group”, “user”:“小明”, “group”:“学习”};
如果群已经存在,回复客户端 {“cmd”:“create_group_reply”, “result”:“group_exist”};
如果群不存在,回复客户端 {“cmd”:“create_group_reply”, “result”:“success”,“group”:“学习”};
5、添加群
客户端发送:{“cmd”:“add_group”, “user”:“小明”, “group”:“学习”};
如果群不存在,服务器回复:{“cmd”:“add_group_reply”, “result”:“group_not_exist”};
如果用户已经在群里里面:{“cmd”:“add_group_reply”, “result”:“user_in_group”};
添加成功,服务器回复:{“cmd”:“add_group_reply”, “result”:“success”,“group”:“学习”};
6、私聊
客户端发送:{“cmd”:“private_chat”,“user_from”:“小明”,“user_to”:“小花”,“text”:“hello”};
如果对方不在线:{“cmd”:“private_chat_reply”,“result”:“offline”};
如果对方在线,服务器转发 :同上
如果对方在线,服务器回复:{“cmd”:“private_chat_reply”,“result”:“success”};
7、群聊
客户端发送:{“cmd”:“group_chat”,“user”:“小明”,“group”:“交流群”,“text”:“hello”}
服务器转发:{“cmd”:“group_chat”,“user”:“小明”,“group”:“交流群”,“text”:“hello”}
服务器回复:{“cmd”:“group_chat_reply”, “result”:“success”}
8、获取群成员
客户端发送:{“cmd”:“get_group_member”,“group”:“交流群”};
服务器回复:{“cmd”:“get_group_member_reply”,“member”:“小李|小张”, “group”:“交流群”}
9、用户下线
客户端发送:{“cmd”:“offline”,“user”:“小明”};
服务器回复所有好友:{“cmd”:“friend_offline”,“friend”:“小明”};
10、文件传输
客户端发送:{“cmd”:“send_file”,“from_user”:“小明”, “to_user”:“小李”,“length”:“10000”, “filename”:“xxx.txt”};
如果对方不在线,服务器回复:{“cmd”:“send_file_reply”,“result”:“offline”};
服务器返回端口号给发送客户端:{“cmd”:“send_file_port_reply”,“port”:8080, “length”:“10000”, “filename”:“xx.txt”};
服务器返回端口号给接收客户端:{“cmd”:“recv_file_port_reply”,“port”:8080, “length”:“10000”, “filename”:“xx.txt”};
如果连接文件服务器超时,返回客户端 {“cmd”:“send_file_reply”, “result”:“timeout”};

1.3 封装数据库类和链表类,用于存储用户和群聊信息

在mysql中,创建user和chatgroup两个数据库, 其中user存储用户信息,为一个用户创建一个表名为用户名的表,表中包含用户的密码password、friend好友、groupchat所在群聊 chatgroup存储群聊信息,为每个群聊创建一张表名为群聊名的表,表中包含群聊的群主owner、群聊成员member
创建online_user和group_info两个链表————————链表访问速度比数据库快,且方便后续功能的实现 其中,online_user存储在线用户的信息,节点为,包括在线用户名和在线用户的缓冲区对象bev; group_info存储群聊信息,节点为,包括群聊名称和群成员链表,其中群成员链表的节点为,只包含群成员姓名

1.4 封装一个服务器类class Server

首先初始化服务器,使其可以监听待连接的客户端:在Server的构造函数中,基于libevent创建事件集合,创建并绑定监听对象,设置监听队列的长度,即最多同时与多少客户端建立TCP连接,开始循环监听,一旦有客户端发起连接,则调用回调函数,在回调函数中创建工作线程来处理该客户端。在某一工作线程中,创建该连接客户端的事件集合,基于该事件集合创建bufferevent缓存区对象,给bufferevent设置回调函数,循环监听集合(监听客户端是否有数据发送过来),一旦从客户端读取到数据,调用回调函数,处理客户端发送的满足Json格式的数据,接下来根据解析到的不同”cmd“,实现服务器端不同的功能,比如注册功能对应server_register(bev, val)函数,当客户端退出或连接断开,释放该连接客户端的事件集合,该工作线程随之退出分离,释放资源。在Server的成员变量中,分别创建一个链表对象static ChatInfo* chatlist和一个数据库对象static ChatDataBase* chatdb,用于访问用户和群聊的相关信息。

1.5 实现注册功能

server_register(bev, val); // 参数1为服务器与该客户端之间的缓存区对象,参数2为客户端发送的信息
首先连接上user数据库,
chatdb->my_database_connect("user");

  • 判断注册用户是否已经存在
    chatdb->my_database_user_exist(val["user"].asString()))

若已经存在,回复客户端注册失败;

若不存在,执行以下函数,将注册用户的用户名和密码,加入到user数据库中;然后回复客户端注册成功。
chatdb->my_database_user_password(val["user"].asString(), val["password"].asString());

  • 关闭user数据库,chatdb->my_database_disconnect();

1.6 实现用户登录功能

首先连接上user数据库,最后再断开连接,同上,接下来涉及到访问user数据库,均需要这两步,不再赘述;

判断登录用户是否已经存在于user数据库中:

若不存在,回复客户端该登录用户不存在;

若存在,判断用户登录密码是否正确:

chatdb->my_database_password_correct(val["user"].asString(), val["password"].asString()
若不正确,回复客户端,输入登录失败,密码不正确,将登录函数结束掉return

若正确:向在线用户链表中加入该登录用户,chatlist->online_user->push_back(u);

获取该登录用户好友friend_list和群聊列表group_list并且返回给客户端,便于qt客户端开发时,显示登录用户的好友和群聊列表,以实现接下来的好友上线提醒、聊天等功能;
chatdb->my_database_get_friend_group(val["user"].asString(), friend_list, group_list)
登录成功后,向该登录用户的所有在线好友,发送自己上线的提醒:遍历friend_list,与 chatlist->online_user的一一匹配,若某好友在线,则向其bev/客户端发送自己上线的提醒

1.7 实现添加好友功能

首先判断用户user——小明将要添加的好友friend小华——是否存在于用户数据库中

若不存在,回复用户小明,你要添加的好友小华不存在,添加好友操作结束return

若存在,再判断两人是否已经是好友关系
chatdb->my_database_is_friend(val["user"].asString(), val["friend"].asString())
若已经是好友关系,回复用户小明,你与添加的好友小华已经是好友关系,添加好友操作结束return

若还不是好友关系:

修改双方的用户表的friend好友字符串,互相添加为好友(注意,此处未考虑好友验证通过的功能,后期优化再实现);
chatdb->my_database_add_new_friend(val["user"].asString(), val["friend"].asString()); chatdb->my_database_add_new_friend(val["friend"].asString(), val["user"].asString());
回复执行添加好友的用户小明,添加好友成功
遍历在线用户链表,判断添加的好友小华是否在线,若在线,回复小华,小明已将你添加为好友。

1.8 实现创建群聊功能

首先连接上chatgroup数据库,使用结束再断开连接,接下来涉及到访问chatgroup数据库,均需要这两步,不再赘述;

判断群是否已存在chatdb->my_database_group_exist(val["group"].asString())

若已经存在,回复客户端用户,该群已存在

若不存在,

在chatgroup中创建该群聊,初始化群聊名,群主,群成员信息

chatdb->my_database_add_new_group(val["group"].asString(), val["user"].asString())
在user中,将新建群聊加入到创建该群聊用户的群聊字符串中

chatdb->my_database_user_add_group(val["user"].asString(), val["group"].asString());
将新建群聊加入到群信息链表中

chatlist->info_add_new_group(val["group"].asString(), val["user"].asString());
回复客户端用户,群聊创建成功

1.9 添加群聊 server_add_group

首先判断该群聊是否存在于chatgroup数据库中

如果不存在,回复客户端用户,你要添加的群聊不存在,return;

如果存在,判断该用户是否已经在该群聊中,此时使用群聊信息链表,访问速度相比于数据库更快

chatlist->info_user_in_group(val["group"].asString(), val["user"].asString())
如果该用户已经存在于该群聊中,回复客户端用户,你已经存在于该群聊中,return;

若不在该群聊中
在user中,将该群聊加入到该用户的群聊字符串中;
在chatgroup中,将该用户加入到该群聊的群成员字符串中;
修改群聊信息链表,将该用户加入到该群聊节点的群成员链表中
chatlist->info_group_add_user(val["group"].asString(), val["user"].asString());
回复客户端用户,添加群聊成功

1.10 私聊 server_private_chat

首先判断好友是否在线————遍历在线用户链表,获得其缓存区对象to_bev

struct bufferevent* to_bev = chatlist->info_get_friend_bev(val["user_to"].asString());
如果NULL == to_bev,说明好友不在线,回复发送方,return
如果好友在线,将发送方用户缓存区对象bev中的内容val转换为字符串s,再转发给好友的缓存区对象to_bev,再回复发送方,私聊发送成功。

1.11 群聊 server_group_chat

首先遍历群聊信息链表找到该群聊
遍历该群聊的群成员链表
遍历在线用户链表,找到每个群成员(包括发送方自己)的缓存区对象to_bev,若不为空,转发发送方的bev,最后回复发送方,群聊发送成功

1.12 获取群聊成员 server_get_group_member

遍历群聊信息链表,获取某群聊的群聊成员字符串member,将其返回给调用该函数的客户端
string member = chatlist->info_get_group_member(val["group"].asString());

1.13 用户下线 server_user_offline

首先从用户在线链表online_user中删除该用户

获取该用户好友friend_list

向在线好友,发送该用户的下线提醒

1.14 发送文件 server_send_file

首先判断对方是否在线
若不在线,回复发送方,接收方不在线,return;
若在线:
启动新线程,创建文件服务器,处理该文件传输任务(不影响聊天等功能),引用方式获得发送方客户端和接收方客户端相对于文件服务器的fd
thread send_file_thread(send_file_handler, val["length"].asInt(), port, &from_fd, &to_fd);
将文件服务器端口号返回给发送客户端,发送客户端根据端口号向文件服务器发起连接;
判断发送客户端连接文件服务器是否成功,文件服务器中的accept()一旦被执行,对应的fd就会被修改(传入时为0),设置最长连接时间为10s,即等待10秒之后,from_fd<=0,说明仍未连接成功,判定为连接超时,取消文件服务器所在的线程,返回发送客户端连接文件服务器超时;相同方法连接接收客户端与文件服务器。

文件服务器一边从f_fd接收,一边向t_fd发送,文件发送结束后,关闭发送与接收客户端与文件服务器之间的描述字,关闭该文件服务器,线程分离,释放资源
send_file_thread.detach();

二、未完待续

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值