muduo网络库浅谈(二)

第二章 muduo网络库之网络通信

muduo网络库的重要组件之一便是非阻塞的TCP网络库,该节将介绍muduo中TCP网络库的设计,该网络库主要由几个类主要的类组成:class Acceptor,class TcpServer,class TcpConnection,class Buffer。

底层库的实现

在介绍class Acceptor之前,需要先简单介绍一下几个底层的库,包括:class Socket,class InetAddress,namespace sockets中定义函数,这几个class来管理socket。

#先来看namespace sockets的源码,namespace sockets中定义的函数是class Socket,class InetAddress功能实现的前提:

namespace sockets

#ifndef MUDUO_NET_SOCKETSOPS_H
#define MUDUO_NET_SOCKETSOPS_H

#include <arpa/inet.h>
#include <endian.h>
// struct sockaddr_in {
//         sa_family_t    sin_family; /* address family: AF_INET */协议族
//         uint16_t       sin_port;   /* port in network byte order */端口号
//         struct in_addr sin_addr;   /* internet address */地址
//     };


//struct sockaddr {  
  //   sa_family_t sin_family;//地址族
 //   char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   };
   
//struct sockaddr与struct sockaddr_in 两者可以相互转化

namespace muduo
{
namespace sockets
{

inline uint64_t hostToNetwork64(uint64_t host64)//主机字节序转网络字节序
{
  return htobe64(host64);
}

inline uint32_t hostToNetwork32(uint32_t host32)//同上
{
  return htonl(host32);
}

inline uint16_t hostToNetwork16(uint16_t host16)//同上
{
  return htons(host16);
}

inline uint64_t networkToHost64(uint64_t net64)//网络字节序转主机
{
  return be64toh(net64);
}

inline uint32_t networkToHost32(uint32_t net32)//同上
{
  return ntohl(net32);
}

inline uint16_t networkToHost16(uint16_t net16)//同上
{
  return ntohs(net16);
}
int createNonblockingOrDie();//创建无阻塞的sockfd即套接字文件描述符

void bindOrDie(int sockfd, const struct sockaddr_in& addr);//绑定地址和端口
void listenOrDie(int sockfd);//监听sockfd
int  accept(int sockfd, struct sockaddr_in* addr);//接受连接,并返回目标的地址端口至addr中
void close(int sockfd);//关闭sockfd

void toHostPort(char* buf, size_t size,
                const struct sockaddr_in& addr);//addr中地址等信息输出到buf[]中
void fromHostPort(const char* ip, uint16_t port,
                  struct sockaddr_in* addr);//将ip,port等输出到addr中
}
}

#endif 
namespace
{

typedef struct sockaddr SA;

const SA* sockaddr_cast(const struct sockaddr_in* addr)//将sockaddr_in*类型转换为sockaddr*类型
{
  return static_cast<const SA*>(implicit_cast<const void*>(addr));
}

SA* sockaddr_cast(struct sockaddr_in* addr)//同上
{
  return static_cast<SA*>(implicit_cast<void*>(addr));
}

void setNonBlockAndCloseOnExec(int sockfd)//设置sockfd为无阻塞NONBLOCK且CLOEXEC
{
  // non-block
  int flags = ::fcntl(sockfd, F_GETFL, 0);
  flags |= O_NONBLOCK;
  int ret = ::fcntl(sockfd, F_SETFL, flags);
  // FIXME check

  // close-on-exec
  flags = ::fcntl(sockfd, F_GETFD, 0);
  flags |= FD_CLOEXEC;
  ret = ::fcntl(sockfd, F_SETFD, flags);
  // FIXME check
}

}

int sockets::createNonblockingOrDie()//创建一个sockfd,
{
  // socket
#if VALGRIND
  int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sockfd < 0)
  {
    LOG_SYSFATAL << "sockets::createNonblockingOrDie";
  }

  setNonBlockAndCloseOnExec(sockfd);
#else
  int sockfd = ::socket(AF_INET,
                        SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
                        IPPROTO_TCP);
  if (sockfd < 0)
  {
    LOG_SYSFATAL << "sockets::createNonblockingOrDie";
  }
#endif
  return sockfd;
}

void sockets::bindOrDie(int sockfd, const struct sockaddr_in& addr)//绑定地址和端口
{
  int ret = ::bind(sockfd, sockaddr_cast(&addr), sizeof addr);
  if (ret < 0)
  {
    LOG_SYSFATAL << "sockets::bindOrDie";
  }
}

