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