前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gsky游戏服务器框架2

Gsky游戏服务器框架2

原创
作者头像
i0gan
修改2021-07-05 09:54:20
3820
修改2021-07-05 09:54:20
举报
文章被收录于专栏:I0gan

github

最近跟新了许多代码,目前代码框架如下:

代码语言:javascript
复制
gsky
├── crypto
│   ├── pe.cc
│   ├── pe.hh
│   ├── pmd5.cc
│   └── pmd5.hh
├── gsky.cc
├── gsky.hh
├── log
│   ├── log.cc
│   ├── log.hh
│   ├── log_thread.cc
│   └── log_thread.hh
├── net
│   ├── channel.cc
│   ├── channel.hh
│   ├── epoll.cc
│   ├── epoll.hh
│   ├── eventloop.cc
│   ├── eventloop.hh
│   ├── eventloop_thread.cc
│   ├── eventloop_thread.hh
│   ├── eventloop_threadpool.cc
│   ├── eventloop_threadpool.hh
│   ├── http
│   ├── net.cc
│   ├── net.hh
│   ├── pp
│   │   ├── pp.hh
│   │   ├── request.cc
│   │   ├── request.hh
│   │   ├── response.cc
│   │   ├── response.hh
│   │   ├── socket.cc
│   │   └── socket.hh
│   ├── socket.cc
│   ├── socket.hh
│   ├── util.cc
│   └── util.hh
├── server.cc
├── server.hh
├── thread
│   ├── condition.hh
│   ├── count_down_latch.hh
│   ├── mutex_lock.hh
│   ├── noncopyable.hh
│   ├── thread.cc
│   └── thread.hh
└── util
    ├── firewall.cc
    ├── firewall.hh
    ├── json.hh
    ├── url.hh
    ├── util.cc
    ├── util.hh
    └── vessel.hh

这几天主要是完善 pp (pwnsky protocol)二进制加密传输协议,还有该框架的拓展性。

pp 协议,全称为 pwnsky protocol, 是一款吸收http部分特性的一款二进制传输协议,主要用于游戏长连接交互协议,目前基于tcp来实现。

该协议头部只占16字节,相对与http更小,由于协议字段都在固定位置,解析起来更快速。

pp协议中定义有状态码,数据类型,数据长度,请求路由。

采用 pwnsky encryption进行数据加密,由服务端随机生成8字节密钥返回给客户端,客户端接收到之后,在断开之前传输数据都采用该密钥进行加解密。

pp协议是我自己根据http特点来压缩而来的,头部大小只有16字节,目前头部定义字段如下:

代码语言:javascript
复制
--------------------------------------------------------------------------
| magic 2字节 | status 1字节| type 1字节 |  length 4 字节                  |
--------------------------------------------------------------------------
|                   route 6 字节             |        code 2字节          |
--------------------------------------------------------------------------

magic: 协议标识,两字节为 "\x50\x50"

status: 状态码,包含客户端请求状态码与服务端响应状态码。

type: 传输数据类型,类似与http中的Content-Type

length: 数据长度

route: 请求路由,类似于http url中的path

code: 校验码,用于检测传输内容是否符合加密规范。

pp协议目前 c++ 定义如下,后面不断完善协议:

代码语言:javascript
复制

namespace pp {
enum class status {
    
    // 客户端请求码
    connect = 0x10, // 建立连接,请求密钥的过程
    data_transfer = 0x11,  // 传输数据

    // 服务端响应码
    protocol_error = 0x20, // 协议解析错误
    too_big = 0x21, // 传输数据过长
    invalid_transfer = 0x22, // 无效传输
    
    ok = 0x30, // 请求成功
    send_key = 0x31, // 发送密钥

    redirct = 0x40, // 重置路由

};

// 数据类型
enum class data_type {
    binary_stream = 0x00, // 二进制数据流
    image = 0x01, // 图片
    video = 0x02, // 视频
    music = 0x03, // 音乐

    text = 0x10, // 文本
    json = 0x11, // json 数据
    xml  = 0x12, // xml 数据
};

// 协议头,只占16 字节
struct header {
    unsigned short magic;    // 协议标识,"PP" 值为0x5050,
    unsigned char status;    // 客户端请求码与服务端响应码
    unsigned char type;      // 数据类型
    unsigned int length;     // 数据长度
    unsigned char route[6];  // 请求路由,代替http url中的path
    unsigned char code[2];   // 数据校验码
};

}