void sockets::listenOrDie(int sockfd)//监听
{
  int ret = ::listen(sockfd, SOMAXCONN);
  if (ret < 0)
  {
    LOG_SYSFATAL << "sockets::listenOrDie";
  }
}

int sockets::accept(int sockfd, struct sockaddr_in* addr)//接受连接,并返回目标的地址端口至addr中
{
  socklen_t addrlen = sizeof *addr;
#if VALGRIND
  int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen);
  setNonBlockAndCloseOnExec(connfd);
#else
  int connfd = ::accept4(sockfd, sockaddr_cast(addr),
                         &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#endif
  if (connfd < 0)
  {
    int savedErrno = errno;
    LOG_SYSERR << "Socket::accept";
    switch (savedErrno)
    {
      case EAGAIN:
      case ECONNABORTED:
      case EINTR:
      case EPROTO: // ???
      case EPERM:
      case EMFILE: // per-process lmit of open file desctiptor ???
        // expected errors
        errno = savedErrno;
        break;
      case EBADF:
      case EFAULT:
      case EINVAL:
      case ENFILE:
      case ENOBUFS:
      case ENOMEM:
      case ENOTSOCK:
      case EOPNOTSUPP:
        // unexpected errors
        LOG_FATAL << "unexpected error of ::accept " << savedErrno;
        break;
      default:
        LOG_FATAL << "unknown error of ::accept " << savedErrno;
        break;
    }
  }
  return connfd;
}

void sockets::close(int sockfd)//关闭
{
  if (::close(sockfd) < 0)
  {
    LOG_SYSERR << "sockets::close";
  }
}

void sockets::toHostPort(char* buf, size_t size,
                         const struct sockaddr_in& addr)
{
  char host[INET_ADDRSTRLEN] = "INVALID";
  ::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof host);
  uint16_t port = sockets::networkToHost16(addr.sin_port);
  snprintf(buf, size, "%s:%u", host, port);
}

void sockets::fromHostPort(const char* ip, uint16_t port,
                           struct sockaddr_in* addr)
{
  addr->sin_family = AF_INET;
  addr->sin_port = hostToNetwork16(port);
  if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
  {
    LOG_SYSERR << "sockets::fromHostPort";
  }
}

namespace sockets提供了一系列函数来提供socket的设置监听功能,以及地址转换函数,提供给class InetAddress极大地便利,class InetAddress提供了地址端口等信息的保存,只有几个简单的成员函数和一个类成员struct sockaddr_in addr_,源码如下:

class InetAddress

#ifndef MUDUO_NET_INETADDRESS_H
#define MUDUO_NET_INETADDRESS_H

#include "datetime/copyable.h"

#include <string>

#include <netinet/in.h>

namespace muduo
{
class InetAddress : public muduo::copyable
{
 public:
  explicit InetAddress(uint16_t port);
  InetAddress(const std::string& ip, uint16_t port);
  InetAddress(const struct sockaddr_in& addr)
    : addr_(addr)
  { }
  std::string toHostPort() const;将sockaddr_in转换为string返回
  const struct sockaddr_in& getSockAddrInet() const { return addr_; }//返回sockaddr_in
  void setSockAddrInet(const struct sockaddr_in& addr) { addr_ = addr; }//设置sockaddr_in

 private:
  struct sockaddr_in addr_;//保存由地址和端口信息
};

}

#endif

成员函数的具体功能实现实际上都是由namespace sockets提供的,就不一一注释了,class InetAddress的成员函数实现如下:

InetAddress::InetAddress(uint16_t port)
{
  bzero(&addr_, sizeof addr_);
  addr_.sin_family = AF_INET;
  addr_.sin_addr.s_addr = sockets::hostToNetwork32(kInaddrAny);
  addr_.sin_port = sockets::hostToNetwork16(port);
}

InetAddress::InetAddress(const std::string& ip, uint16_t port)
{
  bzero(&addr_, sizeof addr_);
  sockets::fromHostPort(ip.c_str(), port, &addr_);
}

std::string InetAddress::toHostPort() const
{
  char buf[32];
  sockets::toHostPort(buf, sizeof buf, addr_);
  return buf;
}


class Socket

class Socket提供了sockfd套接字文件描述符的的管理功能,即对sockfd提供绑定地址端口,listen监听,accept接受连接等功能,当然,底层的功能仍旧是namespace sockets中函数提供的。class Socket源码如下:

