专栏首页I0ganGsky游戏服务器框架2
原创

Gsky游戏服务器框架2

github

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

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字节,目前头部定义字段如下:

--------------------------------------------------------------------------
| 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++ 定义如下,后面不断完善协议:

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++实现如下:

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, 
    };
};

}
}

#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模块目前有:

├── 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服务器。

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • gsky简易高性能游戏服务器框架库1

    有时候方便自己快速开发小游戏服务器,自己采用go,python,c#那些也简单写过一点游戏后台,但是针对与socket长连接,感觉有点不太好控制,go稍微好些,...

    i0gan
  • 游戏服务器框架 Leaf/go

    Leaf 是一个使用 Go 语言开发的开源游戏服务器框架,注重运行效率 并追求极致的开发效率。Leaf 适用于几乎所有的游戏类型。其主要的特性: * 良好的使用...

    李海彬
  • Leaf 游戏服务器框架简介

    下载地址: https://github.com/name5566/leaf/blob/master/TUTORIAL_ZH.md Leaf 游戏服务器框架简介...

    李海彬
  • Go开源游戏服务器框架——Pitaya

    Pitaya是一款由国外游戏公司topfreegames使用golang进行编写,易于使用,快速且轻量级的开源分布式游戏服务器框架 Pitaya使用etcd作为...

    歪歪梯
  • 2 网络游戏服务器开发框架设计介绍

    在开发过程中,会先有一份开发大纲或是一份策划案,但是这些在我的开发中可能不会有,或者即使有,也很有可能是我随性写下来的,但是我会尽可能写好它。

    范蠡
  • 游戏服务器架构:游戏服务器架构设计进化

    对于弱联网游戏,实际上客户端不需要维护和服务器之间的长连接,需要通知服务器数据变化的时候,发个http请求等服务器响应返回即可。

    用户3479834
  • 一个简单的游戏服务器框架_游戏开发

    最近一段时间不是很忙,就写了一个自己的游戏服务器框架雏形,很多地方还不够完善,但是基本上也算是能够跑起来了。我先从上层结构说起,一直到实现细节吧,想起什么就写...

    李海彬
  • 教你从头写游戏服务器框架

    大概已经有差不多一年没写技术文章了,原因是今年投入了一些具体游戏项目的开发。这些新的游戏项目,比较接近独立游戏的开发方式。我觉得公司的“祖传”服务器框架技术不太...

    韩伟
  • 经典游戏服务器端架构概述 (2)

    现代电子游戏,基本上都会使用一定的网络功能。从验证正版,到多人交互等等,都需要架设一些专用的服务器,以及编写在服务器上的程序。因此,游戏服务器端软件的架构,本质...

    韩伟
  • 教你从头写游戏服务器框架(3)

    使用异步非阻塞编程,确实能获得很好的性能。但是在代码上,确非常不直观。因为任何一个可能阻塞的操作,都必须要要通过“回调”函数来链接。比如一个玩家登录,你需要先读...

    韩伟
  • 游戏服务器架构:游戏服务器设计的若干问题

    通过ping下通过A地址解析到的节点地址,看下延迟情况。如果你不熟悉cdn分发或者dig命令,可以查看这篇文章: (十五)深入浅出TCPIP之Hello CD...

    用户3479834
  • 探索 Golang 云原生游戏服务器开发,5 分钟上手 Nano 游戏服务器框架

    nano 是一个轻量级的服务器框架,它最适合的应用领域是网页游戏、社交游戏、移动游戏的服务端。当然还不仅仅是游戏,用 nano 开发高实时 web 应用也非常合...

    为少
  • 再谈游戏服务器架构

    一、服务器划分原则 在现有的网络游戏服务器端架构中,多是以功能和场景来划分服务器结构的。负载均衡和集群暂且不在本文中讨论(bigworld、atlas...

    李海彬
  • 游戏服务器架构概要

    方案:切分xysvr,让多个scene分别服务于一些用户,world负责拉取数据。并协调控制多scene。

    Zoctopus
  • 论可复用的游戏服务器端开发框架(一)

    本文试图以游戏服务器端开发的角度,探讨在需求高度变化的环境下,可重用模块构建的可能性和基本方案。 可复用框架的必要性与可行性 在现代游戏产品的开发中,游戏服务...

    韩伟
  • 论可复用的游戏服务器端开发框架(二)

    RPG系统的可复用模型 RPG系统主要负责提供游戏中提供“积累、成长”的快感,也是驱动玩家反复进行游戏操作的重要系统。RPG系统能提供这种作用的最基本逻辑,是以...

    韩伟
  • 论可复用的游戏服务器端开发框架(三)

    引导类系统的可复用模型 说到游戏中的“引导类系统”,最常见的就是所谓“新手引导”,这些专门设计的游戏流程,让玩家一步步的按规定顺序去操作游戏。而“任务系统”,也...

    韩伟
  • 论可复用的游戏服务器端开发框架(四)

    战斗系统的模型构建思考 战斗系统是一个游戏的玩法核心,也是游戏之间差别最大的地方,想要建立可复用的模型,可谓困难最大。但是,游戏的玩法本身也是有分类和传承的。需...

    韩伟
  • Unity3D-游戏开发移动端网络游戏服务器架构

    弱联网的游戏,主要是指对游戏数据实时性要求比较低的联网游戏,比如卡牌游戏,休闲游戏等。

    孙寅

扫码关注云+社区

领取腾讯云代金券