1:服务器类,基于muduo库开发
//moduo网络库
//Tcpserver服务器
//TcpClient客户端
// epoll + 线程池
//网络的I/O代码和业务代码区分开
//用户的连接和断开,用户的可读写事件
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h> //
#include <iostream>
#include <functional>
#include <string>
using namespace std;
using namespace muduo::net;
using namespace muduo;
using namespace placeholders;
/*基于muduo网络库开发服务器程序
1:组合tcpserver对象
2:创建EventLoop事件循环对象的指针
3:明确tcpserver构造函数参数,输出chatserver
4:服务器类的构造函数中,注册处理连接的回调函数和处理读写事件的回调函数
5:设置合适的服务端线程数量,muduo自己分配io和work线程
*/
class CharServer {
public:
CharServer(EventLoop* loop, //事件循环
const InetAddress& listenAddr, //IP+Port
const string& nameArg) //服务器的名称 ,线程名字
:server_(loop, listenAddr, nameArg),
loop_(loop) {
//给服务器注册用户连接的创建和断开回调
//_1 是参数const TcpConnectionPtr&
//关注连接的状态
server_.setConnectionCallback(std::bind(&CharServer::onConnection,this,_1));
//给服务器注册用户读写事件回调
//关注事件读写的状态
server_.setMessageCallback(std::bind(&CharServer::onMessage, this, _1, _2,_3));
//设置服务器段的线程数量; 1个i/o 3个work
server_.setThreadNum(4);
}
//开启事件循环
void start() {
server_.start();
}
private:
//专门处理用户的连接创建和断开 epoll listenfd accpet
//成员方法有this指针 这里onConnection会有2个参数,this,conn
void onConnection(const TcpConnectionPtr& conn) {
if (conn->connected()) {
cout << conn->peerAddress().toIpPort() << "-->" << conn->localAddress().toIpPort() << "online" << endl;
}
else {
cout << conn->peerAddress().toIpPort() << "-->" << conn->localAddress().toIpPort() << "offline" << endl;
conn->shutdown(); //close(fd); 服务端的fd回收
loop_->quit(); //退出epoll
}
}
//默认有this指针
//处理用户的读写事件
void onMessage(const TcpConnectionPtr& conn, //连接
Buffer* buffer, //缓冲区
Timestamp time) { //接收数据的时间信息
string buf = buffer->retrieveAllAsString();
cout << "recv data:" << buf << "time:" << time.toString() << endl;
conn->send(buf);
}
TcpServer server_;
EventLoop* loop_; //epoll
};
int main() {
EventLoop loop; //epoll
InetAddress listenAddr("127.0.0.1",6000); //IP+Port
CharServer server(&loop, listenAddr,"ChatServer");
server.start(); //listenfd epoll_ctl->epoll 添加套接字到epoll
loop.loop(); //epoll.wait 阻塞的方式等待新用户连接 已连接用户的读写事件 回调onConnection 和onMessage
return 0;
}
2:Redis,服务器中间件
// redis.hpp
class Redis
{
public:
Redis();
~Redis();
// 连接redis服务器
bool connect();
// 向redis指定的通道channel发布消息
bool publish(int channel, string message);
// 向redis指定的通道subscribe订阅消息
bool subscribe(int channel);
// 向redis指定的通道unsubscribe取消订阅消息
bool unsubscribe(int channel);
// 在独立线程中接收订阅通道中的消息
void observer_channel_message();
// 初始化向业务层上报通道消息的回调对象
void init_notify_handler(function<void(int, string)> fn);
private:
// hiredis同步上下文对象,负责publish消息
redisContext *_publish_context;
// hiredis同步上下文对象,负责subscribe消息 //阻塞
redisContext *_subcribe_context;
// 回调操作,收到订阅的消息,给service层上报 //有消息发生,
function<void(int, string)> _notify_message_handler;
};
// redis.cpp
#include "redis.hpp"
#include <iostream>
using namespace std;
Redis::Redis()
: _publish_context(nullptr), _subcribe_context(nullptr)
{
}
Redis::~Redis()
{
if (_publish_context != nullptr)
{
redisFree(_publish_context);
}
if (_subcribe_context != nullptr)
{
redisFree(_subcribe_context);
}
}
bool Redis::connect()
{
// 负责publish发布消息的上下文连接
// Redis的默认端口是6379
_publish_context = redisConnect("127.0.0.1", 6379);
if (nullptr == _publish_context)
{
cerr << "connect redis failed!" << endl;
return false;
}
// 负责subscribe订阅消息的上下文连接
_subcribe_context = redisConnect("127.0.0.1", 6379);
if (nullptr == _subcribe_context)
{
cerr << "connect redis failed!" << endl;
return false;
}
// 在单独的线程中,监听通道上的事件,有消息给业务层进行上报 subscribe 阻塞等待事件发生
thread t([&]() {
observer_channel_message();
});
t.detach();
cout << "connect redis-server success!" << endl;
return true;
}
// 向redis指定的通道channel发布消息 不会阻塞
bool Redis::publish(int channel, string message)
{
//下面三个函数的调用 把命令写到本地缓存,
redisReply *reply = (redisReply *)redisCommand(_publish_context, "PUBLISH %d %s", channel, message.c_str());
if (nullptr == reply)
{
cerr << "publish command failed!" << endl;
return false;
}
freeReplyObject(reply);
return true;
}
// 向redis指定的通道subscribe订阅消息
bool Redis::subscribe(int channel)
{
// SUBSCRIBE命令本身会造成线程阻塞等待通道里面发生消息,这里只做订阅通道,不接收通道消息
// 通道消息的接收专门在observer_channel_message函数中的独立线程中进行
// 只负责发送命令,不阻塞接收redis server响应消息,否则和notifyMsg线程抢占响应资源
if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "SUBSCRIBE %d", channel))
{
cerr << "subscribe command failed!" << endl;
return false;
}
// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)
int done = 0;
while (!done)
{
if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done))
{
cerr << "subscribe command failed!" << endl;
return false;
}
}
// redisGetReply 阻塞的方式等待远端响应,
return true;
}
// 向redis指定的通道unsubscribe取消订阅消息
bool Redis::unsubscribe(int channel)
{
if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "UNSUBSCRIBE %d", channel))
{
cerr << "unsubscribe command failed!" << endl;
return false;
}
// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)
int done = 0;
while (!done)
{
if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done))
{
cerr << "unsubscribe command failed!" << endl;
return false;
}
}
return true;
}
// 在独立线程中接收订阅通道中的消息
void Redis::observer_channel_message()
{
redisReply *reply = nullptr;
//redisGetReply 函数会阻塞, 死循环,
while (REDIS_OK == redisGetReply(this->_subcribe_context, (void **)&reply))
{
// 订阅收到的消息是一个带三元素的数组
if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr)
{
// 给业务层上报通道上发生的消息
_notify_message_handler(atoi(reply->element[1]->str) , reply->element[2]->str);
}
freeReplyObject(reply);
}
cerr << ">>>>>>>>>>>>> observer_channel_message quit <<<<<<<<<<<<<" << endl;
}
void Redis::init_notify_handler(function<void(int,string)> fn)
{
this->_notify_message_handler = fn;
}
总结:
- Redis的默认端口是6379。
- Redis三个成员变量,一个负责发布消息,一个负责订阅消息。一个负责给service层上报。
- Redis独立线程 接受publish信息,当有其他服务器发布消息后,
- 服务器先查看自己 unordered_map<int, TcpConnectionPtr> 是否有该用户,没有会调用Redis,发送消息 publish, 订阅该消息的返回值
redis_.publish(toid, js.dump()); //给其他服务器发消息,其他服务器订阅
REDIS_OK == redisGetReply(this->_subcribe_context, (void **)&reply) 可以回调,上报给服务器。
3:Mysql存储层
//User.hpp
class User
{
public:
User(int id = -1, string name = "", string pwd = "", string state = "offline")
{
id_ = id;
name_ = name;
password_ = pwd;
state_ = state;
}
~User(){}
void SetId(int id)
{
id_ = id;
}
void SetName(string name)
{
name_ = name;
}
void SetPassWord(string pwd)
{
password_ = pwd;
}
void SetState(string state)
{
state_ = state;
}
int GetId()
{
return id_;
}
string GetName()
{
return name_;
}
string GetPassWord()
{
return password_;
}
string GetState()
{
return state_;
}
private:
int id_;
string name_;
string password_;
string state_;
};
// UserModel.h
#pragma once
#include "User.hpp"
// User表的数据操作类
// orm层 数据库的表增删改查
class UserModel
{
public:
UserModel(/* args */){};
~UserModel(){};
bool insert(User &user); // user表增加
User query(int id); // 用户号码,查询用户信息
bool UpdateState(User user); // 更新用户的状态信息
void ResetState(); // 重置用户的状态信息
private:
/* data */
};
//UserModel.cpp
#include "UserModel.hpp"
#include "db.h"
#include <iostream>
using namespace std;
bool UserModel::insert(User &user)
{
// 组装sql语句
char sql[1024] = {0};
sprintf(sql, "insert into user(name,password,state) values('%s','%s','%s')", user.GetName().c_str(), user.GetPassWord().c_str(), user.GetState().c_str());
MySQL mysql;
if (mysql.connect())
{
if (mysql.update(sql))
{
// 获取用户成功的主键ID
user.SetId(mysql_insert_id(mysql.GetConnection()));
return true;
}
}
return false;
}
User UserModel::query(int id)
{
// 组装sql语句
char sql[1024] = {0};
sprintf(sql, "select * from user where id = %d", id);
MySQL mysql;
if (mysql.connect())
{
MYSQL_RES *res = mysql.query(sql);
if (res != nullptr)
{
MYSQL_ROW row = mysql_fetch_row(res);
if (row != nullptr)
{
User user;
user.SetId(atoi(row[0]));
user.SetName(row[1]);
user.SetPassWord(row[2]);
user.SetState(row[3]); // 0 1 2 3 对应字段
mysql_free_result(res);
return user;
}
}
}
return User();
}
bool UserModel::UpdateState(User user)
{
// 组装sql语句
char sql[1024] = {0};
sprintf(sql, "update user set state = '%s' where id = %d", user.GetState().c_str(), user.GetId());
MySQL mysql;
if (mysql.connect())
{
if (mysql.update(sql))
{
return true;
}
}
return false;
}
void UserModel::ResetState()
{
// 组装sql语句
char sql[1024] = "update user set state = 'offline' where state = 'online'";
MySQL mysql;
if (mysql.connect())
{
mysql.update(sql);
}
}
总结
- 数据库的ORM设计,一个表对应一个ORM类,对象映射关系,
- sudo mysql -u root -p,依次输入用户密码,mysql密码
- 一些linux下mysql命令
(1):查看数据库:SHOW DATABASES;
(2):选择数据库: USE database_name, 查找表:SHOW TABLES;
(3): 查看表结构:DESCRIBE table_name;