采用pp协议的gsky服务器连接与客户端过程:

1. 客户端发起获取密钥连接请求

2. 服务端随机生成8字节密钥和2字节code (校验码),并采用PE (Pwnsky Encryption)以全0的8字节的密钥对内容部分进行加密,也对pp协议头部后8字节也进行单独加密。

3. 客户端收到数据,采用全0 的8字节密钥分别解密协议头部后8字节与内容密钥部分,将其code与密钥储存。

4. 客户端发送数据,在协议头部的code值设置为之前服务端发送过来的code,再分别对内容与头部后8字节采用服服务端发送过来的密钥进行加密,再发送给服务端。

5. 服务端接收数据,采用自己的密钥先进行协议头部后8字节解密,检验code值是否正确,正确之后再根据长度接收数据内容与解密数据内容。

大体上连接与传输过程就是这么回事,客户端若不进行密钥获取的话,服务端接收到数据后是直接断开连接的。

那至于为什么要对协议头部后8字节进行加密,协议头部后8字节包含了 6字节的route与2字节的code,route相当于请求路径,也不希望攻击者通过抓包看到的,所以route有必要进行加盟,code是校验值,有一定程度检测数据与密钥的正确性。

上面提到了PE加密,PE加密是自己先暂时写的一个对称加密算法,比较简单,采用密钥轮加变换单字节单字节的异或数据,密码算法简单的目的也是处于服务器的处理效率考虑,目前加解密c++实现如下:

代码语言:javascript
复制
namespace gsky {
namespace crypto {
class pe {
public:
    pe();
    ~pe();

    void encode(unsigned char key[8], void *raw_data, size_t length);
    void decode(unsigned char key[8], void *raw_data, size_t length);

    unsigned char xor_table_[256] = {
        0xbe, 0xd1, 0x90, 0x88, 0x57, 0x00, 0xe9, 0x53, 0x10, 0xbd, 0x2a, 0x34, 0x51, 0x84, 0x07, 0xc4, 
        0x33, 0xc5, 0x3b, 0x53, 0x5f, 0xa8, 0x5d, 0x4b, 0x6d, 0x22, 0x63, 0x5d, 0x3c, 0xbd, 0x47, 0x6d, 
        0x22, 0x3f, 0x38, 0x4b, 0x7a, 0x4c, 0xb8, 0xcc, 0xb8, 0x37, 0x78, 0x17, 0x73, 0x23, 0x27, 0x71, 
        0xb1, 0xc7, 0xa6, 0xd1, 0xa0, 0x48, 0x21, 0xc4, 0x1b, 0x0a, 0xad, 0xc9, 0xa5, 0xe6, 0x14, 0x18, 
        0xfc, 0x7b, 0x53, 0x59, 0x8b, 0x0d, 0x07, 0xcd, 0x07, 0xcc, 0xbc, 0xa5, 0xe0, 0x28, 0x0e, 0xf9, 
        0x31, 0xc8, 0xed, 0x78, 0xf4, 0x75, 0x60, 0x65, 0x52, 0xb4, 0xfb, 0xbf, 0xac, 0x6e, 0xea, 0x5d, 
        0xca, 0x0d, 0xb5, 0x66, 0xac, 0xba, 0x06, 0x30, 0x95, 0xf4, 0x96, 0x42, 0x7a, 0x7f, 0x58, 0x6d, 
        0x83, 0x8e, 0xf6, 0x61, 0x7c, 0x0e, 0xfd, 0x09, 0x6e, 0x42, 0x6b, 0x1e, 0xb9, 0x14, 0x22, 0xf6, 

        0x16, 0xd2, 0xd2, 0x60, 0x29, 0x23, 0x32, 0x9e, 0xb4, 0x82, 0xee, 0x58, 0x3a, 0x7d, 0x1f, 0x74, 
        0x98, 0x5d, 0x17, 0x64, 0xe4, 0x6f, 0xf5, 0xad, 0x94, 0xaa, 0x89, 0xe3, 0xbe, 0x98, 0x91, 0x38, 
        0x70, 0xec, 0x2f, 0x5e, 0x9f, 0xc9, 0xb1, 0x26, 0x3a, 0x64, 0x48, 0x13, 0xf1, 0x1a, 0xc5, 0xd5, 
        0xe5, 0x66, 0x11, 0x11, 0x3a, 0xaa, 0x79, 0x45, 0x42, 0xb4, 0x57, 0x9d, 0x3f, 0xbc, 0xa3, 0xaa, 
        0x98, 0x4e, 0x6b, 0x7a, 0x4a, 0x2f, 0x3e, 0x10, 0x7a, 0xc5, 0x33, 0x8d, 0xac, 0x0b, 0x79, 0x33, 
        0x5d, 0x09, 0xfc, 0x9d, 0x9b, 0xe5, 0x18, 0xcd, 0x1c, 0x7c, 0x8b, 0x0a, 0xa8, 0x95, 0x56, 0xcc, 
        0x4e, 0x34, 0x31, 0x33, 0xf5, 0xc1, 0xf5, 0x03, 0x0a, 0x4a, 0xb4, 0xd1, 0x90, 0xf1, 0x8f, 0x57, 
        0x20, 0x05, 0x0d, 0xa0, 0xcd, 0x82, 0xb3, 0x25, 0xd8, 0xd2, 0x20, 0xf3, 0xc5, 0x96, 0x35, 0x35, 
    };
};

}
}