namespace muduo
{

class InetAddress;
class Socket : boost::noncopyable
{
 public:
  explicit Socket(int sockfd)//文件描述符赋值初始化
    : sockfd_(sockfd)
  { }
  ~Socket();
  int fd() const { return sockfd_; }//返回文件描述符
  void bindAddress(const InetAddress& localaddr);//绑定
  void listen();//监听
  int accept(InetAddress* peeraddr);//接受连接,并返回连接方的地址信息
  void setReuseAddr(bool on);//为文件描述符设置重用

 private:
  const int sockfd_;//文件描述符
};

}
#endif

成员函数实现如下,但不作注释,实现都是namespace sockets提供的:


Socket::~Socket()
{
  sockets::close(sockfd_);
}

void Socket::bindAddress(const InetAddress& addr)
{
  sockets::bindOrDie(sockfd_, addr.getSockAddrInet());
}

void Socket::listen()
{
  sockets::listenOrDie(sockfd_);
}

int Socket::accept(InetAddress* peeraddr)
{
  struct sockaddr_in addr;
  bzero(&addr, sizeof addr);
  int connfd = sockets::accept(sockfd_, &addr);
  if (connfd >= 0)
  {
    peeraddr->setSockAddrInet(addr);
  }
  return connfd;
}

void Socket::setReuseAddr(bool on)
{
  int optval = on ? 1 : 0;
  ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
               &optval, sizeof optval);
}


以上便是网络库关键底层的实现。

class Accetpor

class Accetpor顾名思义拥有socket中的accept的行为,不过稍显不同的是,这种accept行为是使用在poll()中,使得网络通信的行为统一经由EventLoop的loop的poll()函数中处理,先来看看源码:

namespace muduo
{

class EventLoop;
class InetAddress;
class Acceptor : boost::noncopyable
{
 public:
  typedef boost::function<void (int sockfd,
                                const InetAddress&)> NewConnectionCallback;

  Acceptor(EventLoop* loop, const InetAddress& listenAddr);//初始化,绑定端口

  void setNewConnectionCallback(const NewConnectionCallback& cb)//设置当有connect时,调用的函数
  { newConnectionCallback_ = cb; }

  bool listenning() const { return listenning_; }//是否处于监听端口中
  void listen();//监听端口

 private:
  void handleRead();//回调函数,当有连接时,sockfd可读,poll从阻塞返回,并调用handleRead()(即handleRead()会注册到Channel的回调函数中)

  EventLoop* loop_;
  Socket acceptSocket_;//
  Channel acceptChannel_;//
  NewConnectionCallback newConnectionCallback_;//
  bool listenning_;//
};

}

#endif

直接从头文件定义中可以看出,class Acceptor的功能只有提供回调函数和注册Channel而已,之后的行为就是Channel,Poller等类来处理,以及socket等类来初始化本机地址等参数。

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie()),//创建sockfd套接字文件描述符,
    acceptChannel_(loop, acceptSocket_.fd()),//初始化Channel,但并没有正式地将Channel注册到Poller中
    listenning_(false)
{
  acceptSocket_.setReuseAddr(true);//设置sockfd复用
  acceptSocket_.bindAddress(listenAddr);//绑定地址端口等
  acceptChannel_.setReadCallback(
      boost::bind(&Acceptor::handleRead, this));//设置Channel的回调函数
}

void Acceptor::listen()//开始监听,
{
  loop_->assertInLoopThread();
  listenning_ = true;
  acceptSocket_.listen();//监听
  acceptChannel_.enableReading();//将Channel注册到Poller,一旦sockfd由可读事件发生则返回并执行handleRead()
}

void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  InetAddress peerAddr(0);
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);//接受连接,创建并返回连接事件描述符(connfd),服务端与客户端的通信就是在connfd上进行的
  if (connfd >= 0) {
    if (newConnectionCallback_) {
      newConnectionCallback_(connfd, peerAddr);//调用用户提供的好的newConnectionCallback函数
    } else {
      sockets::close(connfd);//未设置newConnectionCallback函数则关闭connfd
    }
  }
}

class TcpServer

上一节中介绍了class Acceptor,但acceptor只是建立了连接,并没有将connfd连接文件描述符来实现进一步的通信,需要编写一个class TcpServer来管理Acceptor后的connfd,以及一系列用户设置的用于当连接时调用的函数ConnectionCallback以及当客户端发送信息后处理的函数MessageCallback。具体看源码:

namespace muduo
{

class Acceptor;
class EventLoop;

class TcpServer : boost::noncopyable
{
 public:

