集群聊天服务器
项目地址:Focuspresent/ChatServer (github.com)
环境搭建(基于Unbuntu20.04)
boost库安装
sudo apt install libboost-all-dev
mysql开发库安装
sudo apt install libmysqlclient-dev
muduo库安装
先需要安装cmake以及boost库
sudo apt install cmake
下载安装包,并解压
wget https://github.com/chenshuo/muduo/archive/refs/tags/v2.0.2.zip
mv v2.0.2.zip muduo-2.0.2.zip
unzip muduo-2.0.2.zip
修改CMakeLists.txt
cd muduo-2.0.2
vim CMakeLists.txt
- 找到23行的
-Werror
注释掉解释 - 找到13行的
option(MUDUO_BUILD_EXAMPLES "Build Muduo examples" ON)
注释掉,不需要测试样例
编译,安装
# 当前在muduo-2.0.2 目录下
./build.sh
./build.sh install
cd ..
# 执行后
➜ Downloads ls
build muduo-2.0.2 muduo-2.0.2.zip
之后,需要进入安装后的目录(位于muduo-2.0.2同一级),将头文件和库文件放到系统头文件路径和系统库文件路径,否则,使用时需要通过编译选项(-I
,-L
)告知编译器
# 拷贝头文件
cd build/release-install-cpp11/include
sudo mv muduo/ /usr/include
# 拷贝库文件
cd ../lib
sudo mv * /usr/local/lib
测试
#include <muduo/net/TcpServer.h>
#include <muduo/base/Logging.h>
#include <boost/bind.hpp>
#include <muduo/net/EventLoop.h>
// 使用muduo开发回显服务器
class EchoServer
{
public:
EchoServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr);
void start();
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn);
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp time);
muduo::net::TcpServer server_;
};
EchoServer::EchoServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr)
: server_(loop, listenAddr, "EchoServer")
{
server_.setConnectionCallback(
boost::bind(&EchoServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&EchoServer::onMessage, this, _1, _2, _3));
}
void EchoServer::start()
{
server_.start();
}
void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
}
void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp time)
{
// 接收到所有的消息,然后回显
muduo::string msg(buf->retrieveAllAsString());
LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
<< "data received at " << time.toString();
conn->send(msg);
}
int main()
{
LOG_INFO << "pid = " << getpid();
muduo::net::EventLoop loop;
muduo::net::InetAddress listenAddr(8888);
EchoServer server(&loop, listenAddr);
server.start();
loop.loop();
}
编译(muduo_net在muduo_base前面)
g++ main.cpp -lmuduo_net -lmuduo_base -lpthread -std=c++11
运行
./a.out
另启一个终端,并向端口发送数据
echo "hello world" | nc localhost 8888
显示的数据
20240815 14:23:48.762392Z 3639 INFO pid = 3639 - main.cpp:61
20240815 14:24:01.374976Z 3639 INFO TcpServer::newConnection [EchoServer] - new connection [EchoServer-0.0.0.0:8888#1] from 127.0.0.1:34428 - TcpServer.cc:80
20240815 14:24:01.375027Z 3639 INFO EchoServer - 127.0.0.1:34428 -> 127.0.0.1:8888 is UP - main.cpp:42
20240815 14:24:01.375066Z 3639 INFO EchoServer-0.0.0.0:8888#1 echo 12 bytes, data received at 1723731841.375035 - main.cpp:53
20240815 14:24:10.038320Z 3639 INFO EchoServer - 127.0.0.1:34428 -> 127.0.0.1:8888 is DOWN - main.cpp:42
20240815 14:24:10.038368Z 3639 INFO TcpServer::removeConnectionInLoop [EchoServer] - connection EchoServer-0.0.0.0:8888#1 - TcpServer.cc:109
nginx 配置tcp负载均衡
源码安装
- 安装
➜ Downloads mkdir nginx
➜ Downloads cd nginx
➜ nginx wget https://nginx.org/download/nginx-1.26.2.tar.gz
➜ nginx tar -zvxf nginx-1.26.2.tar.gz
# 需要什么依赖,就去装
➜ nginx-1.26.2 ./configure --with-stream
➜ nginx-1.26.2 make
➜ nginx-1.26.2 sudo make install
➜ nginx-1.26.2 cd /usr/local/nginx
➜ nginx cd conf
# 修改配置文件
➜ conf sudo vim nginx.conf
- 部分配置文件
stream {
upstream MyServer {
server 127.0.0.1:9001 weight=1;
server 127.0.0.1:9002 weight=1;
}
server {
listen 9000;
proxy_pass MyServer;
tcp_nodelay on;
}
}
nginx需要的命令
# 启动
sudo /usr/local/nginx/sbin/nginx
# 停止
sudo /usr/local/nginx/sbin/nginx -s stop
# 平滑加载(重新读取配置文件)
sudo /usr/local/nginx/sbin/nginx -s reload
redis
redis环境
sudo apt install redis-server
redis开发库
➜ Downloads git clone https://github.com/redis/hiredis.git
➜ Downloads cd hiredis
➜ hiredis git:(master) make
➜ hiredis git:(master) sudo make install
# 如果之后hiredis链接失败
sudo ldconfig /usr/local/lib
数据库设计
数据库名(chat)
- 用户表(users)
列名 | 类型 | 描述 | 约束 |
---|---|---|---|
id | int | 用户id | 主键,自增 |
username | varchar(32) | 用户名 | 唯一,非空 |
password | varchar(32) | 用户密码 | 非空 |
state | enum('offline','online') | 是否在线 | 默认是'offline' |
- 好友表(friends)
列名 | 类型 | 描述 | 约束 |
---|---|---|---|
userid | int | 用户id | 非空,联合主键 |
friendid | int | 好友id | 非空,联合主键 |
state | enum('pass','unverify') | 这条好友消息的状态 | 默认是'unverify' |
- 离线消息表(offlinemessages)
列名 | 类型 | 描述 | 约束 |
---|---|---|---|
userid | int | 用户id | 非空,普通索引 |
offlinemessage | varchar(512) | 离线的消息 | 非空 |
- 群组表(groups)
列名 | 类型 | 描述 | 约束 |
---|---|---|---|
id | int | 群组id | 主键,自增 |
groupname | varchar(64) | 群组名 | 唯一,非空 |
groupdesc | varchar(128) | 群组描述 |
- 群组用户表(groupusers)
列名 | 类型 | 描述 | 约束 |
---|---|---|---|
groupid | int | 群组id | 非空,联合主键 |
userid | int | 用户id | 非空,联合主键 |
role | enum('owner','admin','normal') | 用户在群组中的角色 | 默认是'normal' |
项目架构
├── bin
├── include
│ ├── client
│ └── server
│ ├── db
│ └── model
├── lib
├── src
│ ├── client
│ └── server
│ ├── db
│ └── model
├── test
└── thirdparty
└── jsoncpp
服务端开发
网络层
基于muduo开发的网络类,可以根据事件的发生,调用先前注册好的回调函数,尤其是连接以及处理消息,处理消息可以调用业务类先前注册好的回调函数,根据消息类型进行处理,使用的是json类型
业务层
业务类,主要负责业务层回调函数的实现
登录
凭借先前注册的得到的账号以及账号密码,向服务端发起登录请求,处理后,会返回错误码以及相关信息
注册
用户输入账户名(唯一)以及账号密码(非空),向服务端发起注册请求,处理后,会返回账号,需要记住账号
点对点聊天
该业务没有好友限制,只需要两个客户的账号就行,假如接受端不在线,接受端的信息会被存储到离线消息中,等待接受端上线后,存储在数据库中的离线消息会被清空
添加好友
- 添加好友请求
一个客户向另一个客户发起添加好友请求,需要对方的账号即可,然后由服务端处理,转发给对方,等待对方同意
- 添加好友验证
接受到好友请求的客户,可以选择通过或者不通过,通过后,两方的好友列表(服务端)都会新增对方的信息,不通过则没有
群组聊天
- 创建群组
需要由客户端填写群组名(唯一)和群组描述,向服务端发起请求,成功后会返回群组号
- 添加群组
只需填写群组号即可,向服务端发起请求,不需要其他人的验证
- 群组聊天
向某个群组号填写发送的信息,向服务端发起请求,由服务端来转发除发出者之外的所有群员,离线的群员,消息会存储在离线消息表
刷新
由客户端发起,向服务端更新与当前客户相关的信息,比如,好友状态以及群组状态等
客户端开发
多线程程序,主线程负责读取用户的输入,封装成json数据,并向服务器发出请求,子线程负责读取服务端的响应,以及解析json数据,并把相应的数据打印在界面上
- 启动后
*****************************
login: 1
register: 2
exit: 3
*****************************
- 登录后
******主界面******
******命令列表******
groupchat:群组聊天groupchat:groupid:msg
creategroup:创建群组creategroup:groupname:groupdesc
addgroup:添加群组addgroup:groupid
verifyfriend:验证好友请求verifyfriend:to:[y/n]
addfriend:发送添加好友请求addfriend:to
logout:退出登录
show:显示当前登录的用户信息
refresh:刷新信息refresh
chat:点对点聊天chat:to:msg
help:显示支持的命令
遇到的问题
vscode的cmake插件使用
- 描述
本来想使用vscode的cmake插件的智能提示,发现在CMakelists.txt下没有智能提示,并且侧边栏右击后,无法展示cmake插件的具体信息
- 解决方法
没有智能提示,是没有配置vscode的配置下的cmake路径,配置.vscode/settings.json
文件中的cmake.cmakePath
即可
无法显示插件,是因为我之前使用了Makefile Tools
插件,将其禁用即可
mysql开发库的使用
- 描述
在测试查询不存在的账号或者群组时,服务端异常退出
- 解决方法
这是我之前代码的缺陷之处,只对MYSQL_RES*
进行了成功判断,然后使用mysql_fetch_row()
读取返回结果,但其实,成功了也有可能是空行,所以使用mysql_fetch_row()
读取出来的MYSQL_ROW
也要进行空指针判断,不是空指针,才能读取到数据