项目由三个:服务器、客户端、负载均衡器
一、服务器
服务器分为四层:socket、service、model、mysql,除此之外还有main.cpp、json.cpp、public.cpp等文件
注:虽然网络层是调用muduo层,数据库层是调用mysql数据库,但我们仍需要封装库函数,并构成独立党的类,而不是在其他模块中随意去调用
- 网络层:
成员变量:muduo::net::TcpServer _server; 作用:选定网络协议
成员函数:
(1)构造:绑定IP地址和端口号,绑定链接断开的回调函数onConnection,绑定服务层的回调函数onMessage。后两者onConnection和onMessage还需封装成私有成员方法。
(2)start:启动,由于构造已经绑定了回调函数,开始后就可以处理业务了。
class Socket
{
public:
Socket(muduo::net::EventLoop *loop, const muduo::net::InetAddress &addr)
:_server(loop, addr, "ChatServer")
{
_server.setConnectionCallback(bind(&Socket::onConnection, this, _1));
_server.setMessageCallback(bind(&Socket::onMessage, this, _1, _2, _3));
}
void start() {_server.start();}
private:
muduo::net::TcpServer _server;
void onConnection(const muduo::net::TcpConnectionPtr &con);
void onMessage(const muduo::net::TcpConnectionPtr &con, muduo::net::Buffer *buf, muduo::Timestamp time);
};
- service层
服务层就要具体处理业务,这里总的框架是基类派生类:基类(BaseService)、单服务器(SingService)、多服务器(ClusterService)
2.1 BaseService
按照以前的思维,服务层业务处理函数,网络层通过switch语句调用,但是这次,我们模仿MVC模型实现这个功能。
service层(MVC模型)成员函数:service层基类函数成员为unordered_map<消息类型,处理函数>,通过键值对的方法由消息类型找相应的处理方法。
订阅-》构造函数:将消息类型和相应的处理函数插入哈希表中,处理函数订阅相应的消息类型
发布-》发布肯定不可能在服务层,它实现在网络层,由网络层发布消息,具体就在上面的两个回调函数中,由键访问值。
class BaseService
{
using Handler = std::function<void(const muduo::net::TcpConnectionPtr&, json&, muduo::Timestamp)>;
public:
BaseService()
{
_handlerMap[MSG_LOGIN] = bind(&BaseService::Login, this, _1, _2, _3);//这里绑定this,不然类型就出错了
_handlerMap[MSG_REG] = bind(&BaseService::Register, this, _1, _2, _3);
_handlerMap[MSG_ADD_FRIEND] = bind(&BaseService::addFriend, this, _1, _2, _3);
_handlerMap[MSG_ONE_CHAT] = bind(&BaseService::oneChat, this, _1, _2, _3);
}
virtual void Login(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time) = 0;
virtual void Register(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time) = 0;
virtual void addFriend(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time) = 0;
virtual void oneChat(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time) = 0;
std::unordered_map<int, Handler> handler()const//获取map{return _handlerMap;}
private:
std::unordered_map<int, Handler> _handlerMap;
};
2.2 SingService:
(1)业务处理函数是虚函数,派生类会覆盖。(2)由于网络层中开辟多个线程来处理业务,但是根本不需要构造多个服务层对象,所以SingService实现为单例模式(懒汉模式)。
class SingService : public BaseService
{
public:
static SingService* getInstance()
{
static SingService instance;
return &instance;
}
SingService(const SingService&) = delete;
SingService operator=(const SingService&) = delete;
virtual void Login(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time){}
virtual void Register(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time){}
virtual void addFriend(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time){}
virtual void oneChat(const muduo::net::TcpConnectionPtr& con, json& js, muduo::Timestamp time){}
private:
std::unique_ptr<userModel> userModelPtr;
std::unique_ptr<friendModel> friendModelPtr;
SingService() :userModelPtr(new userModel()), friendModelPtr(new friendModel()){}
};
2.3 ClusterService
多服务层是将单服务器的代码粘贴过来再进行扩展。试想如果聊天的双方不在同一台服务器上,那么就必须要一个消息中间站radis。
radis同service比较类似,service是转发客户端过来的消息,radis也是转发消息,不过它转发的是另一台服务器的消息。
radis的网络模块和MVC模型,源码都已经帮我们实现好了,同理,不去直接调用,而是封装成一个类myRadis。
radis看作是服务器,myRadis是客户端。
成员函数:
redisContext* _context; 订阅通道
redisContext* _pubcontext; 发布通道
channelHandler _channelHandler; 回调函数
(1)由于radis要向myRadis转发,所以myRadis中需要提供成员方法connect,作用是将myRadis的地址端口号发送过去。
(2)subscribe、unsubscribe、publish分别是订阅、取消订阅、发布。注意,这里不要因为服务器即使发布方又是订阅方就共用一个通道。试想一下,MVC模式大部分情况都是,发布方是发布方,订阅放是订阅方,在设计radis时应该把他们发到一起吗。
(3)notifyMsg检测通道消息,注意,它必须放到单独的线程里,不然会阻塞线程,试想一下,线程一直在这里检测消息,那么网络层的消息过来怎么办。
class RedisServer
{
public:
RedisServer(){}
~RedisServer(){}
bool connect()
{
this->_context = redisConnect(redisHost.c_str(), port);
this->_pubcontext = redisConnect(redisHost.c_str(), port);
}
void subscribe(int channel)
{
redisAppendCommand(this->_context, "SUBSCRIBE %d", channel))
}
void publish(int channel, std::string msg)
{
redisReply* reply = (redisReply*)redisCommand(this->_pubcontext, "PUBLISH %d %s", channel, msg.c_str());
}
void unsubscribe(int channel)
{
redisReply* reply = (redisReply*)redisCommand(this->_context, "UNSUBSCRIBE %d", channel);
}
void notifyMsg() {}
using channelHandler = std::function<void(std::string)>;
void setChannelMsgHandler(channelHandler handler)
{
_channelHandler = handler;
}
private:
redisContext* _context;
redisContext* _pubcontext;
channelHandler _channelHandler;
};