  TcpServer(EventLoop* loop, const InetAddress& listenAddr);
  ~TcpServer();  
  void start();//监听开始,并将sockfd加入到Poller中
  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }//设置连接时的回调函数
  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; }//设置处理信息的回调函数

 private:
  void newConnection(int sockfd, const InetAddress& peerAddr);//newConnection()将会被注册到Acceptor的回调函数,继而注册到Poller中,当sockfd可读(有连接)时会调用newConnection()
  typedef std::map<std::string, TcpConnectionPtr> ConnectionMap;//TcpConnectionPtr为accept后返回的connfd通信管理的类的指针
  EventLoop* loop_; 
  const std::string name_;
  boost::scoped_ptr<Acceptor> acceptor_; //只拥有1个Acceptor的实例
  ConnectionCallback connectionCallback_;//回调函数
  MessageCallback messageCallback_;//回调函数
  bool started_;
  int nextConnId_;  //ID编号,即TcpConnection的名称
  ConnectionMap connections_;//用来存储管理已经建立的连接TcpConnection
};

}

#endif

从以上可以看出TcpServer的作用,class Acceptor负责accept的行为,class TcpConnection负责建立连接后信息处理的行为,class TcpServer作用是管理一个Acceptor,当有客户端连接后就创建一个TcpConnection,并通过容器管理TcpConnection的共享指针,来看看其成员函数的实现:

using namespace muduo;

TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr)
  : loop_(CHECK_NOTNULL(loop)),//
    name_(listenAddr.toHostPort()),//本机地址端口信息
    acceptor_(new Acceptor(loop, listenAddr)),//
    started_(false),//开始标识符初始化
    nextConnId_(1)//TcpConnection的编号的启示
{
  acceptor_->setNewConnectionCallback(
      boost::bind(&TcpServer::newConnection, this, _1, _2));//向Acceptor的回调函数绑定该类的newConnection函数
}

TcpServer::~TcpServer()
{
}

void TcpServer::start()//开始监听端口,//监听开始,并将sockfd以及相应的回调函数newConnection加入到Poller中
{
  if (!started_)
  {
    started_ = true;
  }

  if (!acceptor_->listenning())
  {
    loop_->runInLoop(
        boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)//回调函数,当sockfd有连接时,调用该函数,
{
  loop_->assertInLoopThread();
  char buf[32];
  snprintf(buf, sizeof buf, "#%d", nextConnId_);
  ++nextConnId_;
  std::string connName = name_ + buf;//新TcpConnection的名称

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toHostPort();
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  TcpConnectionPtr conn(
      new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));//创建新的TcpConnection,其以共享指针的形式保存
  connections_[connName] = conn;
  conn->setConnectionCallback(connectionCallback_);//对TcpConnection设置connectionCallback_函数
  conn->setMessageCallback(messageCallback_);
  conn->connectEstablished();
}


class TcpServer的函数newConnection()会在sockfd有都市被调用,创建了class TcpConnection的实例,并设置了客户端和服务器端通信处理的一系列的回调函数。

class TcpConnection

初步实现

先来看看muduo一书中介绍的最开始简陋版本的class TcpConnection吧,但在说之前首先要明确他的作用,上节中class TcpServer管理class Acceptor和class TcpConnection,当Acceptor注册到Poller中且有来自客户端的connect,则会调用TcpServer中的newConnection函数,继而创建一个class TcpConnection的实例来管理此次用于通信的connfd文件描述符和双方的地址信息,同时向Poller注册该文件描述符以及相应的处理信息的回调函数。也就是说TcpConnection需要包含一个Channel保存文件描述符和注册,以及相应的调用函数(但这也只是初步的功能)。

class TcpConnection的源码如下:

#ifndef MUDUO_NET_TCPCONNECTION_H
#define MUDUO_NET_TCPCONNECTION_H

#include "Callbacks.h"
#include "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
{

class Channel;
class EventLoop;
class Socket;
class TcpConnection : boost::noncopyable,
                      public boost::enable_shared_from_this<TcpConnection>
{
 public:
  TcpConnection(EventLoop* loop,
                const std::string& name,
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr);//初始化
  ~TcpConnection();
  EventLoop* getLoop() const { return loop_; }
  const std::string& name() const { return name_; }
  const InetAddress& localAddress() { return localAddr_; }
  const InetAddress& peerAddress() { return peerAddr_; }
  bool connected() const { return state_ == kConnected; }//返回当前连接状态
  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }//设置由新连接时调用的回调函数
  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; }//设置当connfd有信息被写入时调用的回调函数
  void connectEstablished(); //负责state_的改写,以及向Poller注册connfd。
 private:
  enum StateE { kConnecting, kConnected, };//枚举型 ,当前的连接状态

  void setState(StateE s) { state_ = s; }
  void handleRead();//该函数是被正式注册到Channel中的回调函数,其中包含了messageCallback_函数,即该函数当connfd有信息被写入时调用,继而调用messageCallback_

  EventLoop* loop_;
  std::string name_;
  StateE state_;  
  boost::scoped_ptr<Socket> socket_;//scoped_ptr自动管理socket_和Channel的生命周期
  boost::scoped_ptr<Channel> channel_;//
  InetAddress localAddr_;//本机地址
  InetAddress peerAddr_;//连接方地址
  ConnectionCallback connectionCallback_;//连接时调用的回调函数,只调用一次
  MessageCallback messageCallback_;//每次connfd可读时调用的回调函数,调用多次
};

typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;//TcpConnection使用共享指针来管理,保证不需要时自动析构

}

#endif 

成员函数实现如下:

#include "TcpConnection.h"

#include "logging/Logging.h"
#include "Channel.h"
#include "EventLoop.h"
#include "Socket.h"

#include <boost/bind.hpp>

#include <errno.h>
#include <stdio.h>

using namespace muduo;

TcpConnection::TcpConnection(EventLoop* loop,
                             const std::string& nameArg,
                             int sockfd,
                             const InetAddress& localAddr,
                             const InetAddress& peerAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(nameArg),
    state_(kConnecting),
    socket_(new Socket(sockfd)),创建以connfd文件描述符初始化的Socket类实例
    channel_(new Channel(loop, sockfd)),//创建Channel,并将connfd文件描述符注册
    localAddr_(localAddr),
    peerAddr_(peerAddr)
{
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  channel_->setReadCallback(
      boost::bind(&TcpConnection::handleRead, this));//将handleRead绑定到Channel的回调函数
}

TcpConnection::~TcpConnection()//其成员函数都是使用智能指针来管理,不需要手动调用其析构
{
  LOG_DEBUG << "TcpConnection::dtor[" <<  name_ << "] at " << this
            << " fd=" << channel_->fd();
}

void TcpConnection::connectEstablished()//该函数实现了Channel注册到Poller中,并执行connectionCallback_函数。connectEstablished函数由TcpServer的newConnection函数调用,且一次connect执行一次。
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);///设置状态
  channel_->enableReading();//向Poller注册

  connectionCallback_(shared_from_this());//调用connectionCallback_,注意该函数的参数,用户在设置是该函数时需要注意
}

void TcpConnection::handleRead()//connfd可读时调用的函数,并调用用户提供的 messageCallback_函数
{
  char buf[65536];
  ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
  messageCallback_(shared_from_this(), buf, n);
}

TcpConnection处理网络通信断开

上节中只实现了TcpConnection的messageCallback_和connectionCallback_初级的消息处理,但TcpConnection只生不灭,需要相应的断开处理,包括移除TcpServer中保存的TcpConnection指针,以及Channel的注销,这些功能的实现需要系统自动调用,直观地实现方式是通过一个closecallback函数注册如Channel中,当连接断开时自动调用,按照正确的顺序移除和析构TcpConnection。

此处列出更改的代码的调用顺序

void Channel::handleEvent()
{
  eventHandling_ = true;
  if (revents_ & POLLNVAL) {
    LOG_WARN << "Channel::handle_event() POLLNVAL";
  }

  if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) {
    LOG_WARN << "Channel::handle_event() POLLHUP";
    if (closeCallback_) closeCallback_();//调用Channel的closeCallback_函数
  }
  if (revents_ & (POLLERR | POLLNVAL)) {
    if (errorCallback_) errorCallback_();
  }
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) {
    if (readCallback_) readCallback_();
  }
  if (revents_ & POLLOUT) {
    if (writeCallback_) writeCallback_();
  }
  eventHandling_ = false;
}

Channel::handleEvent()会在通信断开时调用closeCallback_(),closeCallback_()实际注册的函数为channel_->setCloseCallback(
boost::bind(&TcpConnection::handleClose, this)),即调用TcpConnection::handleClose函数。

TcpConnection::handleClose函数如下:

void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "TcpConnection::handleClose state = " << state_;
  assert(state_ == kConnected);
  channel_->disableAll();//connfd设置为kNoneEvent状态,使得pollfd->fd=-channel->fd()-1,使Poller并不关注fd上的事件
  closeCallback_(shared_from_this());//调用TcpConnection的 closeCallback_函数
}

以上的handleClose函数会调用closeCallback_函数,该函数的注册为TcpConnectionPtr conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)),即TcpConnection的closeCallback_函数会继续调用TcpServer::removeConnection函数。

TcpServer::removeConnection的代码如下:

