bookmark_border游戏平台好友系统需求规格说明书

1. 系统架构

1.1. 技术选型

protobuf

Client与好友系统服务端程序之间,以及服务端各内部组件间均采用protobuf定义通信协议。

Client与服务端之间的交互消息仍然沿用现有的网狐格式,只是将网狐数据包中的消息体部分由C++结构换成protobuf定义。

 

pika

pika兼容redis的核心数据类型以及操作协议,同时支持数据持久化到磁盘。

这里使用pika替代redis主要用于:

1) 持久化存储好友系统涉及到的用户基本信息、用户间好友关系、用户在线状态等

2) 保存好友申请信息列表

3) 缓存离线消息

4) 用作消息队列中间件使用

 

1.2. 整体架构

image.png

核心组件与主要流程:

1) Client从第一个WebAPI返回数据中获得聊天网关地址列表

2) Client登录成功后,尝试连接好友系统的接入网关gateway (轮询网关列表,直到成功建立TCP连接,并成功登录ChatServer)。每个gateway与业务路由ChatRouter之间建立一个TCP连接。

3) Client与好友系统Server端的所有业务交互,好友间的聊天消息,都通过gateway程序转发给ChatRouter,然后ChatRouter根据业务请求类型,分发给不同的业务服务器进行处理

4) ChatRouter(即聊天系统业务路由程序),位于接入网关gateway与聊天服务器(集群)之间,负责业务请求的分发及业务处理结果及聊天消息的回传处理

5) 聊天服务器集群由以下组件构成:

a. ChatServer

聊天系统主服务器:负责处理DataServer下发的新用户信息、用户状态变化、用户积分、游戏数据的同步处理等。

b. MsgProcessor

聊天消息处理程序:负责处理用户发送的聊天消息。根据用户当前在线状态确定是投递到待发送消息的MQ中,还是存入pika离线消息列表中。

c. MsgSender

聊天消息发送程序:负责从MQ中加载待发送消息,并完成消息发送。

6) DataSync(待定)

负责用户信息存量数据的同步:将DB中的用户基本信息、游戏统计数据等同步到Pika NoSQL缓存数据库中。

(待定:考虑只在用户登录DataServer时,通过ChatServer同步用户信息到pika缓存?)

2. DB设计

2.1. 接入网关信息表

RYPlatformDB.dbo.ChatGatewayInfo

字段名称

中文描述

数据类型

备注

ID

网关ID

integer

非自增唯一ID,添加时Web自动生成下一个可用ID,可编辑修改,但需要校验唯一性

PublicIP

公网IP

string

网关公网IP地址

Port

端口

integer

网关服务端口

RouterID

业务路由ID

integer

默认为0,对应的业务路由ID(下拉列表),添加时可选填,之后可编辑修改

2.2. 业务路由信息表

RYPlatformDB.dbo.ChatRouterInfo

字段名称

中文描述

数据类型

备注

ID

路由ID

integer

非自增唯一ID,添加时Web自动生成下一个可用ID,可编辑修改,但需要校验唯一性

LocalIP

内网IP

string

内网IP地址

Port

端口

integer

服务端口

 

2.3. 聊天服务信息表

RYPlatformDB.dbo.ChatServerInfo

字段名称

中文描述

数据类型

备注

ID

服务器ID

integer

非自增唯一ID,添加时Web自动生成下一个可用ID,可编辑修改,但需要校验唯一性

ServerType

类型

integer

单选按钮:1 ChatServer2 MsgProcessor3 MsgSender

LocalIP

内网IP

string

内网IP地址

Port

端口

integer

服务端口

 

2.4. 敏感词汇信息表

RYPlatformDB.dbo.ChatForbiddenWords

字段名称

中文描述

数据类型

备注

en

英语

string

es

西班牙语

string

pt

葡萄牙语

string

3. pika缓存设计

3.1. 用户信息

使用HashSet存储用户信息。

key】:

userid: + <userid>

 

【示例】:

userid=100001用户为例,其用户信息等价的json内容为:

{
    "user:100001":{
        "userid":100001,
        "nickname":"test",
        "faceid":1,
        "faceurl":"",
        "facebookid":"",
        "viplevel":1,
        "wincount":1,
        "lostcount":1
    }
}

 

3.2. 好友关系

使用Set存储用户的好友、黑名单信息

3.2.1. 好友列表

key】:

user: + <userid> + :friends

 

【示例】:

userid100001用户添加好友100002,操作为:

sadd user:100001:friends 100002

3.2.2. 黑名单列表

key】:

user: + <userid> + :blacklist

 

【示例】:

userid100001用户添加黑名单100003,操作为:

sadd user:100001:blacklist 100003

 

3.3. 好友申请列表

每个好友申请分配一个唯一的序列号作为friendrequestid,使用一个全局的hashset存储所有好友申请列表。

