帧同步服务器开发历程

这篇文章将会记录我从0开始学习网络编程后如何实现一个帧同步的游戏服务器
第一版使用了epoll和socket基于protobuf实现了一个简单帧同步服务器1,代码比较多,就没有特地的去加注释,主要的问题简单说一下:
1、用户根据一个.h文件里面写死的账号密码登录,没有实现注册登录功能
2、单线程,在IO的时候会造成cpu的浪费,导致服务器效率变低
3、没有动态的分配内存,虽然会快一点,但是会造成大量的空间被浪费
针对上一版的问题简单的修改了一下新版本服务器2。先说改进的地方
1.实现了用户的注册与登录,并将数据存在MySQL数据库中。
2.加入了Redis,提高效率。
3.实现了多线程,主线程读取完网络消息后塞到消息队列中,交给其他线程处理。
4.除了客户端读取的缓冲区(因为稍微有点麻烦,这个等后面写正式的服务器的时候再改进),其他地方都实现了动态内存分配。
但是这个地方又引出了新的问题,如果仔细看过上面新版本服务器的代码会发现我的SQL和Redis被我封装成了单例,这就导致几个线程调Sql语句的时候会有多线程的问题(其实MySQL是线程安全的),但是因为我的骚操作,导致代码跑起来会出现很多问题。改进方法就是删掉单例代码,在每个线程中都实例化一个SQL对象,分别建立连接,MySQL内部会处理多线程的问题,同时要注意及时的清空结果集

改进后能顺利跑起来的服务器代码,SQLTool.h的代码放到最后。
下方代码虽然能跑起来但依旧存在一个问题:虽然消息是按顺序发来的,多线程也是在队列里面依次去取的,但是多个线程处理完之后放到待发送区却不是依次发过去的,所以还需要保证消息的有序性,解决办法可以在消息结构体最后加上一个序号用来标记,再每帧数据发送前sort一下,然后再依次发送,同时记得去掉最后的序号标记,那玩意客户端用不着,发过去就没必要了。

2022.3.17更新:离大谱,最初的单线程版本处理2500条登录请求算上网络延迟只需要2ms(设置TCP_NODELAY后),多线程的要900毫秒(虽然没设置TCP_NODELA,但这差距也太大了),后来发现可能是我哪辅助线程的原因,最后只开两个线程,主线程收发消息,副线程处理,最终在设置TCP_NODELAY的情况下,算上网络延迟总算是提高到了20ms,这里得出结论:消息处理很简单的话没必要开多线程

mark一下,怕忘记Linux下编译的指令,要加上protobuf还有sql还有多线程的模块所以编译指令很长: g++ FileName.cpp GameMessage.pb.cc -o FileName -std=c++11 -lprotobuf -I/usr/include/mysql/ -L/usr/lib64/mysql -L/usr/local/lib/ -lmysqlclient_r -lhiredis -lpthread

SQLTool.h的代码如下

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
using namespace std;
class SQLTool
{
public:
    SQLTool();

    bool init();

    //暂时不提供修改地址、数据库账户密码的接口
    void setPort(int32_t iPort) { m_iPort = iPort; }
    int32_t getPort() { return m_iPort; }

    void setHost(string strHost) { m_strHost = strHost; }
    string getHost() { return m_strHost; }

    void setTable(string strTableName) { m_strTableName = strTableName; }
    string getTable() { return m_strTableName; }
    //数据库操作接口(根据表的类型来增加接口)
    bool getPassWord(string strUsername,string &strPassWord);
    bool setPassWord(string strUsername, string strNewPassWord);
    bool addUser(string strUserName, string strPassWord);
    bool deleteUser(string strUserName);
    
    //关闭数据库连接
    void closeSql(){
        mysql_close(&m_sqlHandler);
    }
private:

    pthread_mutex_t mutex_SQL;
    int32_t m_iPort;
    string m_strUsername; //库的name
    string m_strPassword;
    string m_strHost;
    string m_strDBName;
    string m_strTableName;
    MYSQL m_sqlHandler;
};
SQLTool::SQLTool()
{
    m_iPort = 3306;
    m_strUsername = "game";
    m_strPassword = "game123u";
    m_strHost = "10.0.128.199";
    m_strDBName = "DB1";
    m_strTableName = "UserInfo";
    init();
}
bool SQLTool::init()
{
    
    pthread_mutex_init(&mutex_SQL, NULL);
    uint32_t client_flag = 0;

    //初始化连接
    mysql_init(&m_sqlHandler);
    mysql_options(&m_sqlHandler, MYSQL_OPT_RECONNECT, "1");
    mysql_options(&m_sqlHandler, MYSQL_SET_CHARSET_NAME, "utf8");
    MYSQL* connect_result = mysql_real_connect(&m_sqlHandler,  m_strHost.c_str(),  m_strUsername.c_str(), m_strPassword.c_str(), NULL, m_iPort, NULL, client_flag);
    if (NULL == connect_result)
    {
        printf("mysql_real_connect failed with code [%d] msg[%s]\n", mysql_errno(&m_sqlHandler), mysql_error(&m_sqlHandler));
        return false;
    }

    if (mysql_ping(&m_sqlHandler))
    {
        printf("mysql ping failed\n");
        return false;
    }

    if (mysql_select_db(&m_sqlHandler, m_strDBName.c_str()))
    {
        printf("select db failed\n");
        return false;
    }

    if (mysql_ping(&m_sqlHandler))
    {
        printf("mysql_ping failed\n");
        return false;
    }
    return true;
}
bool SQLTool::getPassWord(string strUsername,string &strPassWord)
{
    string sql = "SELECT PassWord FROM " + m_strTableName + " WHERE UserName=\"" + strUsername +"\"";
    
    pthread_mutex_lock(&mutex_SQL);
    if(mysql_real_query(&m_sqlHandler, sql.c_str(), sql.size()))
    {
        printf("mysql_real_query failed with code [%d] msg[%s]\n", mysql_errno(&m_sqlHandler), mysql_error(&m_sqlHandler));
        return false;
    }
    MYSQL_RES* mysql_result = mysql_use_result(&m_sqlHandler);
    if(mysql_result == nullptr && (mysql_field_count(&m_sqlHandler) > 0))
    {
        printf("mysql_use_result failed with code [%d] msg[%s]\n", mysql_errno(&m_sqlHandler), mysql_error(&m_sqlHandler));
        return false;
    }
    MYSQL_ROW m_current_row = mysql_fetch_row(mysql_result);
    if(!m_current_row)
    {
        return false;
    }
    uint64_t *m_fields_length = mysql_fetch_lengths(mysql_result); // 每一列得数据长度
    strPassWord = std::string(m_current_row[0], (size_t)m_fields_length[0]);
    mysql_free_result(mysql_result);
    
    pthread_mutex_unlock(&mutex_SQL);
    return true;
}
bool SQLTool::setPassWord(string strUsername, string strNewPassWord)
{
    string sql = "UPDATE " + m_strTableName + " SET PassWord=\"" + strNewPassWord +"\" WHERE UserName=\"" + strUsername + "\"";
    if(mysql_real_query(&m_sqlHandler, sql.c_str(), sql.size()))
    {
        printf("mysql_real_query failed with code [%d] msg[%s]\n", mysql_errno(&m_sqlHandler), mysql_error(&m_sqlHandler));
        return false;
    }
    return true;
}
bool SQLTool::addUser(string strUserName, string strPassWord)
{
    if(getPassWord(strUserName,strPassWord))
    {
        return false;
    }
    string sql = "INSERT INTO " + m_strTableName + " (UserName,PassWord) VALUES (\"" + strUserName + "\",\"" + strPassWord + "\")";
    if(mysql_real_query(&m_sqlHandler, sql.c_str(), sql.size()))
    {
        printf("mysql_real_query failed with code [%d] msg[%s]\n", mysql_errno(&m_sqlHandler), mysql_error(&m_sqlHandler));
        return false;
    }
    return true;
}
bool SQLTool::deleteUser(string strUserName)
{
    return true;
}
/* TEST
    string usename = "XXX";
    string password = "xxx";
    if(SQLTool::getInstance()->getPassWord(usename ,password))
        cout<>password;
        if(SQLTool::getInstance()->addUser(usename ,password)){
            cout<<"注册 成功\n";
        }
    }
    if(SQLTool::getInstance()->getPassWord(usename ,password)){
        cout<<"登录成功,你的用户名和密码是:"<
class RedisTool{
public:
    
    RedisTool();

    bool init();

    bool RedisToolConnect();
    int handleReply();
    int setValue(const string &key,const string &value);
    int getValue(const string &key,string &value);
    int delKey(const string &key);
private:
    int m_iNum;
    int m_iRoom;
    redisContext* m_pm_rct;
    redisReply* m_pm_rr;
};

RedisTool::RedisTool()
{
    init();
}
bool  RedisTool::init()
{
    
    m_iNum=1;
    m_iRoom=1;
    m_pm_rct=nullptr;
    m_pm_rr = nullptr;
    if(RedisToolConnect())
    {
        return true;
    }
    return false;
}
bool RedisTool::RedisToolConnect()
{
    m_pm_rct = redisConnect("127.0.0.1",6379);
    if(m_pm_rct->err)
	{
		cout<<"redis connect error"<<"\n";
        return false;
	}
    return true;
}
int RedisTool::handleReply()
{
	return m_pm_rct->err;
}

int RedisTool::setValue(const string &key,const string &value)
{
    string cmd="set "+key+" "+value;
	m_pm_rr=(redisReply*)redisCommand(m_pm_rct,cmd.c_str());
	return handleReply();
}
int RedisTool::getValue(const string &key,string &value)
{
	string cmd="get "+key;
	m_pm_rr=(redisReply*)redisCommand(m_pm_rct,cmd.c_str());
    if(m_pm_rr->str)
    {
	    value=string(m_pm_rr->str);
    }
	return handleReply();
}
int RedisTool::delKey(const string &key)
{
	string cmd="del "+key;
	m_pm_rr=(redisReply*)redisCommand(m_pm_rct,cmd.c_str());
	return handleReply();
}

你可能感兴趣的:(服务器,运维)