void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  LOG_INFO << "TcpServer::removeConnection [" << name_
           << "] - connection " << conn->name();
  size_t n = connections_.erase(conn->name());//从map<std::string, TcpConnectionPtr>容器中移除TcpConnection的共享指针,但不会析构,因为当前函数输入拥有一个TcpConnection的共享指针。
  assert(n == 1); (void)n;
  loop_->queueInLoop(
      boost::bind(&TcpConnection::connectDestroyed, conn));//调用queueInLoop,将TcpConnection::connectDestroyed加入到线程队列,使其所在的线程执行完所有活跃pollfd相应的回调函数后执行。
//该TcpConnectionPtr conn的生命周期被延长到了执行函数队列时,那时候没有任何读取需要删除的pollfd的行为(该函数必定被所属的EventLoop执行,
//Channel的nocopyable保证了pollfd亦不会在其他线程使用),可以安全的移除Channel和彻底的析构TcpConnectionPtr conn
}

在执行函数队列时,会调用TcpConnection::connectDestroyed函数,并执行完后,TcpConnectionPtr conn的引用计数将至1,正式自动析构。

TcpConnection::connectDestroyed函数的代码如下:

void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnected);
  setState(kDisconnected);
  channel_->disableAll();
  connectionCallback_(shared_from_this());//告诉用户连接已断开

  loop_->removeChannel(get_pointer(channel_));//移除Channel,该函数结束后,自动析构TcpConnection
}

缓冲区的实现

TcpConnection的函数会负责读取connfd文件描述符上的内容,poll函数具有水平触发方式,messagecallback函数需要一次性读取其中的可读内容,但有时这些connfd上的可读内容并不可以解析成一条完整的指令或一条半的指令,那么这些信息需要存储在一个buffer中,即将connfd上的内容搬运到TcpConnection的buffer中,该buffer的实现在《Linux多线程服务端编程 使用muduo C++网络库》P208。

新增了读写缓冲区的TcpConnection:

namespace muduo
{

class Channel;
class EventLoop;
class Socket;

///
/// TCP connection, for both client and server usage.
///
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 std::string& name,
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr);
  ~TcpConnection();

  EventLoop* getLoop() const { return loop_; }
  const std::string& name() const { return name_; }
  const InetAddress& localAddress() { return localAddr_; }
  const InetAddress& peerAddress() { return peerAddr_; }
  bool connected() const { return state_ == kConnected; }

  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; }

  /// Internal use only.
  void setCloseCallback(const CloseCallback& cb)
  { closeCallback_ = cb; }

  // 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 { kConnecting, kConnected, kDisconnected, };

  void setState(StateE s) { state_ = s; }
  void handleRead(Timestamp receiveTime);
  void handleWrite();
  void handleClose();
  void handleError();

  EventLoop* loop_;
  std::string name_;
  StateE state_;  
  boost::scoped_ptr<Socket> socket_;
  boost::scoped_ptr<Channel> channel_;
  InetAddress localAddr_;
  InetAddress peerAddr_;
  ConnectionCallback connectionCallback_;
  MessageCallback messageCallback_;
  CloseCallback closeCallback_;
  Buffer inputBuffer_;//新增内容,用于存放connfd上的内容
};

typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;

}

有了缓冲区后,相应的读取数据进行处理的函数需要重新实现:

void TcpConnection::handleRead(Timestamp receiveTime)
{
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);//使用buffer来存放读取的数据
  if (n > 0) {
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);//读取buffer中的内容进行相应的处理
  } else if (n == 0) {
    handleClose();
  } else {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

发送数据和output缓冲区

发送数据则是在connfd可写时调用,源码如下:

#ifndef MUDUO_NET_TCPCONNECTION_H
#define MUDUO_NET_TCPCONNECTION_H

#include "Buffer.h"
#include "Callbacks.h"
#include "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
{

class Channel;
class EventLoop;
class Socket;
class TcpConnection : boost::noncopyable,
                      public boost::enable_shared_from_this<TcpConnection>
{
 public:
  TcpConnection(EventLoop* loop,
                const std::string& name,
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr);
  ~TcpConnection();

  EventLoop* getLoop() const { return loop_; }
  const std::string& name() const { return name_; }
  const InetAddress& localAddress() { return localAddr_; }
  const InetAddress& peerAddress() { return peerAddr_; }
  bool connected() const { return state_ == kConnected; }
  void send(const std::string& message);//对挖接口,调用sendInLoop函数,实现线程安全的发送数据
  void shutdown();//对外接口,调用shutdownInLoop(),实现线程安全的关闭connfd的write

  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; }

  void setCloseCallback(const CloseCallback& cb)
  { closeCallback_ = cb; }
  void connectEstablished();  
  void connectDestroyed();  
 private:
  enum StateE { kConnecting, kConnected, kDisconnecting, kDisconnected, };

  void setState(StateE s) { state_ = s; }
  void handleRead(Timestamp receiveTime);
  void handleWrite();//当connfd可写时,调用该函数,取出outputbuffer中的数据,进行发送
  void handleClose();
  void handleError();
  void sendInLoop(const std::string& message);//发送数据
  void shutdownInLoop();//关闭connfd的write

  EventLoop* loop_;
  std::string name_;
  StateE state_; 
  boost::scoped_ptr<Socket> socket_;
  boost::scoped_ptr<Channel> channel_;
  InetAddress localAddr_;
  InetAddress peerAddr_;
  ConnectionCallback connectionCallback_;
  MessageCallback messageCallback_;
  CloseCallback closeCallback_;
  Buffer inputBuffer_;
  Buffer outputBuffer_;//新增 outputbuffer
};

typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;

}