然后,一个好友申请对应的发起申请的用户和被邀请加好友的用户各自关联到相同的friendrequestid

全局好友申请列表hashset如下:

key】:

friendrequest: + <requestid>

 

【示例】:

hmset friendrequest:1 from 100001 to 100002 time 1683600098 status 0

sadd user:100001:friendrequests 1

sadd user:100002:friendrequests 1

 

3.4. 在线用户列表

使用set存储在线用户列表:

sadd onlineusers 100001

3.5. 离线消息

使用list存储离线消息:

lpush user:110001:offlinemsgs {from:100001,to:110001,msg:test msg}

 

3.6. 待发送消息

使用list作为消息队列存储待发送消息

 

【写消息】:

lpush msgs {from:100001,to:100002,msg:test msg}

 

【读消息】:

 rpop msgs

 

4. Web

4.1. 系统配置

4.1.1. 菜单项变更

1) 新增菜单项:

系统维护 > 好友系统

2) 好友系统配置页面包含5TAB页:

参数设置

业务路由

接入网关

聊天服务

敏感词汇

4.1.2. 参数设置

1) 数据表:RYPlatformDB.dbo.ServerParam

2) 编辑界面如下:

好友申请过期时间:

3   (单位:天,Name=ADD_FRIEND_EXPIRATION

离线消息保留时长:

7   (单位:天,Name=OFFLINE_MESSAGE_EXPIRATION

最大消息长度:

200 (单条消息最大字数,Name=MAX_CHAT_MESSAGE_LENGTH

4.1.3. 业务路由

1) 数据表:RYPlatformDB.dbo.ChatRouterInfo

2) 编辑界面如下:

路由ID

1   (字段:ID

内网IP

192.1.1.1   (字段:LocalIP

端口:

3001   (字段:Port

3) 需提供增、删、改、浏览功能

4.1.4. 接入网关

1) 数据表:RYPlatformDB.dbo.ChatGatewayInfo

2) 编辑界面如下:

网关ID

1   (字段:ID

公网IP

100.1.1.1   (字段:PublicIP

端口:

3001   (字段:Port

业务路由:

下拉列表选择 (来源于ChatRouterInfo表),列表中显示:未选择、及业务路由信息的IP,Port,如:10.1.1.1,8000

3) 需提供增、删、改、浏览功能

4.1.5. 聊天服务

1) 数据表:RYPlatformDB.dbo.ChatServerInfo

2) 编辑界面如下:

服务器ID

1  (字段ID)

服务类型:

单选按钮(字段:ServerType,取值: 1  ChatServer2  MsgProcessor,  3  MsgSender

内网IP

100.1.1.1   (字段:LocalIP

端口:

5001   (字段:Port

3) 需提供增、删、改、浏览功能

4.1.6. 敏感词汇

1) 数据表:RYPlatformDB.dbo.ChatForbidenWords

2) 界面如下:

英语:

shit   (字段:en

西班牙语:

mierda(字段:es

葡萄牙语:

merda(字段:pt

 

4.2. WebAPI

Client调用的第一个WebAPI应答消息中增加好友系统的参数设置,如下所示:

{
    "data":{
        "Theversion":0,
   ⁝

"chat":{
         "maxchatmessagelength":200,
         "gateways":[
             {
                  "ip":"1.1.1.1",
                 "port":10001
             },
             {
                 "ip":"1.1.1.1",
                 "port":10002
              }
         ]
       }

    }

}

 

【字段说明】:

maxchatmessagelength单条消息的最大长度

 

【注】:

1. Client只有在登录成功后,才能发起与接入网关之间的TCP连接

2. Client与网关之间为TCP长连接,进游戏房间后,仍保持与网关之间的连接

3. 登录成功后,client需从网关地址列表中随机挑选一个地址尝试连接,若连接失败,则依次顺序尝试连接下一个地址

4. client与网关成功建立连接后,若收发消息出错/超时,则client关闭当前到网关的TCP连接,然后向列表中的下一个网关地址发起重连

5. ClientServer(聊天服务器集群)交互消息定义

ClientServer(聊天服务器集群)间交互消息采用protobuf

具体消息定义请参考项目工程目录下的proto定义文件:protogen/proto/chat.proto

 

 

 

 

bookmark_borderc++11 function和bind使用简介

bind

定义在头文件 functional 里

template<typename _Func, typename... _BoundArgs>inline typename
_Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type
bind(_Func&& __f, _BoundArgs&&... __args)

template<typename _Result, typename _Func, typename... _BoundArgs>inline
typename _Bindres_helper<_Result, _Func, _BoundArgs...>::type
bind(_Func&& __f, _BoundArgs&&... __args)

函数模板 bind 生成 f 的转发调用包装器。调用此包装器等价于以一些绑定到 args 的参数调用 f 。类似于 python 的 functools.partial

参数 f 表示可调用对象(函数对象、指向函数指针、函数的引用、指向成员函数指针或指向数据成员指针)

参数 __args 表示要绑定的参数列表。未绑定参数使用命名空间 std::placeholders 的占位符 _1, _2, _3... 所替换

需要注意的是:

调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问其成员的对象。

到 bind 的参数被复制或移动,而且决不按引用传递,除非包装于 ref 或 cref

允许同一 bind 表达式中的多重占位符(例如多个 _1 ),但结果仅若对应参数( u1 )是左值或不可移动右值才良好定义。

function

定义在头文件 functional 里

template<typename _Res, typename... _ArgTypes>class function<_Res(_ArgTypes...)>

类模板 std::function 是通用多态函数封装器。 std::function 的实例能存储、复制及调用任何可回调目标——函数、lambda表达式、bind表达式、其他函数对象,还可以指向成员函数指针和指向数据成员指针。

存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为。调用 std::function 的目标导致抛出 bad_function_call 异常。

function 满足可复制构造和可复制赋值。

function的用法:

1. 保存普通函数

void printA(int a)
{
     cout << a << endl;          
}

std::function<void(int a)> func;
func = printA;
func(2);   //2

2. 保存lambda表达式

std::function<void()> func_1 = [](){cout << "hello world" << endl;};    
func_1();  //hello world

3. 保存成员函数

class Foo{
    Foo(int num) : num_(num){}    void print_add(int i) const {cout << num_ + i << endl;}    int num_;  
};//保存成员函数std::function<void(const Foo&,int)> f_add_display = &Foo::print_add;
Foo foo(2);
f_add_display(foo,1);

关于bind的用法:

可将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。 from 《C++ primer》第五版

#include <iostream>#include <functional>using namespace std;class A
{public:    void fun_3(int k,int m)
    {
        cout<<"print: k="<<k<<",m="<<m<<endl;
    }
};void fun_1(int x,int y,int z)
{
    cout<<"print: x=" <<x<<",y="<< y << ",z=" <<z<<endl;
}void fun_2(int &a,int &b)
{
    a++;
    b++;
    cout<<"print: a=" <<a<<",b="<<b<<endl;
}int main(int argc, char * argv[])
{    //f1的类型为 function<void(int, int, int)>
    auto f1 = std::bind(fun_1,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
    f1(); //print: x=1,y=2,z=3
    auto f2 = std::bind(fun_1, placeholders::_1,placeholders::_2,3);    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f2 的第一,二个参数指定
    f2(1,2);//print: x=1,y=2,z=3 
    auto f3 = std::bind(fun_1,placeholders::_2,placeholders::_1,3);    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f3 的第二,一个参数指定    //注意: f2  和  f3 的区别。
    f3(1,2);//print: x=2,y=1,z=3

    int m = 2;    int n = 3;
    auto f4 = std::bind(fun_2, placeholders::_1, n); //表示绑定fun_2的第一个参数为n, fun_2的第二个参数由调用f4的第一个参数(_1)指定。
    f4(m); //print: m=3,n=4
    cout<<"m="<<m<<endl;//m=3  说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的,如m
    cout<<"n="<<n<<endl;//n=3  说明:bind对于预先绑定的函数参数是通过值传递的,如n    
    A a;    //f5的类型为 function<void(int, int)>
    auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2); //使用auto关键字
    f5(10,20);//调用a.fun_3(10,20),print: k=10,m=20
    std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
    fc(10,20);   //调用a.fun_3(10,20) print: k=10,m=20 
    return 0; 
}

bookmark_border一种典型的休闲游戏服务端架构简介

服务端整体介绍

图片1.png

图片2_iqiyi.png

服务端程序启停

服务器程序启动先后顺序依次是:proxycontrolDBlobbyloginnatrecordgameplatform。各个模块启动后,都会向control发送注册请求。各个模块关闭后,会向control发送关闭请求。

图片3.png

服务端各模块详细说明

ProxyServer

Proxy负责服务端组件之间的消息转发。Proxy本身维护一个与Control之间的TCP长连接。除ProxyControl之外的其他服务端组件(NATLoginLobbyDBServerGameServer)都会维护一个与 Proxy之间的TCP长连接。

ProxyControl之外的其他服务端组件之间相互不知道对方的存在,不直接通信,它们之间通过Proxy中转消息。

如:LoginServer接收到Client的登录请求时,LoginServer会通过Proxy中转向DBServer发送用户登录处理请求,由DBServer完成到DB中查询校验登录信息的处理;DBServer完成用户登录信息的校验处理后,将校验结果通过Proxy中转返回给LoginServer,然后LoginServer将登录结果返回给 Client

 

ControlServer

Control是整个服务端的控制管理中心,control服务内存中管理维护所有其他服务端组件(Proxy除外)的信息和状态。

 

DBServer

DBServer主要作为操作数据库(简称DB)的一个“代理”服务器,异步处理其他服务端组件的DB操作请求。比如:除了启动时从DB加载相关配置项,GameServer在需要操作DB时,都会向DBServer发送相应的DB操作请求;DBServer完成实际的DB查询操作后,将查询结果返回给GameServer 

另一方面,DBServer提供了对部分频繁访问的数据的缓存功能,比如:在一定时间内登录用户的信息会缓存在DBServer内,用户登录时,首先会在缓存中查找,查找不到时才会去查询DB

LobbyServer

负责管理游戏大厅配置、游戏房间信息。Client进入大厅后,当进入具体某个游戏房间时,client会向LobbyServer请求对应的GameServer地址信息。

 

LoginServer

LoginServer负责处理Client的登录请求。如果Client登录成功,LoginServer会为Client指定并下发LobbyServer地址信息。

 

NATServer

NATServer一定程度上相当于一个负载均衡器,客户端连接NATServer后,NATServer会为Client 选择指定其登录连接的LoginServer地址。这样可以实现简单的负载分担,并具有一定的高可用性。

RecordServer

 RecordServer负责游戏记录的保存与查询请求。

GameServer

GameServer即游戏服务器,负责实现具体的游戏逻辑,处理玩家的进入游戏房间、上桌、坐下、站起、举手准备等。每一个游戏都是一个单独的库,由GameServer加载管理,当玩家从大厅进入到某个游戏后,GameServer负责客户端和相应游戏库之间的交互。

 

bookmark_borderASCII格式与Hex十六进制格式转换

ascii转hex

int Ascii2Hex(const char* ascii, char* hex)
{
    int i, len = strlen(ascii);
    char chHex[] = "0123456789ABCDEF";

    for (i = 0; i<len; i++)
    {
        hex[i * 3] = chHex[((BYTE)ascii[i]) >> 4];
        hex[i * 3 + 1] = chHex[((BYTE)ascii[i]) & 0xf];
        hex[i * 3 + 2] = ' ';
    }

    hex[len * 3] = '\0';

    return len * 3;
}

hex转ascii

int Hex2Ascii(const char* hex, char* ascii, int maxCount)
{
    int len = strlen(hex), tlen, i, cnt;

    for (i = 0, cnt = 0, tlen = 0; i<len && tlen < maxCount; i++)
    {
        char c = toupper(hex[i]);

        if ((c >= '0'&& c <= '9') || (c >= 'A'&& c <= 'F'))
        {
            BYTE t = (c >= 'A') ? c - 'A' + 10 : c - '0';

            if (cnt)
                ascii[tlen++] += t, cnt = 0;
            else
                ascii[tlen] = t << 4, cnt = 1;
        }
    }

    return tlen;
}

hexdump函数

void hexdump(const void* _data, uint32_t size)
{
    const uint8_t* data = (const uint8_t*)_data;
    unsigned int offset = 0;

    while (offset < size)
    {
        printf("%08x  ", offset);
        size_t n = size - offset;

        if (n > 16)
        {
            n = 16;
        }

        for (size_t i = 0; i < 16; ++i)
        {
            if (i == 8)
            {
                printf(" ");
            }

            if (offset + i < size)
            {
                printf("%02x ", data[offset + i]);
            }
            else
            {
                printf("   ");
            }
        }

        printf(" ");

        for (size_t i = 0; i < n; ++i)
        {
            if (isprint(data[offset + i]))
            {
                printf("%c", data[offset + i]);
            }
            else
            {
                printf(".");
            }
        }

        printf("\n");
        offset += 16;
    }
}

bookmark_border一个使用Poco::http库封装的c++ http调用类

头文件webapi.h

#pragma once
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/HTMLForm.h>
#include <Poco/URI.h>
#include <Poco/NumberParser.h>
#include <Poco/String.h>
#include <atomic>
#include <string>

using namespace std;

class CWebApi
{
public:
	CWebApi(string baseUrl = "");
	~CWebApi();

	void setBaseUrl(const string& baseUrl);
	string getBaseUrl();
	bool httpGet(string api, string& resp, int32_t& status, string& errmsg);

private:
	string baseUrl_;
};

源文件webapi.cpp

#include "webapi.h"
#include <iostream>
#include "log.h"

CWebApi::CWebApi(string baseUrl)
	:baseUrl_(baseUrl)
{

}

CWebApi::~CWebApi()
{

}

void CWebApi::setBaseUrl(const string& baseUrl)
{
	baseUrl_ = baseUrl;
}

string CWebApi::getBaseUrl()
{
	return baseUrl_;
}

bool CWebApi::httpGet(string api, string& resp, int32_t& status, string& errmsg)
{
	LOG_INFO("BEGIN {}", api);

	bool ret = false;
	string strUrl;
	
	strUrl = baseUrl_ + api;

	try {

		Poco::URI httpUrl(strUrl);
		Poco::Net::HTTPClientSession session(httpUrl.getHost(), httpUrl.getPort());
		session.setTimeout(Poco::Timespan(8, 0), Poco::Timespan(8, 0), Poco::Timespan(8, 0));
		Poco::Net::HTTPRequest httpReq(Poco::Net::HTTPRequest::HTTP_GET, httpUrl.getPathAndQuery());
				
		httpReq.add("Connection", "close");
		session.sendRequest(httpReq);

		Poco::Net::HTTPResponse httpResp;
		istream& is = session.receiveResponse(httpResp);
		Poco::StreamCopier::copyToString(is, resp);
		status = httpResp.getStatus();

		if (status == Poco::Net::HTTPResponse::HTTP_NOT_MODIFIED) 
		{
			LOG_DEBUG("WebAPI {}, configuration not modified!", strUrl);
		}

		if (status == Poco::Net::HTTPResponse::HTTP_OK
			|| status == Poco::Net::HTTPResponse::HTTP_NOT_MODIFIED)
		{

			ret = true;
		}
		else
		{

		}

		session.reset();
	}
	catch (Poco::Net::NetException & ex) 
	{
		errmsg = ex.displayText();
	}
	catch (std::exception& exc) 
	{
		errmsg = exc.what();
	}
	catch (...) {

	}

	if (!ret)
	{
		LOG_ERROR("WebAPI {} error, status = {}, errmsg = {}", strUrl, status, errmsg);
	}
	LOG_INFO("END {}", api);

	return ret;
}

bookmark_borderC++日期时间函数

获取毫秒时间戳

int64_t GetMilliSecond()
{
    return ::chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
}

获取秒级时间戳

int32_t GetSecond()
{
	return ::chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
}

获取秒级时间戳(根据给定的年、月、日、时、分、秒)

int32_t GetSecond(int year, int month, int day, int hour, int minute, int second)
{
	tm tm_;                                    // 定义tm结构体。
	tm_.tm_year = year - 1900;                 // 年,由于tm结构体存储的是从1900年开始的时间,所以tm_year为int临时变量减去1900。
	tm_.tm_mon = month - 1;                   // 月,由于tm结构体的月份存储范围为0-11,所以tm_mon为int临时变量减去1。
	tm_.tm_mday = day;                      // 日。
	tm_.tm_hour = hour;                      // 时。
	tm_.tm_min = minute;                     // 分。
	tm_.tm_sec = second;                     // 秒。
	tm_.tm_isdst = 0;                       // 非夏令时。
	time_t t_ = mktime(&tm_);                  // 将tm结构体转换成time_t格式。
	return t_;                            
}

bookmark_border小巧易用的c++格式化库fmtlib介绍

在标准C++中常常觉得拼接字符串是一件比较麻烦的事情,sscanf/sprintf系列函数可能我们大家用的最多的, 从第一次使用fmtlib库后,就发现这个东西实在是太方便了。

下载及使用方法

可以从https://github.com/fmtlib/fmt 下载源码,不需要编译,只需要包含对应的头文件即可。

使用起来非常方便,通过几个示例来看一下:

std::string tempstr = "world";
fmt::print("{}, {}!", "Hello", tempstr);
// 十六进制表示
std::string tempstr = fmt::format("{:x}", 80);
//The date is 2018-10-09.
std::time_t t = std::time(nullptr);fmt::print("The date is {:%Y-%m-%d}.\n", *std::localtime(&t));
//tempstr =="abracadabra"
std::string tempstr = fmt::format("{0}{1}{0}", "abra", "cad");

使用了fmtlib的相关项目

bookmark_borderWindows下UTF-8与GBK相互转换


dsdafdadadf

UTF-8转GBK:

std::string UTF8ToGBK(const std::string& utf8_string)
{
	int len = MultiByteToWideChar(CP_UTF8, 0, utf8_string.c_str(), -1, NULL, 0);
	std::wstring unicode_string(len, L'\0');
	MultiByteToWideChar(CP_UTF8, 0, utf8_string.c_str(), -1, &unicode_string[0], len);

	len = WideCharToMultiByte(CP_ACP, 0, &unicode_string[0], -1, NULL, 0, NULL, NULL);
	std::string gbk_string(len, '\0');
	WideCharToMultiByte(CP_ACP, 0, &unicode_string[0], -1, &gbk_string[0], len, NULL, NULL);

	return gbk_string;
}

GBK转UTF-8:

std::string GBKToUTF8(const std::string& gbk_string)
{
	int len = MultiByteToWideChar(CP_ACP, 0, gbk_string.c_str(), (int)gbk_string.size(), NULL, 0);
	std::wstring unicode_string(len, L'\0');
	MultiByteToWideChar(CP_ACP, 0, gbk_string.c_str(), (int)gbk_string.size(), &unicode_string[0], len);

	len = WideCharToMultiByte(CP_UTF8, 0, &unicode_string[0], (int)unicode_string.size(), NULL, 0, NULL, NULL);
	std::string utf8_string(len, u8'\0');
	WideCharToMultiByte(CP_UTF8, 0, &unicode_string[0], (int)unicode_string.size(), &utf8_string[0], len, NULL, NULL);

	return utf8_string;
}

bookmark_borderrapidjson库示例及函数封装

开发工作中涉及JSON文件的解析/编码是不可避免的,象PHP/JAVA/C#等语言中都内置有非常方便的JSON操作类库,但C++自身没有提供这样的操作方法,因此不得不借助一些第三方库来完成JSON操作。之前使用过jsoncpp,感觉不是太好,对于一些含有特殊字符的json内容解析时还可能发生程序崩溃,后来接触到RadpidJSON,发现非常不错,据官方宣称是最快的JSON库,所以称之为"RAPID"。

下面是本人在工作中封装的工具函数,使用非常方便,能够满足大多数情况下JSON操作的需要,能够大节省我们的编码时间。

struct TStrMapVec
{
    vector<map<string, string>> vec_;
};

struct TJsonParseResult
{
public:
    bool FindParam(const std::string& key)
    {
        std::string name(key);
        std::transform(name.begin(), name.end(), name.begin(), ::tolower);

        auto it = m_mapParams.find(name);
        if (it != m_mapParams.end())
        {
            return true;
        }

        auto it1 = m_mapArray.find(name);
        if (it1 != m_mapArray.end())
        {
            return true;
        }

        return false;
    }

    std::string GetParam(const std::string& key)
    {
        std::string name(key);
        std::transform(name.begin(), name.end(), name.begin(), ::tolower);

        std::string value;
        auto it = m_mapParams.find(name);
        if (it != m_mapParams.end())
        {
            value = it->second;
        }
        return value;
    }

    void GetArrayParam(const std::string& key, std::vector<std::map<std::string, std::string>>& vecParams)
    {
        std::string name(key);
        std::transform(name.begin(), name.end(), name.begin(), ::tolower);

        auto it = m_mapArray.find(name);
        if (it != m_mapArray.end())
        {
            vecParams = it->second.vec_;
        }
    }

    void AddParam(const std::string& key, const std::string& value)
    {
        std::string name(key);
        std::transform(name.begin(), name.end(), name.begin(), ::tolower);
        m_mapParams[name] = value;
    }

    void AddArrayParam(const std::string& key, std::map<std::string, std::string>& mapParams)
    {
        std::string name(key);
        std::transform(name.begin(), name.end(), name.begin(), ::tolower);
        m_mapArray[name].vec_.push_back(mapParams);
    }

    void RemoveParam(std::string name)
    {
        m_mapParams.erase(name);
        m_mapArray.erase(name);
    }

    bool IsEmpty()
    {
        return m_mapParams.empty() && m_mapArray.empty();
    }

public:
    map<string, string> m_mapParams;    
    map<string, TStrMapVec> m_mapArray;
};

static void ParseJsonObj(const rapidjson::Value& jsonObj, TJsonParseResult& zjapiRes, string namePrefix = "")
{
    if (jsonObj.IsNull()
        || jsonObj.IsObject() == false)
    {
        return;
    }

    for (auto it = jsonObj.MemberBegin(); it != jsonObj.MemberEnd(); ++it)
    {
        auto key = it->name.GetString();
        const rapidjson::Value& jsonValue = it->value;
        string name = namePrefix;
        string value;

        if (name.empty() == false)
        {
            name += ".";
        }
        name += key;

        if (jsonValue.IsString()
            || jsonValue.IsNumber())
        {
            if (jsonValue.IsString())
            {
                value = jsonValue.GetString();
            }
            else if (jsonValue.IsInt())
            {
                value = to_string(jsonValue.GetInt());
            }
            else if (jsonValue.IsUint())
            {
                value = to_string(jsonValue.GetUint());
            }
            else if (jsonValue.IsInt64())
            {
                value = to_string(jsonValue.GetInt64());
            }
            else if (jsonValue.IsUint64())
            {
                value = to_string(jsonValue.GetUint64());
            }
            else if (jsonValue.IsDouble())
            {
                value = to_string(jsonValue.GetDouble());
            }

            zjapiRes.AddParam(name, value);
        }
        else if (jsonValue.IsArray())
        {
            //假定为数组时,数组元素只能是类似于{"a":1,"b":2}这样不再包含子数组
            map<string, string> mapParams;
            for (size_t i = 0; i < jsonValue.Size(); i++)
            {
                const rapidjson::Value& arrItem = jsonValue[i];
                if (arrItem.IsNull()
                    || arrItem.IsObject() == false)
                {
                    continue;
                }

                auto jsonText = std::move(JsonToString(arrItem));
                mapParams["json"] = jsonText;

                for (auto itItemMember = arrItem.MemberBegin(); itItemMember != arrItem.MemberEnd(); ++itItemMember)
                {
                    auto itemMemberKey = itItemMember->name.GetString();
                    const rapidjson::Value& jsonItemMemberValue = itItemMember->value;

                    if (jsonItemMemberValue.IsString())
                    {
                        mapParams[itemMemberKey] = jsonItemMemberValue.GetString();
                    }
                    else if (jsonItemMemberValue.IsInt())
                    {
                        mapParams[itemMemberKey] = to_string(jsonItemMemberValue.GetInt());
                    }
                    else if (jsonItemMemberValue.IsUint())
                    {
                        mapParams[itemMemberKey] = to_string(jsonItemMemberValue.GetUint());
                    }
                    else if (jsonItemMemberValue.IsInt64())
                    {
                        mapParams[itemMemberKey] = to_string(jsonItemMemberValue.GetInt64());
                    }
                    else if (jsonItemMemberValue.IsUint64())
                    {
                        mapParams[itemMemberKey] = to_string(jsonItemMemberValue.GetUint64());
                    }
                    else if (jsonItemMemberValue.IsArray())
                    {
                        mapParams[itemMemberKey] = std::move(JsonToString(jsonItemMemberValue));
                    }
                }

                zjapiRes.AddArrayParam(name, mapParams);
            }
        }
        else if (jsonValue.IsObject())
        {
            ParseJsonObj(jsonValue, zjapiRes, name);
        }
    }
}

//JSON解析工具函数
void ParseJson(const std::string& jsonRes, TJsonParseResult& jsonparseresult)
{
    rapidjson::Document jsonDoc;
    jsonDoc.Parse<0>(jsonRes.c_str());
    if (jsonDoc.HasParseError())
    {
        rapidjson::ParseErrorCode code = jsonDoc.GetParseError();
        return;
    }

    ParseJsonObj(jsonDoc, jsonparseresult);
}

比如对于json内容{"a":1,"b":2},调用ParseJson后,输出参数jsonparseresult的map中存在2个元素:
"a" => 1, "b" => 2

bookmark_borderPoco C++库简介 (转)

学习一个框架前,要先明白它的是什么,为什么,怎么用.下面这些文字,是从中文poco官网上转过来的,正如poco c++库的特点,非常清晰,代码风格更是一目了然.

   poco开发库的特点,非常适合写后台处理程序,效率也是很高的.前台界面程序使用Qt框架库,非常好,两个库相辅相成,可解决项目上的大部分问题.

注:更多详情可以访问Poco官方网站:https://pocoproject.org

Poco C++库是:

  • 一系列C++类库,类似Java类库,.Net框架,Apple的Cocoa;

  • 侧重于互联网时代的网络应用程序

  • 使用高效的,现代的标准ANSI/ISO C++,并基于STL

  • 高可移值性,并可在多个平台下可用

  • 开源,并使用Boost Software License发布

  • 不管是否商用,都完全免费

特性:

  • DynamicAny与Any类

  • Cache框架

  • 基于OpenSSL的密码系统

  • 日期,时间类库

  • 事件和通知框架

  • FTP客户端

  • 跨平台的文件系统类库

  • HTML表单类库

  • HTTP客户端和服务端(支持SSL),C++ 服务器页面编译器

  • 日志框架

  • 多线程框架(线程池,活动对象,工作队列等)

  • POP3客户端类库

  • 跨平台,一次编写,多平台编译和运行

  • 进程管理和进程间通信类库

  • 反射框架

  • 基于PCRE的正则表达式

  • SMTP客户端类库

  • 数据库访问类库(SQLite,MySQL, ODBC)

  • 支持SSL/TLS,基于OpenSSL

  • 动态类库加载

  • 先进的内存和指针管理(Buffer, Pool)

  • Socket类库

  • 网络数据流类库,支持Base64,HexBinary编解码,压缩等等

  • 字符串格式化和其它字符串工具类库

  • TCP服务器框架(多线程)

  • 文本编码和转换

  • Tuples

  • URI支持

  • 支持UTF8和Unicode编码

  • UUID生成器

  • XML生成和解析器

  • Zip文件操作类库

Poco的使命

  • Poco是一个强大的类库,并易于构建自己的应用程序

  • Poco帮助你创建跨平台的应用程序(一次编写,多平台编译和运行)

  • 模块化和可扩展,可应用于嵌入式到企业级程序

  • 提供全面,易懂的编程接口

  • 使用C++语言,快速且高效

  • Poco崇尚简易

  • 在设计,代码风格和文档上保持一致

  • Poco强调代码质量,包括可读性,综合性,一致性,编码风格和可测试性

  • Poco使得C++编程更加容易

指导方针

  • 高度关注代码质量,编码风格,一致性,可读性

  • 高度关注测试

  • 注重实用性

  • 基于现有的,可靠的组件开发

版本历史

  • Summer 2004: Günter Obiltschnig started development

  • February 2005: First release on SourceForge

  • (Release 0.91 under Sleepycat license)

  • May 2005: First contributions by Aleksandar Fabijanic

  • January 2006: Release 1.0

  • March 2006: Release 1.1

  • July 2006: Moved to Boost license, POCO Community Website

  • August 2006: Release 1.2

  • May 2007: Release 1.3

  • July 2010: Stable Release 1.3.7,

  • about 20 contributors, used in 100s of projects

支持平台

  • Microsoft Windows

  • Linux

  • Mac OS X

  • HP-UX, Solaris, AIX*

  • Embedded Linux (uClibc, glibc)

  • iOS

  • Windows Embedded CE

  • QNX

Poco应用场景

  • 创建自动化的中间件和设备

  • 工业自动化和工业设备

  • 流量控制系统

  • 健康系统

  • 测量,数据收集和测试系统

  • 消费电子产品和家庭自动化产品

  • 测量

  • 航空交通管理系统

  • VoIP

  • 票务和入口控制系统

  • 包装应用程序

Poco的优势

  • 全面的,完整的C++框架,可减少开发工作,快速让产品走向市场

  • 易学易使用,以及相当多的示例代码和良好的文档

  • 原生的C++代码,性能优秀,低内存占用

  • 平台无关,一处编写,跨平台编译和运行

    • 大多数情况下,可以开发机上完成开发和调试工作

    • 可以非常容易的迁移到新平台

       

 

设计 & 实现

   基于现代的、标准的ANSI C++编写,使用C++ STL库。模块化设计,极少的外部依赖,易于编译和使用。结合传统的面向对象与现代的C++设计,代码易读,代码风格统一,以及相当全面的测试用例。

支持平台

  • 桌面/服务器: Windows, Linux, Mac OS X, Solaris, HP-UX, AIX

  • 嵌入式系统: Windows Embedded CE, Embedded Linux (uClibc or glibc), iOS, QNX, VxWorks, Android

  • 最低系统要求: 75 MHz ARM9, 8 MB RAM (Embedded Linux).

License

  • Boost Software License 1.0

核心特性

  • 支持Any 和 DynamicAny 的动态类型

  • 提供缓存框架

  • 日期和时间

  • 事件与通知框架

  • (libPoco.com翻译,转载请注明)

  • 基于PCRE的正则表达式

  • 动态库加载

  • 完美的指针和内存管理 (buffer, pool)

  • 字符串格式化和工具

  • tuples

压缩

  • 基于zlib的压缩/解压类

  • 创建和解压ZIP文件

加密

  • 支持多种hash算法

  • 支持X509数字证书

  • 对称的和 RSA算法

  • 支持streams加解密

  • 基于OpenSSL

数据库

  • 不同的数据库提供了统一的访问接口(SQLite, MySQL, ODBC)

  • 自动数据类型匹配

  • 支持集合类 (std::vector, std::set, std::map, etc.)

  • 支持记录集和tuples

  • 支持连接池

文件系统

  • 跨平台的文件路径操作

  • 目录列表

  • 通配符支持

  • 文件属性管理

日志

  • 可扩展的日志框架,可拔插的日志通道和消息格式化

  • 日志可输出到终端,日志文件,syslog,远程syslog,Windows事件服务

多线程

  • 线程和线程同步类库

  • 线程池

  • 工作队列

  • 活动对象

  • 任务管理

  • 定时器

网络

  • 流,数据报,多播,服务器和原生socket.

  • TCP服务器框架(多线程)

  • 反射服务器框架

  • HTTP(S)客户和服务器框架

  • HTTP认证

  • CSP(C++ Server Page)编译器

  • FTP客户端

  • SMTP和POP3客户端

  • (libPoco.com翻译,转载请注明)

  • 支持URI, UUID

  • HTML表单处理

  • MIME支持

  • 基于 OpenSSL支持SSL/TLS

  • WebSocket (RFC 6455) 客户和服务器

进程管理和 IPC

  • 进程启停

  • 进程间同步

  • 共享内存

  • Base64 和 HexBinary 编解码

  • 压缩(zlib)

  • 行结束符车换

  • 内存stream

  • 文本编码转换

  • URI流读取

文本编码

  • 支持UTF-8 和 Unicode

  • 文本编码和转换

  • 字符分类

工具类

  • 支持命令行和服务器程序框架

  • 命令行参数解析器

  • 配置文件解析

  • 支持Unix和Windows后台服务

XML

  • 基于 Expat 的XML解析

  • SAX2 (Simple API for XML, version 2) 解析器

  • DOM (Document Object Model, Level 1-3) 解析器

  • XML生成器