代码语言:javascript
复制
#include <gsky/crypto/pe.hh>

gsky::crypto::pe::pe() {
    
}

gsky::crypto::pe::~pe() {
    
}

// key length is 8 bytes
// 加密概述
// 采用密钥重叠循环,查表来进行异或。
//
void gsky::crypto::pe::encode(unsigned char key[8], void *raw_data, size_t length) {
    unsigned char keys[8];
    memcpy(keys, key, 8);
    char *data = (char *)raw_data;
    for(int i = 0; i < length; i ++) {
        data[i] ^= keys[i % 8];
        unsigned char n = ((keys[i % 8] + keys[(i + 1) % 8]) * keys[(i + 2) % 8]) & 0xff;
        data[i] ^= n ^ xor_table_[n];
        keys[i % 8] = (n * 2 + 3) % 0x100;
    }
}

// 解密
void gsky::crypto::pe::decode(unsigned char key[8], void *raw_data, size_t length) {
    unsigned char keys[8];
    memcpy(keys, key, 8);
    char *data = (char *)raw_data;
    for(int i = 0; i < length; i ++) {
        char t_key = keys[i % 8];
        unsigned char n = ((keys[i % 8] + keys[(i + 1) % 8]) * keys[(i + 2) % 8]) & 0xff;
        data[i] ^= n ^ xor_table_[n];
        data[i] ^= t_key;
        keys[i % 8] = (n * 2 + 3) % 0x100;
    }
}

协议拓展部分,为了更方便的自定义传输协议,我对框架进行了比较大的整改,在net模块目前有:

代码语言:javascript
复制
├── net
│   ├── channel.cc
│   ├── channel.hh
│   ├── epoll.cc
│   ├── epoll.hh
│   ├── eventloop.cc
│   ├── eventloop.hh
│   ├── eventloop_thread.cc
│   ├── eventloop_thread.hh
│   ├── eventloop_threadpool.cc
│   ├── eventloop_threadpool.hh
│   ├── http // http解析,有待实现
│   ├── net.cc
│   ├── net.hh
│   ├── pp  // pp解析
│   │   ├── pp.hh
│   │   ├── request.cc
│   │   ├── request.hh
│   │   ├── response.cc
│   │   ├── response.hh
│   │   ├── socket.cc
│   │   └── socket.hh
│   ├── socket.cc 
│   ├── socket.hh
│   ├── util.cc
│   └── util.hh

上面部分主要是从net::socket类进行协议的分支,该类主要是相当与一个epoll架构的单纯tcp套接子处理,基于tcp之上,再对数据进行协议解析,为了让库更好的拓展,我特意模仿了golang语言中的http库写了两个类request类和response类,request类是存储客户端请求信息,response类是让数据发送给客户端的封装接口。

目前来说pp协议服务端已经基本差不多了,只是pp协议客户端还有待实现一下sdk,方便接入gsky服务器。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档