#endif 

成员函数如下:

#include "TcpConnection.h"

#include "logging/Logging.h"
#include "Channel.h"
#include "EventLoop.h"
#include "Socket.h"
#include "SocketsOps.h"

#include <boost/bind.hpp>

#include <errno.h>
#include <stdio.h>

using namespace muduo;

TcpConnection::TcpConnection(EventLoop* loop,
                             const std::string& nameArg,
                             int sockfd,
                             const InetAddress& localAddr,
                             const InetAddress& peerAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(nameArg),
    state_(kConnecting),
    socket_(new Socket(sockfd)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),
    peerAddr_(peerAddr)
{
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  channel_->setReadCallback(
      boost::bind(&TcpConnection::handleRead, this, _1));
  channel_->setWriteCallback(
      boost::bind(&TcpConnection::handleWrite, this));
  channel_->setCloseCallback(
      boost::bind(&TcpConnection::handleClose, this));
  channel_->setErrorCallback(
      boost::bind(&TcpConnection::handleError, this));
}

TcpConnection::~TcpConnection()
{
  LOG_DEBUG << "TcpConnection::dtor[" <<  name_ << "] at " << this
            << " fd=" << channel_->fd();
}

void TcpConnection::send(const std::string& message)//对外接口,用于线程安全的发送数据
{
  if (state_ == kConnected) {
    if (loop_->isInLoopThread()) {
      sendInLoop(message);
    } else {
      loop_->runInLoop(
          boost::bind(&TcpConnection::sendInLoop, this, message));
    }
  }
}

void TcpConnection::sendInLoop(const std::string& message)//发送数据
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  //判断,buffer中没数据且Channel不关注可写事件时直接尝试写入message
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) {
    nwrote = ::write(channel_->fd(), message.data(), message.size());
    if (nwrote >= 0) {
      if (implicit_cast<size_t>(nwrote) < message.size()) {
        LOG_TRACE << "I am going to write more data";
      }
    } else {
      nwrote = 0;
      if (errno != EWOULDBLOCK) {
        LOG_SYSERR << "TcpConnection::sendInLoop";
      }
    }
  }

  assert(nwrote >= 0);
  if (implicit_cast<size_t>(nwrote) < message.size()) {
    outputBuffer_.append(message.data()+nwrote, message.size()-nwrote);//未发送完message,则将剩余的放入buffer,并时Channel重新开始关注可写事件
    if (!channel_->isWriting()) {
      channel_->enableWriting();
    }
  }
}

void TcpConnection::shutdown()//线程安全的关闭socket的write
{
  if (state_ == kConnected)
  {
    setState(kDisconnecting);//设置状态为kDisconnecting
    loop_->runInLoop(boost::bind(&TcpConnection::shutdownInLoop, this));
  }
}

void TcpConnection::shutdownInLoop()
{
  loop_->assertInLoopThread();
  if (!channel_->isWriting())//如果当前Channel还是在关注可写事件,说明outputbuffer中还有数据未发送完,
  //则暂且不执行socket_->shutdownWrite(),handlewrite函数将
  //在发送完outputbuffer中数据后判断状态来执行 socket_->shutdownWrite();
  {
    socket_->shutdownWrite();
  }
}

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  channel_->enableReading();
  connectionCallback_(shared_from_this());
}

void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnected || state_ == kDisconnecting);
  setState(kDisconnected);
  channel_->disableAll();
  connectionCallback_(shared_from_this());

  loop_->removeChannel(get_pointer(channel_));
}

