这篇文章将会记录我从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 <hiredis/hiredis.h>
#include <mysql.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <pthread.h>
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 mute