背景
一直说Tars是使用epoll模型的,而且之前校招面试的时候也老会被问到epoll,所以这次打算先把epoll看一下,看看tars是怎么做epoll的,但由于对网络编程没有什么了解,所以会先看一些基础的其他类。
在网上找了一个系列博客,感觉还挺多的,可以看看。
https://blog.csdn.net/stpeace/category_7554860.html
大概会照着他这个目录看一看。
他的目录中第二篇看的是一个tc_loki.h
,但我看了下和epoll暂时没有什么关系,所以先跳过这个文件,从tc_commom.h
开始看。
tc_commom
这个文件是基础工具类,主要是tars提供的一些common函数,能够在开发中直接使用的,能节省很多时间,都是一些有用的小函数,而且都是static的,可以直接用,工作中使用过很多。
大致扫了一眼头文件,发现原来提供了这么多基础函数啊,以后类似在C++中遇到分割字符串等基础工作再也不用手动造轮子了,用这里面的基础函数既省事也放心,函数类型大致可以分为以下几类:
- sleep函数
- 浮点数比较
- 字符串相关操作
- 时间相关
- 数值和字符串或者时间转换
- 其他
针对其中一些函数,还使用了模板特化
tc_exception
tars中的异常类,主要包装了一个buffer和一个错误码code,并且针对不同平台做了一些兼容处理。
tc_socket
封装了socket编程,看源码里注释都很清晰了,这里挑几个函数看一看。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/*
* 这里面用到了几个没见过的值,简单说明一下
* SOCK_STREAM表示基于TCP,SOCK_DGRAM则是基于UDP,表示tars是支持这两种协议的,而且默认是SOCK_STREAM
* iDomain的默认值是AF_INET,查了下就理解成基于ipv4的地址就行,就是我们常用的那种host:port
*/
void TC_Socket::createSocket(int iSocketType, int iDomain)
{
assert(iSocketType == SOCK_STREAM || iSocketType == SOCK_DGRAM);
close();
_iDomain = iDomain;
_sock = socket(iDomain, iSocketType, 0);
if(_sock < 0)
{
_sock = INVALID_SOCKET;
THROW_EXCEPTION_SYSCODE(TC_Socket_Exception, "[TC_Socket::createSocket] create socket error");
// throw TC_Socket_Exception("[TC_Socket::createSocket] create socket error! :" + string(strerror(errno)));
}
else
{
ignoreSigPipe();//这个是用来忽视什么信号的,系列2里有
}
}
对socket编程完全不了解呀,看下来云里雾里,但感觉就是把socket抽象封装了一层,以后再看。
tc_epoll
epoll操作封装类。
首先有一个平平无奇的TC_Epoller_Exception类。
TC_Epoller默认采用了ET方式触发,epoll初始化不是线程安全的,需要在一开始全局设置一下。
1 | //创建epoll,可以看到这个size其实最终并没有赋值给_max_connections, |
tc_autoptr.h
这是tars里自己实现的自动指针类,底层利用atomic实现了引用计数,所有需要智能指针的类都需要从TC_HandleBase类继承。
除了atomic引用计数外,该类还有一个表示是否删除的标志位,看起来是为了防止del多次。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80class UTIL_DLL_API TC_HandleBase
{
public:
/**
* @brief 复制.
*
* @return TC_HandleBase&
*/
TC_HandleBase& operator=(const TC_HandleBase&)
{
return *this;
}
/**
* @brief 增加计数
*/
void incRef() { ++_atomic; }
/**
* @brief 减少计数, 当计数==0时, 且需要删除数据时, 释放对象
*/
void decRef()
{
if((--_atomic) == 0 && !_bNoDelete)
{
_bNoDelete = true;
delete this;
}
}
/**
* @brief 获取计数.
*
* @return int 计数值
*/
int getRef() const { return _atomic; }
/**
* @brief 设置不自动释放.
*
* @param b 是否自动删除,true or false
*/
void setNoDelete(bool b) { _bNoDelete = b; }
protected:
/**
* @brief 构造函数
*/
TC_HandleBase() : _atomic(0), _bNoDelete(false)
{
}
/**
* @brief 拷贝构造
*/
TC_HandleBase(const TC_HandleBase&) : _atomic(0), _bNoDelete(false)
{
}
/**
* @brief 析够
*/
virtual ~TC_HandleBase()
{
}
protected:
/**
* 计数
*/
std::atomic<int> _atomic;
/**
* 是否自动删除
*/
bool _bNoDelete;
};
接下来是一个智能指针模板类TC_AutoPtr
,方法没什么好说的,只是要注意模板参数T必须继承于TC_HandleBase
。
还有个要注意点的点是这些智能指针类之间的等于、不等于、小于比较全都是直接比较指针本身,而不是比较指针指向的值。
tc_config
配置文件读取类。
配置文件能实现配置参数和代码逻辑的分离,如果需要修改配置参数,仅仅修改配置即可。
配置文件说白了,就是把数据从外存导到内存来使用(我觉得这个理解挺好的)。
tars的配置文件在我使用过程中就是类似于读取/a/b/c<d>
这种格式,就能从第一层a开始取到最后一层d所指的数据,对应下面这种格式的配置文件:1
2
3
4
5
6
7<a>
<b>
<c>
d=hello
</c>
</b>
</a>
tars的配置文件就长这样,所以这个tc_config类主要就是用来解析这种格式的文件,知道了作用,接下来开始看源码。
Tars读取配置文件是线程安全的,将整个配置文件分为了域和参数两部分,其中kv键值对的k是参数,而<>表示的是域,用以下宏定义的字符来分割。1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 域分隔符
*/
const char TC_CONFIG_DOMAIN_SEP = '/';
/**
* 参数开始符
*/
const char TC_CONFIG_PARAM_BEGIN = '<';
/**
* 参数结束符
*/
const char TC_CONFIG_PARAM_END = '>';
有几个类:
- 定义了两个异常类
- TC_ConfigDomain类,定义配置文件中域的类。上层的TC_Config类就包含一个 TC_ConfigDomain类的成员变量,域类里会有子域,感觉实现上面像个广度优先的树,TC_Config类就是root节点。这种用树的结构来实现的例子也要记住。
看几个函数吧:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 //tc_config.h
/**
* @brief 解析一个domain
* 对形如"/Main/Domain<Name>"的path进行解析,解析的结果为一个
* DomainPath 类型,包括路径_domains和路径中对应的配置项_param.
*
* @param path 一个经过处理的字符串,必须符合一定的要求
* @param bWithParam "/Main/Domain<Name>"时bWithParam为ture
* "/Main/Domain"时bWithParam为false
* @return DomainPath 一个DomainPath对象,解析出域中的域名和参数
*/
static DomainPath parseDomainName(const string& path, bool bWithParam);
//定义
//tc_config.cpp
TC_ConfigDomain::DomainPath TC_ConfigDomain::parseDomainName(const string& path, bool bWithParam)
{
TC_ConfigDomain::DomainPath dp;
if(bWithParam)
{
string::size_type pos1 = path.find_first_of(TC_CONFIG_PARAM_BEGIN);
if(pos1 == string::npos)
{
throw TC_Config_Exception("[TC_Config::parseDomainName] : param path '" + path + "' is invalid!" );
}
if(path[0] != TC_CONFIG_DOMAIN_SEP)
{
throw TC_Config_Exception("[TC_Config::parseDomainName] : param path '" + path + "' must start with '/'!" );
}
string::size_type pos2 = path.find_first_of(TC_CONFIG_PARAM_END);
if(pos2 == string::npos)
{
throw TC_Config_Exception("[TC_Config::parseDomainName] : param path '" + path + "' is invalid!" );
}
dp._domains = TC_Common::sepstr<string>(path.substr(1, pos1-1), TC_Common::tostr(TC_CONFIG_DOMAIN_SEP));
dp._param = path.substr(pos1 + 1, pos2 - pos1 - 1);
}
else
{
// if(path.length() <= 1 || path[0] != TC_CONFIG_DOMAIN_SEP)
if(path[0] != TC_CONFIG_DOMAIN_SEP)
{
throw TC_Config_Exception("[TC_Config::parseDomainName] : param path '" + path + "' must start with '/'!" );
}
dp._domains = TC_Common::sepstr<string>(path.substr(1), TC_Common::tostr(TC_CONFIG_DOMAIN_SEP));
}
return dp;
}
//之所以要看这个方法,是因为我想了一下,
//这个简答的字符串处理如果换成是我来写,那我肯定是for循环来处理了,
//所以看到这个实现的时候有种恍然大悟的感觉,啥叫优雅的写法,这就是呀……
//但是在setParamValue方法里不知道为啥就用了for循环,感觉代码不是一个人写的呀。
递归搜索子域的方法getSubTcConfigDomain
怎么感觉没明白呢?它搜了个寂寞?既然是搜索,但参数里没有个要搜索的目标啊,就给了两个迭代器?好奇怪。后需要再看下。
1 | //20200701联合tc_config::operator[]再看,感觉理解了一些, |
tars的配置里配置项如果没写=
,看起来是会把整行当做一个key,然后对应值是空。
1 | /** |
tc_config的operator[]方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15string TC_Config::operator[](const string &path) const
{
TC_ConfigDomain::DomainPath dp = TC_ConfigDomain::parseDomainName(path, true);
//这里所搜索的域其实就是dp._domains的最后一个元素对应的域
//和上面的getSubTcConfigDomain结合起来看更好理解
const TC_ConfigDomain *pTcConfigDomain = searchTcConfigDomain(dp._domains);
if(pTcConfigDomain == NULL)
{
throw TC_ConfigNoParam_Exception("[TC_Config::operator[]] path '" + path + "' not exits!");
}
return pTcConfigDomain->getParamValue(dp._param);
}
1 | /** |