主机Ping通虚拟机的前提:
1.NAT模式下,需要对应网络的设置和外面的设置一样 IP、网关、子网掩码
Linux: ubuntu18.04
安装Json第三方库 thirdparty
进行序列化和反序列化测试 testjson
msg_type:2 //消息类型 (登录消息、注册消息、添加好友、添加群聊、好友聊天、群组聊天)
from:xxx //来自哪里
to:xxx //发送到哪里
msg:xxx //消息内容
通过网络TCP发送的都是字节流,所以对于这样的消息结构,要进行数据的序列化(转成json字节流发送给网络)和反序列化(远端接收到字节流,上报给应用,应用把json字节流反序列化成这样的消息结构)
xml节点的元素和标签特别多,浪费空间,现在protobuf和json多。
json数据序列化:
通过json.dump()函数将json数据对象转换为字符串 ,通过网络TCP发送
#include "json.hpp"
using json = nlohmann::json;
#include
#include
#include
#include
using namespace std;
//json序列化示例1
string func1()
{
json js;//定义一个json类型的对象//添加数组
js["msg_type"] = 2;
js["from"] = "linzeyu";
js["to"] = "zhang san";
js["msg"] = "hello, are you ok now?";
string sendBuf = js.dump();//输出的意思
//cout<
return sendBuf;
}
//json序列化示例2
string func2()
{
json js;
//添加数组
js["id"] = {1, 2, 3, 4, 5};
//添加key-value
js["name"] = "zhang san";
//添加对象
js["msg"]["zhang san"] = "hello world";
js["msg"]["liu shuo"] = "hello china";
//上面等同于下面这句一次性添加数组对象
js["msg"] = {{"zhang san", "hello world"}, {"liu shuo", "hello china"}};
//cout << js << endl;
return js.dump();
}
//json序列化示例代码3
string func3()
{
json js;
//直接序列化一个vector容器
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(5);
js["list"] = vec;
//直接序列化一个map容器
map<int, string> m;
m.insert({1, "黄山"});
m.insert({2, "华山"});
m.insert({3, "泰山"});
js["path"] = m;
string sendBuf = js.dump(); // json数据对象 =》序列化 json字符串
//cout<
return sendBuf;
}
int main()
{
//func1();
//unc2();
func3();
return 0;
}
结果:
底层链式哈希表 无序
json数据反序列化:
通过parse函数将网络上接收的字符串(字节流)转为json数据对象甚至容器
int main()
{
string recvBuf = func1();
//数据的反序列化 json字符串 =》反序列化 数据对象(看作容器,方便访问)
json jsbuf = json::parse(recvBuf);
cout<<jsbuf["msg_type"]<<endl;
cout<<jsbuf["from"]<<endl;
cout<<jsbuf["to"]<<endl;
cout<<jsbuf["msg"]<<endl;
return 0;
}
结果:
总结:
采用json的原因
(1)采用C++11标准编写的
(2)可以和STL容器相互转化
安装boost库
安装muduo库
网络服务器运行测试 testmuduo
muduo网络库的底层就是epoll加linux的pthread线程库。
/*
muduo网络库给用户提供了两个主要的类
TcpServer: 用于编写服务器程序的
TcpClient: 用于编写客户端程序的
epoll + 线程池
好处:能够把网络I/O的代码和业务代码区分开
业务代码就是:用户的连接和断开,用户的可读写事件
我们只需要关注业务代码,什么时候发生和如何监听这些事情的发生由muduo库去做
*/
#include
#include //事件循环
#include
#include //绑定器
#include
using namespace std;
using namespace muduo;
using namespace muduo::net;
using namespace placeholders;
//muduo的名字空间作用域
/*
基于muduo网络库开发服务器程序
步骤如下:
1.组合TcpServer对象
2.创建EventLoop事件循环对象的指针
3.明确TcpServer构造函数需要什么参数,输出ChatServer的构造函数
4.在当前服务器类的构造函数当中,注册处理连接的回调函数和处理读写时间的回调函数
5.设置合适的服务端线程数量,muduo库会自己分配I/O线程和worker线程
*/
class ChatServer//TCPServer
{
public:
ChatServer(EventLoop *loop, //事件循环
const InetAddress &listenAddr, //muduo封装好的,绑定IP+Port
const string &nameArg)//给TCPserver一个名字
: _server(loop, listenAddr, nameArg), _loop(loop)//没有默认构造哦
{
//给服务器注册用户连接的创建和断开的回调,回调就是对端的相应事件发生了告诉网络库 ,然后网络库告诉我 ,我在回调函数开发业务
_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1));//绑定this对象到这个方法中,_1是参数占位符
//给服务器注册用户读写事件的回调
_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3));//绑定this对象到这个方法中
//设置服务器端的线程数量 1个I/O线程(监听新用户的连接事件), 3个worker线程
//不设置的话,就1个线程而已,要处理连接又要处理业务
_server.setThreadNum(4);//设置4个线程,1个I/O线程,3个worker线程
}
void start()//开启事件循环
{
_server.start();
}
private:
//专门处理:用户的连接创建和断开 epoll listenfd accept
//如果有新用户的连接或者断开,muduo库就会调用这个函数
void onConnection(const TcpConnectionPtr &conn)
{
if (conn->connected())//连接 , peerAddress()对端的地址 localAddress() 本地的地址
{
cout << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " state:online" << endl;
}
else//断开
{
cout << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " state:offline" << endl;
conn->shutdown();//相当于这些close(fd)
//_loop->quit();
}
}
//专门处理:用户的读写事件,muduo库去调用这个函数
void onMessage(const TcpConnectionPtr &conn, //连接,通过这个连接可以读写数据
Buffer *buffer, //缓冲区,提高数据收发的性能
Timestamp time) //接收到数据的时间信息
{
string buf = buffer->retrieveAllAsString();//收到的数据放到这个字符串中
cout << "recv data:" << buf << " time:" << time.toFormattedString() << endl;
conn->send(buf);//返回 ,收到什么发送什么
}
TcpServer _server;//第一步
EventLoop *_loop; //第二步相当于 epoll 事件循环的指针,有事件发生,loop上报
};
int main()
{
EventLoop loop;//相当于像是创建了epoll
InetAddress addr("127.0.0.1", 6000);//IP地址,端口号
ChatServer server(&loop, addr, "ChatServer");
server.start();//listenfd通过 epoll_ctl 添加到 epoll
loop.loop();//相当于epoll_wait,以阻塞方式等待新用户连接,已连接用户的读写事件等
}
CMakeLists 创建及基本语法
一级 chatserver
二级 src
三级 server client
mysql安装
表设计
提供了一个model类,专门对数据库代码进程封装,数据层和业务层分开
bin: 存放可执行文件
build:存放cmake生成的编译文件
include:存放头文件(.h)
src:存放源码(.cpp)
test:存放测试示例
thirdparty:存放第三方库(json)
autobuild.sh:shell脚本 一键运行
CMakeLists:cmake编译
README:工程简介
1.网络模块代码 chatserver
2.业务模块代码 chatservice
3.网络模块和业务模块降级耦合
4.网络模块 分发业务事件回调操作
5.mysql数据库代码封装
6.model数据层代码框架
7.用户注册业务
8.用户登录业务
9.线程安全 互斥锁
解析json字符串,得到了name 和 pwd, 产生对应的id号
然后set一下,把id 和 pwd 传入到mysql中,
注册完成以后的话需要返回注册成功的消息
解析json字符串,得到id和pwd
在mysql中使用query查询id号
判断密码是否正确,正确的话,在mysql中把这个id号由offline改为online状态
msgid 消息id
id 本机id号
from 名字
to 对方id号
msg 聊天消息内容
首先判断对方是否在线,在线需要找到对应用户的connection
不在线,把消息存到历史消息中,等待对方上线发送给对方
管理者和普通人员
多表联合查询
多了一个role角色
group name
group id
querygroup 查询群组信息
addgroup函数
querygroupusers
userid groupid
数据库中专门有一个offline messqge存储离线消息
id
message
登录成功后检查用户是否有离线消息,有的话载入model类
服务器间的通信成为问题,服务器之间直连会造成socket资源负载过重,引入redis的MQ消息队列处理服务器之间的通信请求
MQ消息队列