目录
八、TcpConnection的id如何存放,当从client connection收到数据,如何得知其id
muduo是陈硕个人使用C++开发的一款网络库,代码写的很有学习价值,总结的内容来自书籍《Linux 多线程服务器端编程》,也是由陈硕编写,可以配合github代码一起使用。
muduo github网址:https://github.com/chenshuo/muduo
一、TcpServer接受新连接
1、TcpServer
TcpServer新建连接的相关函数调用顺序见下图
Channel::handleEvent()的触发条件是listening socket可读,表明有新连接到达,TcpServer会为新连接创建对应的TcpConnection对象。在新连接到达时,Acceptor会回调newConnection(),后者会创建TcpConnection对象conn,把它加入connectionMap,设置好callback,再调用conn->connectEstablished(),其中会调用用户提供的ConnectionCallback。
2、TcpConnection
是muduo最核心也是最复杂的类,TcpConnection使用Channel获得socket上的IO事件,它会自己处理writable事件,而把readable事件通过MessageCallback传送给用户,TcpConnection拥有Tcp socket,析构函数会close(fd)。
二、TcpConnection断开连接
muduo可用被动关闭,即对方先关闭连接,本地read()返回0,触发关闭逻辑。也可以调用TcpConnection的forceClose()成员函数,用于主动关闭连接,后者会调用handleClose()函数,函数调用的流程如下图所示:
TcpServer会向TcpConnection注册CloseCallback,用于接收连接断开的消息。handleClose()的主要功能是调用closeCallback_,这个回调绑定到TcpServer::removeConnection(),后者会把conn从ConnectionMap里删除。
TcpConnection并没有提供close()函数,而是使用shutdown()函数,muduo把主动关闭连接这件事分成了两步:如果要主动关闭连接,先关闭本地的“写”端,等到对方关闭连接后,再关闭本地“读”端,这种half-close主要是为了防止关闭连接时,对方还在发送数据。
三、TcpConnection发送数据
用户通过调用TcpConnection::send()来发送数据,由于消息的读写都必须在EventLoop的同一个线程,因此必须确保线程安全性,因此在send的时候,会先检测是否在当前的IO线程,如果是就直接调用sendInLoop(),如果不在一个线程的话,会把messsage复制一份,传给IO线程中的sendInLoop()来发送。
sendInLoop()会尝试直接发送数据,如果一次发送完,就不会启用WriteCallback,如果只发送了部分数据,则把剩余的数据放入outputBuffer_,并开始关注writable事件,然后在handleWrite()中发送剩余的数据。
四、如何限制服务器的最大并发连接数
准备一个空闲的文件描述符,当本进程使用的文件描述符已经达到上限时(Acceptor的handleRead()执行accept,返回errno为EMFILE),先关闭这个空闲的文件描述符,获得一个文件描述符的名额,再accept()拿到新socket连接的描述符,随后立即close()掉,这样就优雅的断开了客户端连接,最后在重新打开一个空闲文件描述符,把“坑”占住,以备再次出现这种情况使用。
五、如何踢掉空闲连接
使用timeing wheel,大致的数据结构为:8个桶(hash set)组成的循环队列(超时时间8s)。第一个桶放1秒之后将要超时的连接,第二个桶放2秒之后将要超时的连接以此类推,每个连接一收到数据就把自己放入第8个桶,然后在每秒的timer里把第一个桶里的连接断开,把这个空桶挪到队尾。这样不用每次检查所有的连接。
连接超时被踢掉的过程:
连接刷新:
具体实现时,桶里放的不是连接,而是Entry struct,每个Entry包含TcpConnection的weak_ptr,当Entry的引用计数递减到0,说明它不存在于任何一个盒子里,就会连接超时,Entry的析构函数就会断开连接。
六、发送数据时的流量控制
muduo设置了WriteCompleteCallback()和HighWaterCallback()作为“低水位回调”和“高水位回调”,当发送缓冲区清空时调用WriteCompleteCallback(),当输出缓冲区长度超过用户规定大小,就调用HighWaterCallback()。
七、为什么TcpConnection对象是用shared_ptr来管理对象
因为从TCP连接收到一个request开始,程序处理这个连接可能要花费一段时间,为了确认socket连接在处理request期间是否已经关闭了,需要持有TcpConnection对象的引用计数。
八、TcpConnection的id如何存放,当从client connection收到数据,如何得知其id
TcpConnection的id使用std::map<int, TcpConnectionPtr> clientConns_保存id到client connection的映射,muduo的TcpConnection还用context功能,每个TcpConnection都有一个boost::any成员,可以用于保存与connection绑定的任意数据,比如连接id,连接最后数据到达时间,连接所代表的用户名等。