void TcpConnection::handleRead(Timestamp receiveTime)
{
  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();
  }
}

void TcpConnection::handleWrite()//当connfd可写时,调用该函数,取出outputbuffer中的数据,进行发送
{
  loop_->assertInLoopThread();
  if (channel_->isWriting()) {
    ssize_t n = ::write(channel_->fd(),
                        outputBuffer_.peek(),
                        outputBuffer_.readableBytes());//写入数据
    if (n > 0) {
      outputBuffer_.retrieve(n);//根据写入数据的多少,对buffer的可读范围进行调整
      if (outputBuffer_.readableBytes() == 0) {
        channel_->disableWriting();//当outputbuffer没有可读内容时,关闭Channel对connfd的可写事件的关注
        if (state_ == kDisconnecting) {
          shutdownInLoop();//当TcpConnection为kDisconnecting状态时,关闭connfd的可写事件
        }
      } else {
        LOG_TRACE << "I am going to write more data";
      }
    } else {
      LOG_SYSERR << "TcpConnection::handleWrite";
    }
  } else {
    LOG_TRACE << "Connection is down, no more writing";
  }
}

void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "TcpConnection::handleClose state = " << state_;
  assert(state_ == kConnected || state_ == kDisconnecting);
  channel_->disableAll();
  closeCallback_(shared_from_this());
}

void TcpConnection::handleError()
{
  int err = sockets::getSocketError(channel_->fd());
  LOG_ERROR << "TcpConnection::handleError [" << name_
            << "] - SO_ERROR = " << err << " " << strerror_tl(err);
}

数据堆积

如果发送数据时,对方的接受数据的速度与发送方的发送数据的速度不匹配,那么双方中的其中一方会出现数据堆积的情况,用户需要自己实现相应数据堆积时的回调函数(高水位回调),以及当前数据不出现堆积时的回调函数(低水位回调)。muduo中这两个函数名为:WriteCompleteCallback和HighWaterMarkCallback。

WriteCompleteCallback会在outputbuffer的可读内容为0时被调用,说明当前的发送内容全被发送完成,则相应的SendInloop和handleWrite涉及write行为的函数需要相应的更改(write完以后判断当前的buffer可读内容是否为0),而HighWaterMarkCallback则是只在涉及在对outputbuffer进行添加内容的处理,即只在sendInloop更改。

void TcpConnection::sendInLoop(const void* data, size_t len)
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  size_t remaining = len;
  bool faultError = false;
  if (state_ == kDisconnected)
  {
    LOG_WARN << "disconnected, give up writing";
    return;
  }
  // if no thing in output queue, try writing directly
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
  {
    nwrote = sockets::write(channel_->fd(), data, len);
    if (nwrote >= 0)
    {
      remaining = len - nwrote;
      if (remaining == 0 && writeCompleteCallback_)
      {//缓冲区的内容被全部发送,调用低水位回调函数
        loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
      }
    }
    else // nwrote < 0
    {
      nwrote = 0;
      if (errno != EWOULDBLOCK)
      {
        LOG_SYSERR << "TcpConnection::sendInLoop";
        if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
        {
          faultError = true;
        }
      }
    }
  }

  assert(remaining <= len);
  if (!faultError && remaining > 0)
  {
    size_t oldLen = outputBuffer_.readableBytes();
    if (oldLen + remaining >= highWaterMark_
        && oldLen < highWaterMark_
        && highWaterMarkCallback_)
    {//判断当前缓冲区是否过大,调用相应的highWaterMarkCallback_函数
      loop_->queueInLoop(std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
    }
    outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
    if (!channel_->isWriting())
    {
      channel_->enableWriting();
    }
  }
}


void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting()) {
    ssize_t n = ::write(channel_->fd(),
                        outputBuffer_.peek(),
                        outputBuffer_.readableBytes());
    if (n > 0) {
      outputBuffer_.retrieve(n);
      if (outputBuffer_.readableBytes() == 0) {
        channel_->disableWriting();//buffer缓冲区的内容被全部发送完成,调用低水位回调函数
        if (writeCompleteCallback_) {
          loop_->queueInLoop(
              boost::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting) {
          shutdownInLoop();
        }
      } else {
        LOG_TRACE << "I am going to write more data";
      }
    } else {
      LOG_SYSERR << "TcpConnection::handleWrite";
    }
  } else {
    LOG_TRACE << "Connection is down, no more writing";
  }
}

至此基本的TCP网络库的基本框架完成,剩下的就是多线程和epoll的实现。

未完待续…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值