高性能C++网络库libtnet实现:Connection
Connection
libtnet只支持IPv4 TCP Connection,之所以这么做都是为了使得实现尽可能的简单。我们主要在Connection类中封装了对tcp连接的操作。
Connection继承自std::enable_shared_from_this,也就意味着外部我们会操作其shared_ptr,libtnet几乎所有的对象都采用智能指针的方式来进行内存管理。
当Connection创建成功之后,会通过IOLoop的addHandler接口,将其绑定到ioloop上面:
ConnectionPtr_t conn = shared_from_this();
m_loop->addHandler(m_fd, TNET_READ, std::bind(&Connection::onHandler, conn, _1, _2));
因为我们直接在std::bind里面使用了shared_ptr,所以ioloop自然引用了该Connection,外部不需要在存储Connection,以防内存泄露。
对于一个connection而言,它只可能有几种状态,
- Connecting,表明正在尝试连接,发生在connect返回EINPROGRESS。
- Connected,连接已经建立成功,发生在connect成功或者accept成功。
- Disconnecting,表明连接正在断开,发生在用户主动调用shutDown之后。
- Disconnected,连接已经断开,这时候对应的socket也会被close掉。
Event Callback
在Connection中,我们使用一个event callback来绑定相应事件的回调。主要有如下connection event:
- Conn_EstablishedEvent, 当server accept成功,创建了Connection对象之后,触发。
- Conn_ConnectEvent,当client connect成功,触发。
- Conn_ConnectingEvent,当client connect返回EINPROGRESS,触发。
- Conn_ReadEvent,当连接可读,触发。
- Conn_WriteCompleteEvent,当发送的数据都发送完毕,触发。
- Conn_ErrorEvent,当连接有错误发生,触发。
- Conn_CloseEvent,当连接主动或者被动关闭,触发。
event callback原型如下:
typedef shared_ptr<Connection> ConnectionPtr_t;
typedef std::function<void (const ConnectionPtr_t&, ConnEvent, const void* context)> ConnEventCallback_t;
对应不同的事件,触发的时候context的内容不同。现阶段,只有ReadEvent的时候context为StackBuffer,原型如下:
class StackBuffer
{
public:
StackBuffer(const char* buf, size_t c) : buffer(buf), count(c) {}
const char* buffer;
size_t count;
};
当连接可读的时候,Connection会将数据读取到栈上面,并用StackBuffer来指代,这样当外部处理ReadEvent的时候就能通过将context转换成StackBuffer获取到读取的数据。
下面简单说明一下一些设计上面的取舍:
为什么只提供一个event callback,而不提供read callback,write complete callback,close callback多个回调接口?
libtnet的所有callback都采用的是std::function实现,而该对象占用32字节,如果每个event都提供一个对应的callback,那么内存的开销会有点大,同时大部分时候很多callback我们是不感兴趣的。
还有一个重要的原因在于只提供一个event callback,外部的一些对象就可以通过该callback跟Connection绑定,也就是将其自身的生命周期与Connection绑定在了一起,当Connection删除的时候该对象也自行删除。libtnet中,HttpConnection,WsConnection都是采用该方法,因为对于一个Http连接来说,如果底层的Tcp连接都断开无效了,基于Tcp的Http连接自然就无效了。
Connection为什么不缓存读取的数据,而是交由外部callback去处理?
Connection作为一个底层的类,对于读取的数据,并不知道具体需要如何处理,所以还不如将数据直接发到外层,供上层实际的应用逻辑处理。但是如果后续Connection考虑支持ssl,那么就需要进行缓存数据了。
Write
Connection建立之后,默认只会在ioloop中设置TNET_READ事件,因为epoll采用的水平触发模式,如果直接设置TNET_WRITE事件,那么epoll会一直通知socket可写,但实际上并没有可以发送的数据。
所以,libtnet采用如下的方式进行数据发送:
- 直接调用writev函数进行数据发送
- 如果数据未发送完毕,则向ioloop注册TNET_WRITE事件,下次触发可写的时候继续发送,直至发送成功,清除TNET_WRITE事件。
另外,在发送的时候,我们还需要考虑signal pipe的情况,所以需要忽略该singal。使用如下方式:
class IgnoreSigPipe
{
public:
IgnoreSigPipe()
{
signal(SIGPIPE, SIG_IGN);
}
};
static IgnoreSigPipe initObj;
当libtnet启动的时候,就忽略了signal pipe信号。虽然这样做稍微有一点副作用,但大部分时候我们并不需要关注SIGPIPE信号。
Kick Off Connection
通常,为了处理不活跃连接,程序都会将每个connection设置一个timer,如果timer到了该连接仍然没有交互,则会删除该连接,否则则继续更新timer。另一种做法就是提供一个time wheel,将connection放置在该wheel中,如果有交互,则在wheel中移动。
libtnet采用了一种更简单,但是精度比较差的做法。
当server成功创建一个connection之后,将会添加到一个ConnChecker中,checker保存的是该connection的weak_ptr。每隔一段时间,checker检查一批connection:
- 如果connection weak_ptr无法lock提升至shared_ptr,证明该连接已经删除,checker直接移除。
- 如果connection处于connecting状态,并且超过了设置的最大连接超时时间,shutDown该connection。
- 如果connection处于connected状态,并且在一段时间内没有任何交互,shutDown。
ConnChecker的检查间隔以及每次检查步数都可以通过外部设置。使用ConnChecker虽然简单,但是在连接数过大的情况下面,一些过期的connection不能立刻被清理掉。对于这个问题,我觉得可以接受,一个连接一秒之后被关闭还是两秒之后被关闭,差别真的不大。如果我们真的需要对一些connection做精确的时间控制,那直接可以对其使用timer。
libtnet的地址https://github.com/siddontang/libtnet,欢迎围观。
高性能C++网络库libtnet实现:Connection的更多相关文章
- 高性能C++网络库libtnet实现:http
HTTP libtnet提供了简单的http支持,使用也很简单. 一个简单的http server: void onHandler(const HttpConnectionPtr_t& con ...
- 高性能C++网络库libtnet实现:IOLoop
IOLoop libtnet采用的是prefork + event loop的架构方式,prefork就是server在启动的时候预先fork多个子进程同时工作,而event loop则是基于epol ...
- 高性能C++网络库libtnet实践:comet单机百万连接挂载测试
最近在用go语言做一个挂载大量长连接的推送服务器,虽然已经完成,但是内存占用情况让我不怎么满意,于是考虑使用libtnet来重新实现一个.后续我会使用comet来表明推送服务器. 对于comet来说, ...
- 发布一个参考tornado的高性能c++网络库:libtnet
libtnet是一个用c++编写的高性能网络库,它在设计上面主要参考tornado,为服务端网络编程提供简洁而高效的接口,非常易于使用. Echo Server void onConnEvent(co ...
- 专注于HTTP的高性能高易用性网络库:Fslib.network库
博客列表页:http://blog.fishlee.net/tag/fslib-network/ 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库) FSLib.Networ ...
- [开源] gnet: 一个轻量级且高性能的 Golang 网络库
Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...
- boost.ASIO-可能是下一代C++标准的网络库
曾几何时,Boost中有一个Socket库,但后来没有了下文,C++社区一直在翘首盼望一个标准网络库的出现,网络上开源的网络库也有不少,例如Apache Portable Runtime就是比较著名的 ...
- Cowboy 开源 WebSocket 网络库
Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...
- 网络库crash以及boost asio strand dispath分析
最近在做服务器的稳定性的相关测试,服务器的网络底层使用的是boost asio,然后自己做的二次封装以更好的满足需求. 服务器昨天晚上发现crash了一次,之前测试了将近半个多月,有一次是莫名的退出了 ...
随机推荐
- linux 下搭建 ftp
最近为了方便目标板与PC机上LINUX之间的通讯,就在LINUX搭建了FTP,工作不难,写个总结.主要经过以下几个步骤: 1.检查是否安装了vsftpd服务器 可以用rpm -q vsftpd命令来查 ...
- js_DOM属性
.nodeType==1,指的是li, .nodeType==3,则指的文本节点. .children属性,和 .childNodes属性类似,但是只会包含元素节点,而不会包含文本节点. .child ...
- shell的wc命令统计 head tail命令详解
Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数.字数.行数,并将统计结果显示输出. 1.命令格式: wc [选项]文件... 2.命令功能: 统计指定文件中的字节数. ...
- access数据库
//访问动态创建access数据库 string conn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Server.M ...
- qml+opencv(二),实现人脸检测
http://blog.csdn.net/zhx6044/article/details/45048765
- libvirt(virsh命令介绍)
有了virt-install是安装虚拟机的命令,当然也需要一个管理虚拟机的命令了,那就是virsh. virsh命令使用 virsh <command> <domain-id> ...
- go - 变量和常量
1.定义变量 goLang中定义变量的方式很多 先声明再使用:如果定义的变量未使用编译时会报错 a. /*定义单个变量*/ var varName type //定义一个 type 类型的变量 var ...
- java中排序一个字符串数组
package test_set_map; import java.util.Arrays; import java.util.Collections; public class Test_Colle ...
- SQL基础使用
result = executeQuery(“ SELECT FName, FSalary FROM T_Employee ”); for(i=0;i<result.count;i++) { s ...
- python groupby
groupby() 将key函数作用于原循环器的各个元素.根据key函数结果,将拥有相同函数结果的元素分到一个新的循环器.每个新的循环器以函数返回结果为标签. 这就好像一群人的身高作为循环器.我们可以 ...