集群聊天服务器二-Chatserver与Chatservice

集群聊天服务器二-Chatserver与Chatservice

shilinkun
2022-05-10 / 0 评论 / 74 阅读 / 正在检测是否收录...
博客网址:www.shicoder.top
微信:kj11011029
欢迎加群聊天 :452380935

本次主要是对ChatserverChatservice进行代码实现

ChatServer

首先我们利用muduo库建立一个ChatServer类,muduo库的讲解我会在后面再写一个项目来深入分析

class ChatServer
{

public:
    ChatServer(EventLoop *loop,
               const InetAddress &listenAddr,
               const string &nameArg);
    void start();

private:
    TcpServer _server;
    EventLoop *_loop;

    // 回调连接相关的事件
    void onConnection(const TcpConnectionPtr &conn);
    // 回调读写事件
    void onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time);
};

其中主要的代码注释已经写出来了,注意由于muduo库已经帮我们实现了套接字的连接,我们只需要自己定义回调函数,即可让客户端连接时候以及客户端发送请求的时候进行自我回调。

注意为什么要在ChatServer类中实现上述代码的构造函数

我们去muduo中的TcpServer源码可知,TcpServer没有空的构造函数,所以我们必须要用初始化列表对TcpServer进行初始化。下面是TcpServer源码的构造函数

  //TcpServer(EventLoop* loop, const InetAddress& listenAddr);
  TcpServer(EventLoop* loop,
            const InetAddress& listenAddr,
            const string& nameArg,
            Option option = kNoReusePort);
  ~TcpServer();  // force out-line dtor, for std::unique_ptr members.

以下代码是ChatServer对应的实现细节

ChatServer::ChatServer(EventLoop *loop,
                       const InetAddress &listenAddr,
                       const string &nameArg)
    : _server(loop, listenAddr, nameArg), _loop(loop)
{
    _server.setConnectionCallback(bind(&ChatServer::onConnection, this, _1));

    _server.setMessageCallback(bind(&ChatServer::onMessage, this, _1, _2, _3));

    _server.setThreadNum(4);
}

void ChatServer::start()
{
    _server.start();
}

void ChatServer::onConnection(const TcpConnectionPtr &conn)
{
    // 用户断开连接
    if (!conn->connected())
    {
        conn->shutdown();
    }
}

void ChatServer::onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time)
{
    string buf = buffer->retrieveAllAsString();
    json js = json::parse(buf);
    // 通过js中的id来获取一个handler,这样把网络模块和业务模块分开
    // 转换成int类型
    auto handler = ChatService::instance()->getHandler(js["msgid"].get<int>());
    // handler在执行的时候才知道是login还是reg
    handler(conn, js, time);
}

注意在onMessage方法中,我们为了把网络模块和业务模块分开,采用的是回调机制,将msgid和消息对应的处理函数绑定起来,然后当一个消息接受到之后,利用json库对msgid进行解析,从而自动回调对应的处理函数。

ChatService

该类主要就是针对msgid和对应的handler进行绑定。

class ChatService
{

public:
    // 获取单例
    static ChatService *instance();

    // 处理登录业务
    void login(const TcpConnectionPtr &conn, json &js, Timestamp);
    // 处理注册业务
    void reg(const TcpConnectionPtr &conn, json &js, Timestamp);

    // 获取消息对应的处理器
    MsgHandler getHandler(int msgid);

private:
    ChatService();
    // 存储消息id和业务处理的方法
    unordered_map<int, MsgHandler> _msgHandlerMap;

};

为了进行绑定操作,我们在ChatService构造函数直接将msgid和对应handler进行绑定

// 注册消息以及对应的回调操作
ChatService::ChatService()
{
    _msgHandlerMap.insert({LOGIN_MSG, std::bind(&ChatService::login, this, _1, _2, _3)});
    _msgHandlerMap.insert({REG_MSG, std::bind(&ChatService::reg, this, _1, _2, _3)});
}

其中std::bind的语法大家可以自行百度,其实可以理解为一种函数指针,将LOGIN_MSGChatService::login绑定起来,当收到LOGIN_MSG,自动去调用ChatService::login方法。然后我们主要是实现登陆和注册两个函数的具体代码。

数据库建立

为了实现登陆和注册功能,我们需要自行建立对应的数据库,我们这里使用的MySQL数据库。对应的user表如下:

字段名称字段类型字段说明约束
idINT用户idPRIMARY KEY、AUTO_INCREMENT
nameVARCHAR(50)用户名NOT NULL, UNIQUE
passwordVARCHAR(50)用户密码NOT NULL
stateENUM('online', 'offline')当前登录状态DEFAULT 'offline'

然后对登陆和注册函数进行实现

// 处理登录业务
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp)
{
    int id = js["id"].get<int>();
    string password = js["password"];
    User user = _usermodel.query(id);
    if (user.getId() == id && user.getPwd() == password)
    {
        if (user.getState() == "online")
        {
            // 用户已经登陆,不允许重复登陆
            json response;
            response["msgid"] = LOGIN_MSG_ACK;
            response["error"] = 2; // 2表示已经登陆
            response["errormsg"] = "已经登陆,不允许重复登陆";
            conn->send(response.dump());
        }
        else
        {
            //登陆成功,然后更新用户登陆信息
            user.setState("online");
            _usermodel.updateState(user);
            json response;
            response["msgid"] = LOGIN_MSG_ACK;
            response["error"] = 0; //0表示成功
            response["id"] = user.getId();
            response["name"] = user.getName();
            conn->send(response.dump());
        }
    }
    else
    {
        // 该用户不存在
        json response;
        response["msgid"] = LOGIN_MSG_ACK;
        response["error"] = 1; //1表示不存在
        response["errormsg"] = "用户名或密码不存在";
        conn->send(response.dump());
    }
}
// 处理注册业务
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp)
{
    string name = js["name"];
    string password = js["password"];

    User user;
    user.setName(name);
    user.setPwd(password);
    bool ans = _usermodel.insert(user);
    if (ans)
    {
        // 注册成功
        json response;
        response["msgid"] = REG_MSG_ACK;
        response["error"] = 0; //0表示成功
        response["id"] = user.getId();
        conn->send(response.dump());
    }
    else
    {
        // 注册失败
        json response;
        response["msgid"] = REG_MSG_ACK;
        response["error"] = 1; //1表示失败
        conn->send(response.dump());
    }
}

注意我们同样在这里使用低耦合思想,把业务和数据库模块分开,注意_usermodel.query(id)这一句,其实query就是其中数据库的操作模块,里面才是具体的数据库语句。

这样建立了ChatServer后,就可以在main函数中直接启动

  EventLoop loop;
  InetAddress addr("127.0.0.1", 6000);
  ChatServer server(&loop, addr, "ChatServer");
  server.start();
  loop.loop();
1

评论 (0)

取消