简介
TcpConnection类,TcpServer类,Acceptor类是构成非阻塞TCP网络编程库的重要组成部分。
本文主要针对muduo源码进行分析。(Acceptor类在上篇中已经分析过了)
muduo网络库的单线程设计方式,即一个EventLoop 处理所有的事件,包括链接的建立、IO、计算、以及链接的销毁,多线程的方式即每一个连接一个EventLoop。(one loop per thread)
TcpServer
多线程的muduo::TcpServer,主要通过添加一个EventLoopThreadPool 事件循环线程池实现,新建TcpConnection时从event loop pool里挑选一个loop给TcpConnection用。 也就是说多线程TcpServer自己的EventLoop只用来接受新连接, 而新连接会用其他EventLoop来执行IO。 (单线程TcpServer的EventLoop是与TcpConnection共享的。)
EventLoopThreadPooll 按one loop per thread的思想实现多线程TcpServer, 此时主线程循环只负责TCP链接的建立,及任务的分配,需要让哪个线程干活, 就把timer或IO(如TCP连接) 注册到那个线程的循环里即可;对实时性有要求的connection可以单独用一个线程; 数据量大的connection可以独占一个线程;并把数据处理任务分摊到另几个计算线程中(用线程池);其他次要的辅助性connections共享一个线程。
线程池设计模式
池是一种设计模式,线程是是一种资源,线程池设计模式通常是事先申请一定的资源,当需要使用时,去资源池中申请资源,用完后再放回资源池中。EventLoopThreadPool 正是借鉴了这种设计模式,虽然使用的线程并不会放回资源池中,但是本身的一个EventLoop即一个Reactor,本身就具备等待机制了。
TcpServer每次新建一条TcpConnection就会通过EventLoopThreadPool::getNextLoop()方法来取一个EventLoop, 目前的getNextLoop()只是循环的从池中取一条loop,如果提供给每条TcpConncetion的是均等的服务,那么这样就能很均匀的分配系统的资源了。
TcpServer的工作方式取决于EventLoopThreadPool中线程的创建数量。
0 意味着所有的I/O 都在TcpServer的主事件循环中,不会创建新的线程。
1 意味着所有的 I/O 在另一个线程中 ,TcpServer的主线程只负责建立连接。
N 意味着新的连接会被循环的分配到N条线程中工作。
下面是源码(带有注释)
TcpServer.h
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) // // This is a public header file, it must only include public header files. /* 这个类相当于把TcpConnection以及Accept类整合起来,完全能够实现Tcp通信,也就是socket函数都实现了 * 总结一下整个TCP通信过程: * 一个TcpServer类中,有Acceptor,EventLoopThreadPool各一个,以及多个TcpConnection类的指针, * 在TcpServer类的启动函数中,先开启EventLoopThreadPool线程池,然后将Acceptor监听函数放入eventloop中去执行 * 在TcpServer类的构造函数中,就已经把一个成功连接的回调函数绑定在Acceptor类的连接回调函数中,如果Acceptor监听 * 到有连接进来,先调监听socket描述符的回调函数,把这个连接accept进来,然后再调用newConnectionCallback_函数 * 来处理连接,每个连接都有一个对应的TcpConnection类来作为缓冲区 * */ #ifndef MUDUO_NET_TCPSERVER_H #define MUDUO_NET_TCPSERVER_H #include <muduo/base/Types.h> #include <muduo/net/TcpConnection.h> #include <map> #include <boost/noncopyable.hpp> #include <boost/scoped_ptr.hpp> namespace muduo { namespace net { class Acceptor; class EventLoop; class EventLoopThreadPool; /// /// TCP server, supports single-threaded and thread-pool models. /// /// This is an interface class, so don't expose too much details. class TcpServer : boost::noncopyable { public: typedef boost::function<void(EventLoop *)> ThreadInitCallback; //TcpServer(EventLoop* loop, const InetAddress& listenAddr); TcpServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg); ~TcpServer(); // force out-line dtor, for scoped_ptr members. const string &hostport() const { return hostport_; } const string &name() const { return name_; } /// Set the number of threads for handling input. /// /// Always accepts new connection in loop's thread. /// Must be called before @c start /// @param numThreads /// - 0 means all I/O in loop's thread, no thread will created. /// this is the default value. /// - 1 means all I/O in another thread. /// - N means a thread pool with N threads, new connections /// are assigned on a round-robin basis. void setThreadNum(int numThreads); void setThreadInitCallback( const ThreadInitCallback &cb) { threadInitCallback_ = cb; }//这个函数会作为EventLoopThreadPool::start的入口参数 /// Starts the server if it's not listenning. /// /// It's harmless to call it multiple times. /// Thread safe. void start(); /// Set connection callback. /// Not thread safe. // 设置连接到来或者连接关闭回调函数,这个函数指针会赋值给TcpConnection::connectionCallback_函数,就是在连接建立之后,和连接断开之前会调用 void setConnectionCallback(const ConnectionCallback &cb) { connectionCallback_ = cb; } /// Set message callback. /// Not thread safe. // 设置消息到来回调函数,这个函数指针在TcpConnection::handleread函数中调用,也就是TcpConnection的Channel的读函数的一部分 void setMessageCallback(const MessageCallback &cb) { messageCallback_ = cb; } /// Set write complete callback. /// Not thread safe. /// 在发送完消息以后调用,这个函数指针会赋值给TcpConnection::writeCompleteCallback_函数 void setWriteCompleteCallback(const WriteCompleteCallback &cb) { writeCompleteCallback_ = cb; } private: /// Not thread safe, but in loop void newConnection(int sockfd, const InetAddress &peerAddr); //这个函数会赋值给Acceptor::newConnectionCallback_,在新连接建立以后调用 /// Thread safe. /// 会赋值给TcpConnection::closeCallback_函数,也就是当连接描述符关闭以后调用这个 void removeConnection(const TcpConnectionPtr &conn); /// Not thread safe, but in loop,在上面这个函数removeConnection中调用 void removeConnectionInLoop(const TcpConnectionPtr &conn); typedef std::map <string, TcpConnectionPtr> ConnectionMap; EventLoop *loop_; // the acceptor loop const string hostport_; // 服务的ip:端口 const string name_; // 服务名 boost::scoped_ptr <Acceptor> acceptor_; // avoid revealing Acceptor boost::scoped_ptr <EventLoopThreadPool> threadPool_; ConnectionCallback connectionCallback_; MessageCallback messageCallback_; WriteCompleteCallback writeCompleteCallback_; // 数据发送完毕,会回调此函数 ThreadInitCallback threadInitCallback_; // IO线程池中的线程在进入事件循环前,会回调用此函数 bool started_; // always in loop thread int nextConnId_; // 下一个连接ID,每次增加一个就加1 ConnectionMap connections_; // 连接列表 }; } } #endif // MUDUO_NET_TCPSERVER_H
TcpServer.cc
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) #include <muduo/net/TcpServer.h> #include <muduo/base/Logging.h> #include <muduo/net/Acceptor.h> #include <muduo/net/EventLoop.h> #include <muduo/net/EventLoopThreadPool.h> #include <muduo/net/SocketsOps.h> #include <boost/bind.hpp> #include <stdio.h> // snprintf using namespace muduo; using namespace muduo::net; TcpServer::TcpServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg) : loop_(CHECK_NOTNULL(loop)), hostport_(listenAddr.toIpPort()), name_(nameArg), acceptor_(new Acceptor(loop, listenAddr)), threadPool_(new EventLoopThreadPool(loop)), connectionCallback_(defaultConnectionCallback), messageCallback_(defaultMessageCallback), started_(false), nextConnId_(1) { // Acceptor::handleRead函数中会回调用TcpServer::newConnection // _1对应的是socket文件描述符,_2对应的是对等方的地址(InetAddress) acceptor_->setNewConnectionCallback( boost::bind(&TcpServer::newConnection, this, _1, _2)); } TcpServer::~TcpServer() { loop_->assertInLoopThread(); LOG_TRACE << "TcpServer::~TcpServer [" << name_ << "] destructing"; for (ConnectionMap::iterator it(connections_.begin()); it != connections_.end(); ++it) { TcpConnectionPtr conn = it->second; it->second.reset(); // 释放当前所控制的对象,引用计数减一 conn->getLoop()->runInLoop( boost::bind(&TcpConnection::connectDestroyed, conn)); conn.reset(); // 释放当前所控制的对象,引用计数减一 } } void TcpServer::setThreadNum(int numThreads) { assert(0 <= numThreads); threadPool_->setThreadNum(numThreads); } // 该函数多次调用是无害的 // 该函数可以跨线程调用 void TcpServer::start() { if (!started_) { started_ = true; threadPool_->start(threadInitCallback_); } if (!acceptor_->listenning()) { // get_pointer返回原生指针 loop_->runInLoop( boost::bind(&Acceptor::listen, get_pointer(acceptor_))); } } void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)//建立新连接以后的回调函数 { loop_->assertInLoopThread(); // 按照轮叫的方式选择一个EventLoop EventLoop *ioLoop = threadPool_->getNextLoop(); char buf[32]; snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_);//buf的内容是 ip:端口#nextConnId_ ++nextConnId_; string connName = name_ + buf; LOG_INFO << "TcpServer::newConnection [" << name_ << "] - new connection [" << connName << "] from " << peerAddr.toIpPort(); InetAddress localAddr(sockets::getLocalAddr(sockfd)); // FIXME poll with zero timeout to double confirm the new connection // FIXME use make_shared if necessary /*TcpConnectionPtr conn(new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));*/ TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr)); LOG_TRACE << "[1] usecount=" << conn.use_count(); connections_[connName] = conn;//将连接名和TCPConnection的指针拷贝进连接列表中,这样就有两个shared_ptr指针指向conn了, //如果没有这一句程序,这个conn在newConnection函数执行结束以后就会析构掉,所以真正要删除时,也要把这个列表中的对应元素也删除了。 LOG_TRACE << "[2] usecount=" << conn.use_count(); //设置回调函数 conn->setConnectionCallback(connectionCallback_); conn->setMessageCallback(messageCallback_); conn->setWriteCompleteCallback(writeCompleteCallback_);//无论是否非空,都可以先设置,在使用之前会有判断 conn->setCloseCallback( boost::bind(&TcpServer::removeConnection, this, _1)); // conn->connectEstablished(); ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn)); //个人理解bind在绑定类成员函数时,后面跟的参数一定比输入参数多一个,就是一个类指针,表明这个函数属于那个类变量的, //一般都使用this,而这里是用的TcpConnectionPtr LOG_TRACE << "[5] usecount=" << conn.use_count(); } void TcpServer::removeConnection(const TcpConnectionPtr &conn) { /* loop_->assertInLoopThread(); LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_ << "] - connection " << conn->name(); LOG_TRACE << "[8] usecount=" << conn.use_count(); size_t n = connections_.erase(conn->name()); LOG_TRACE << "[9] usecount=" << conn.use_count(); (void)n; assert(n == 1); loop_->queueInLoop( boost::bind(&TcpConnection::connectDestroyed, conn)); LOG_TRACE << "[10] usecount=" << conn.use_count(); */ loop_->runInLoop(boost::bind(&TcpServer::removeConnectionInLoop, this, conn)); } void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)//就是把TcpConnection从Eventloop中移除 { loop_->assertInLoopThread(); LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_ << "] - connection " << conn->name(); LOG_TRACE << "[8] usecount=" << conn.use_count(); size_t n = connections_.erase(conn->name()); LOG_TRACE << "[9] usecount=" << conn.use_count(); (void) n; assert(n == 1); EventLoop *ioLoop = conn->getLoop(); ioLoop->queueInLoop( boost::bind(&TcpConnection::connectDestroyed, conn)); //loop_->queueInLoop( // boost::bind(&TcpConnection::connectDestroyed, conn)); LOG_TRACE << "[10] usecount=" << conn.use_count(); }
TcpConnection
TcpConnection类主要负责封装一次TCP连接,向Channel类注册回调函数(可读、可写、可关闭、错误处理),将来当Channel类上的事件发生时,调用相应的回调函数进行数据收发或者错误处理。
TcpConnection是使用shared_ptr来管理的类,因为它的生命周期模糊。TcpConnection表示已经建立或正在建立的连接,建立连接后,用户只需要在上层类如TcpServer中设置连接到来和消息到来的处理函数,继而回调TcpConnection中的 setConnectionCallback和setMessageCallback函数,实现对事件的处理。用户需要关心的事件是有限的,其他都由网络库负责。
TcpConnection中封装了InputBuffer和OutputBuffer,用来表示应用层的缓冲区。在发送数据时,如果不能一次将Buffer中的数据发送完毕,它还会继续关注Channel中的可写事件,当sockfd可写时,会再次发送。
前面提到TcpConnection的生存期模糊,主要是因为我们不能在TcpServer中直接erase掉TcpConnection对象,因为此时有可能Channel中的handleEvent还在执行,如果析构TcpConnection对象,那么他的成员channel_也会被析构,会导致core dump。也就是说我们需要TcpConnection 对象生存期要长于handleEvent() 函数,直到执行完connectDestroyed() 后才会析构。
断开连接:
TcpConnection的断开是采用被动方式,即对方先关闭连接,本地read(2)返回0后,调用顺序如下:
handleClose()->TcpServer::removeConnection->TcpConnection::connectDestroyed()。
连接关闭时序图:
当连接到来,创建一个TcpConnection对象,立刻用shared_ptr来管理,引用计数为1,在Channel中维护一个weak_ptr(tie_),将这个shared_ptr对象赋值给_tie,引用计数仍然为1。当连接关闭时,在handleEvent中,将tie_提升,得到一个shard_ptr对象,引用计数就变成了2。当shared_ptr的计数不为0时,TcpConnection不会被销毁。
TcpConnection.h源码分析
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) // // This is a public header file, it must only include public header files. /*我所理解的这个类,主要用来和buffer类一起作为非阻塞IO的一个读取桥梁,其中主要封装的函数是从文件描述符中读取传输的数据到 *接受缓冲区中,或者把规定数据,或者触发写事件的输出缓冲区的数据写入对应的文件描述符中。 */ #ifndef MUDUO_NET_TCPCONNECTION_H #define MUDUO_NET_TCPCONNECTION_H #include <muduo/base/Mutex.h> #include <muduo/base/StringPiece.h> #include <muduo/base/Types.h> #include <muduo/net/Callbacks.h> #include <muduo/net/Buffer.h> #include <muduo/net/InetAddress.h> #include <boost/any.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/noncopyable.hpp> #include <boost/scoped_ptr.hpp> #include <boost/shared_ptr.hpp> namespace muduo { namespace net { class Channel; class EventLoop; class Socket; /// /// TCP connection, for both client and server usage. /// /// This is an interface class, so don't expose too much details. class TcpConnection : boost::noncopyable,public boost::enable_shared_from_this<TcpConnection> { public: /// Constructs a TcpConnection with a connected sockfd /// /// User should not create this object. TcpConnection(EventLoop *loop,const string &name,int sockfd, const InetAddress &localAddr, const InetAddress &peerAddr); ~TcpConnection(); EventLoop *getLoop() const { return loop_; }//获取当前TcpConnection所在的Eventloop const string &name() const { return name_; }// const InetAddress &localAddress() { return localAddr_; } const InetAddress &peerAddress() { return peerAddr_; } bool connected() const { return state_ == kConnected; } // void send(string&& message); // C++11 void send(const void *message, size_t len); void send(const StringPiece &message); // void send(Buffer&& message); // C++11 void send(Buffer *message); // this one will swap data void shutdown(); // NOT thread safe, no simultaneous calling void setTcpNoDelay(bool on); void setContext(const boost::any &context) { context_ = context; } const boost::any &getContext() const//得到常数值的context_ { return context_; } boost::any *getMutableContext()//得到可以改变的context_ { return &context_; } void setConnectionCallback(const ConnectionCallback &cb) { connectionCallback_ = cb; } //在handleClose,connectEstablished,connectDestroyed中调用,个人理解这个连接回调函数主要起到 //显示作用,就是在和连接描述符建立连接或者关闭连接前,显示连接状态的,表明还在连接中 void setMessageCallback(const MessageCallback &cb) { messageCallback_ = cb; } //在handleRead函数当中调用了,也可以理解为channel_写函数的一部分 void setWriteCompleteCallback(const WriteCompleteCallback &cb) { writeCompleteCallback_ = cb; } //在handleWrite和sendInLoop写函数中,写完调用的 void setHighWaterMarkCallback(const HighWaterMarkCallback &cb, size_t highWaterMark) { highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }//都在sendInLoop中调用了 Buffer *inputBuffer() { return &inputBuffer_; } /// Internal use only. void setCloseCallback(const CloseCallback &cb) { closeCallback_ = cb; }//在handleClose函数中调用 // called when TcpServer accepts a new connection void connectEstablished(); // should be called only once // called when TcpServer has removed me from its map void connectDestroyed(); // should be called only once private: enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting }; void handleRead(Timestamp receiveTime);//绑定channel_的读函数 void handleWrite();//绑定channel_的写函数 void handleClose();//绑定channel_的关闭函数,同时也在handleRead中调用 void handleError();////绑定channel_的错误函数 void sendInLoop(const StringPiece &message); void sendInLoop(const void *message, size_t len); void shutdownInLoop(); void setState(StateE s) { state_ = s; }//设置状态位 EventLoop *loop_; // 所属EventLoop string name_; // 连接名 StateE state_; // FIXME: use atomic variable // we don't expose those classes to client. //连接状态 boost::scoped_ptr <Socket> socket_; boost::scoped_ptr <Channel> channel_; //channel_在TCPServer中绑定了连接套接字,就是能够实现通信的那个connfd套接字,这个套接字是从Socket::accept函数得到的 //在Tcpclient绑定的是创建的套接字,因为客户端只需要一个套接字就可以了,这个套接字是从socket()函数中得到的 InetAddress localAddr_;//当前服务端的地址 InetAddress peerAddr_;//当前建立连接的客户端地址 ConnectionCallback connectionCallback_; MessageCallback messageCallback_; WriteCompleteCallback writeCompleteCallback_; // 数据发送完毕回调函数,即所有的用户数据都已拷贝到内核缓冲区时回调该函数 // outputBuffer_被清空也会回调该函数,可以理解为低水位标回调函数 HighWaterMarkCallback highWaterMarkCallback_; // 高水位标回调函数 CloseCallback closeCallback_; size_t highWaterMark_; // 高水位标 Buffer inputBuffer_; // 应用层接收缓冲区 Buffer outputBuffer_; // 应用层发送缓冲区 boost::any context_; // 绑定一个未知类型的上下文对象,一般用来放HttpContext类的 }; typedef boost::shared_ptr <TcpConnection> TcpConnectionPtr; } } #endif // MUDUO_NET_TCPCONNECTION_H
TcpConnection.cc源码分析
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) #include <muduo/net/TcpConnection.h> #include <muduo/base/Logging.h> #include <muduo/net/Channel.h> #include <muduo/net/EventLoop.h> #include <muduo/net/Socket.h> #include <muduo/net/SocketsOps.h> #include <boost/bind.hpp> #include <errno.h> #include <stdio.h> using namespace muduo; using namespace muduo::net; void muduo::net::defaultConnectionCallback(const TcpConnectionPtr &conn)//默认的连接回调函数,输出连接状态 { LOG_TRACE << conn->localAddress().toIpPort() << " -> " << conn->peerAddress().toIpPort() << " is " << (conn->connected() ? "UP" : "DOWN"); } void muduo::net::defaultMessageCallback(const TcpConnectionPtr &, Buffer *buf, Timestamp) //默认的有消息时执行的回调函数,把缓冲区读指针和写指针回到初始化的位置 //可以理解为将缓冲区清零 { buf->retrieveAll(); } TcpConnection::TcpConnection(EventLoop *loop, const string &nameArg, int sockfd, const InetAddress &localAddr, const InetAddress &peerAddr) : loop_(CHECK_NOTNULL(loop)), // 所属EventLoop name_(nameArg),// 连接名 state_(kConnecting),//连接状态 socket_(new Socket(sockfd)),//连接套接字 channel_(new Channel(loop, sockfd)), //channel_在TCPServer中绑定了连接套接字,就是能够实现通信的那个connfd套接字,这个套接字是从Socket::accept函数得到的 //在Tcpclient绑定的是创建的套接字,因为客户端只需要一个套接字就可以了,这个套接字是从socket()函数中得到的 localAddr_(localAddr),//当前服务端的地址 peerAddr_(peerAddr),//当前建立连接的客户端地址 highWaterMark_(64 * 1024 * 1024) { // 通道可读事件到来的时候,回调TcpConnection::handleRead,_1是事件发生时间 channel_->setReadCallback(boost::bind(&TcpConnection::handleRead, this, _1)); // 通道可写事件到来的时候,回调TcpConnection::handleWrite channel_->setWriteCallback(boost::bind(&TcpConnection::handleWrite, this)); // 连接关闭,回调TcpConnection::handleClose channel_->setCloseCallback(boost::bind(&TcpConnection::handleClose, this)); // 发生错误,回调TcpConnection::handleError channel_->setErrorCallback(boost::bind(&TcpConnection::handleError, this)); LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this<< " fd=" << sockfd; socket_->setKeepAlive(true);//定期探测连接是否存在,类似于心跳包 } TcpConnection::~TcpConnection() { LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this << " fd=" << channel_->fd(); } // 线程安全,可以跨线程调用 void TcpConnection::send(const void *data, size_t len) { if (state_ == kConnected) { if (loop_->isInLoopThread()) { sendInLoop(data, len); } else { string message(static_cast<const char *>(data), len); loop_->runInLoop(boost::bind(&TcpConnection::sendInLoop,this,message)); } } } // 线程安全,可以跨线程调用 void TcpConnection::send(const StringPiece &message) { if (state_ == kConnected) { if (loop_->isInLoopThread()) { sendInLoop(message); } else { loop_->runInLoop(boost::bind(&TcpConnection::sendInLoop,this,message.as_string())); //std::forward<string>(message))); } } } // 线程安全,可以跨线程调用 void TcpConnection::send(Buffer *buf) { if (state_ == kConnected) { if (loop_->isInLoopThread()) { sendInLoop(buf->peek(), buf->readableBytes()); buf->retrieveAll(); } else { loop_->runInLoop(boost::bind(&TcpConnection::sendInLoop,this,buf->retrieveAllAsString())); //std::forward<string>(message))); } } } void TcpConnection::sendInLoop(const StringPiece &message) { sendInLoop(message.data(), message.size()); } //???这个函数和handlewrite函数都是向文件描述符中写入,有什么区别呢? void TcpConnection::sendInLoop(const void *data, size_t len) { /* loop_->assertInLoopThread(); sockets::write(channel_->fd(), data, len); */ loop_->assertInLoopThread(); ssize_t nwrote = 0; size_t remaining = len; bool error = false; if (state_ == kDisconnected) { LOG_WARN << "disconnected, give up writing"; return; } // if no thing in output queue, try writing directly // 通道没有关注可写事件并且发送缓冲区没有数据,直接write if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) { nwrote = sockets::write(channel_->fd(), data, len); if (nwrote >= 0) { remaining = len - nwrote; // 写完了,回调writeCompleteCallback_ if (remaining == 0 && writeCompleteCallback_) { loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this())); } } else // nwrote < 0 { nwrote = 0; if (errno != EWOULDBLOCK) { LOG_SYSERR << "TcpConnection::sendInLoop"; if (errno == EPIPE) // FIXME: any others? { error = true; } } } } assert(remaining <= len); // 没有错误,并且还有未写完的数据(说明内核发送缓冲区满,要将未写完的数据添加到output buffer中) if (!error && remaining > 0) { LOG_TRACE << "I am going to write more data"; size_t oldLen = outputBuffer_.readableBytes(); // 如果超过highWaterMark_(高水位标),回调highWaterMarkCallback_ if (oldLen + remaining >= highWaterMark_ && oldLen < highWaterMark_ && highWaterMarkCallback_) { loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining)); } outputBuffer_.append(static_cast<const char *>(data) + nwrote, remaining);//将剩余数据存入应用层发送缓冲区 if (!channel_->isWriting()) { channel_->enableWriting(); // 关注POLLOUT事件 } } } void TcpConnection::shutdown()//关闭连接 { // FIXME: use compare and swap if (state_ == kConnected) { setState(kDisconnecting); // FIXME: shared_from_this()? loop_->runInLoop(boost::bind(&TcpConnection::shutdownInLoop, this)); } } void TcpConnection::shutdownInLoop()//在loop中关闭写半边,还是可以读数据 { loop_->assertInLoopThread(); if (!channel_->isWriting()) { // we are not writing socket_->shutdownWrite(); } } void TcpConnection::setTcpNoDelay(bool on)//设置TCP延迟连接 { socket_->setTcpNoDelay(on); } void TcpConnection::connectEstablished()//这个建立连接是TcpConnection类中的channel加入到对应的比如Tcpclient或者Tcpserver类所属的eventloop中 { loop_->assertInLoopThread(); assert(state_ == kConnecting);//设置正在连接状态 setState(kConnected); LOG_TRACE << "[3] usecount=" << shared_from_this().use_count(); channel_->tie(shared_from_this()); channel_->enableReading(); // TcpConnection所对应的通道加入到Poller关注 connectionCallback_(shared_from_this()); LOG_TRACE << "[4] usecount=" << shared_from_this().use_count(); } void TcpConnection::connectDestroyed()//取消连接,从对应的Eventloop上的epoll队列中去除 { loop_->assertInLoopThread(); if (state_ == kConnected) { setState(kDisconnected); channel_->disableAll(); connectionCallback_(shared_from_this()); } channel_->remove();//将channel从epoll队列中移除 } void TcpConnection::handleRead(Timestamp receiveTime)//处理读事件的函数 { /* loop_->assertInLoopThread(); int savedErrno = 0; ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); if (n > 0) { messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); } else if (n == 0) { handleClose(); } else { errno = savedErrno; LOG_SYSERR << "TcpConnection::handleRead"; handleError(); } */ /* loop_->assertInLoopThread(); int savedErrno = 0; char buf[65536]; ssize_t n = ::read(channel_->fd(), buf, sizeof buf); if (n > 0) { messageCallback_(shared_from_this(), buf, n); } else if (n == 0) { handleClose(); } else { errno = savedErrno; LOG_SYSERR << "TcpConnection::handleRead"; handleError(); } */ loop_->assertInLoopThread(); int savedErrno = 0; ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);//直接将数据读到inputBuffer_缓冲区 if (n > 0) { messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); } else if (n == 0) { handleClose();//如果读到的数据为0,就自动退出 } else { errno = savedErrno; LOG_SYSERR << "TcpConnection::handleRead"; handleError(); } } // 监听到写事件了,就调用这个函数,此时服务器已经把要写的内容写到outputBuffer_中去了,所以要写的内容从读指针处开始 void TcpConnection::handleWrite() { loop_->assertInLoopThread(); if (channel_->isWriting())//查看是否有写事件需要关注 { ssize_t n = sockets::write(channel_->fd(), outputBuffer_.peek(), outputBuffer_.readableBytes());//写到文件描述符中去 if (n > 0) { outputBuffer_.retrieve(n);//处理读写指针 if (outputBuffer_.readableBytes() == 0) // 发送缓冲区已清空 { channel_->disableWriting(); // 停止关注POLLOUT事件,以免出现busy loop if (writeCompleteCallback_) // 回调writeCompleteCallback_ { // 应用层发送缓冲区被清空,就回调用writeCompleteCallback_ // 发送给IO线程进行处理 loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this())); } if (state_ == kDisconnecting) // 发送缓冲区已清空并且连接状态是kDisconnecting, 要关闭连接 { shutdownInLoop(); // 关闭写连接 } } else { LOG_TRACE << "I am going to write more data"; } } else { LOG_SYSERR << "TcpConnection::handleWrite"; // if (state_ == kDisconnecting) // { // shutdownInLoop(); // } } } else { LOG_TRACE << "Connection fd = " << channel_->fd() << " is down, no more writing"; } } void TcpConnection::handleClose()//关闭事件处理,也是epoll如果发生关闭事件的回调函数 { loop_->assertInLoopThread(); LOG_TRACE << "fd = " << channel_->fd() << " state = " << state_; assert(state_ == kConnected || state_ == kDisconnecting); // we don't close fd, leave it to dtor, so we can find leaks easily. setState(kDisconnected); channel_->disableAll(); TcpConnectionPtr guardThis(shared_from_this()); connectionCallback_(guardThis); // 在结束前,最后一次处理一下,这一行,可以不调用 LOG_TRACE << "[7] usecount=" << guardThis.use_count(); // must be the last line closeCallback_(guardThis); // 调用TcpServer::removeConnection LOG_TRACE << "[11] usecount=" << guardThis.use_count(); } void TcpConnection::handleError()//处理错误的函数,也是epoll如果发生错误事件的回调函数 { int err = sockets::getSocketError(channel_->fd()); LOG_ERROR << "TcpConnection::handleError [" << name_ << "] - SO_ERROR = " << err << " " << strerror_tl(err); }