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的更多相关文章

  1. 高性能C++网络库libtnet实现:http

    HTTP libtnet提供了简单的http支持,使用也很简单. 一个简单的http server: void onHandler(const HttpConnectionPtr_t& con ...

  2. 高性能C++网络库libtnet实现:IOLoop

    IOLoop libtnet采用的是prefork + event loop的架构方式,prefork就是server在启动的时候预先fork多个子进程同时工作,而event loop则是基于epol ...

  3. 高性能C++网络库libtnet实践:comet单机百万连接挂载测试

    最近在用go语言做一个挂载大量长连接的推送服务器,虽然已经完成,但是内存占用情况让我不怎么满意,于是考虑使用libtnet来重新实现一个.后续我会使用comet来表明推送服务器. 对于comet来说, ...

  4. 发布一个参考tornado的高性能c++网络库:libtnet

    libtnet是一个用c++编写的高性能网络库,它在设计上面主要参考tornado,为服务端网络编程提供简洁而高效的接口,非常易于使用. Echo Server void onConnEvent(co ...

  5. 专注于HTTP的高性能高易用性网络库:Fslib.network库

    博客列表页:http://blog.fishlee.net/tag/fslib-network/ 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库) FSLib.Networ ...

  6. [开源] gnet: 一个轻量级且高性能的 Golang 网络库

    Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...

  7. boost.ASIO-可能是下一代C++标准的网络库

    曾几何时,Boost中有一个Socket库,但后来没有了下文,C++社区一直在翘首盼望一个标准网络库的出现,网络上开源的网络库也有不少,例如Apache Portable Runtime就是比较著名的 ...

  8. Cowboy 开源 WebSocket 网络库

    Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...

  9. 网络库crash以及boost asio strand dispath分析

    最近在做服务器的稳定性的相关测试,服务器的网络底层使用的是boost asio,然后自己做的二次封装以更好的满足需求. 服务器昨天晚上发现crash了一次,之前测试了将近半个多月,有一次是莫名的退出了 ...

随机推荐

  1. Android Studio入门到精通

    链接地址:http://blog.csdn.net/yanbober/article/details/45306483 目标:Android Studio新手–>下载安装配置–>零基础入门 ...

  2. MacBook外接显示器设置方法(新手入门贴)

    小屏幕的MacBook/MacBook Pro放在桌上长时间使用,眼睛比较累,而且,长时间低头看屏幕,易得颈椎病,绝对有损健康.配一台大屏幕的外置显示器不失为两全其美的好办法. 首先,得买一台中意的大 ...

  3. Codeforces 490B Queue【模拟】

    题意还是很好理解的,根据题目给出描述条件然后求出这串QUEUE 我的做法就是用两个数组 before[] 和 after[] 表示 ai 前面的前面的人的学号 和 ai 后面的后面的人的学号 ex[] ...

  4. Struts 2 OGNL

    1.什么是OGNL? 2.Struts 2 OGNL 表达式      ======================  华丽丽的分割线  ======================  1.什么是OG ...

  5. c# 数据库编程(利用DataSet 和 DataAdaper对象操作数据库--单表操作)

    一.概述 前面2篇文章,介绍了使用SqlCommand对象利用sql命令来操作数据库. 这篇文章我们来介绍使用c#的DataSet 和 DataAdaper对象操作操作数据库. 先来介绍下这两个对象是 ...

  6. 第2次增加ssh 主机信任脚本

    dr-mysql01:/root# cat a1.sh #用户名 uname="$1" #密码 passwd="$2" #执行检测并安装expect模块 ep= ...

  7. THUSC2015

    这些题目在BZOJ上面有,可惜是权限题.话说BZOJ上面的题目真的挺好的,要不是我穷,早就去弄个号了! 言归正传,今年的题目难度可以由一个名人名言看出: 题目太水.--某某神犇 可是我掂量了一下,发现 ...

  8. IntelliJ idea 14 集成 tomcat 7

    来到公司第一个任务就是先把web项目跑起来,所以如何在IntelliJ idea14中把Tomcat集成起来也是个不小的问题. 首先确认你的idea 14 不是 Communit Edition 社区 ...

  9. BZOJ 1499 NOI2005 瑰丽华尔兹 单调队列

    题目大意:给定一个m*n的地图,一些点有障碍物,钢琴初始在一个点,每一个时间段能够选择向给定的方向移动一段距离,求最长路径长 朴素DP的话,我们有T个时间段,每一个时间段有m*n个点,n个时间,一定会 ...

  10. stm32之watchdog

    在嵌入式系统中,由于MCU的工作常常受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统陷入停滞状态,发送不可预料的后果,所以出 ...