Design and Implementation of Muduo Library Learning 05-Implementation of TCP Network Library

Dongyang's study notes

So far, the Reactor event processing framework has begun to take shape. At the beginning of this article, we will use it to gradually implement a non-blocking TCP network programming library.

Blocking from poll(2) returning to calling poll(2) again is called an event loop.

  • Traditional Reactor implementations generally make timers a separate step in the loop.

Insert picture description here

一、Acceptor class

First define the Acceptor class for accept(2) new TCP connection, and notify the user through callback; it is used by TcpServer, and the lifetime is controlled by the latter.

The following is the Acceptor interface:

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)
  {
      
       newConnectionCallback_ = cb; }

  bool listenning() const {
      
       return listenning_; }
  void listen();

1.1 Data members of Acceptor

Acceptor's data members include Socket, Channel, etc.

  • Among them, Socket is an RAII handle, which encapsulates the lifetime of the socket file descriptor; Acceptor's socket is listen socket
  • Channel is used to observe the readable event on this socket and call back Acceptor::handleRead()
    • handleRead will call accept(2) to accept the new connection and call back the user callback.
 private:
  void handleRead();

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

1.2 Constructor and Acceptor::listen()

Acceptor constructor and Acceptor::listen()perform the traditional steps to create a TCP server

  • That is, calling socket(2) -> bind() -> listen(2) and other Socket APIs, any error in any of the steps will cause the program to terminate, so there is no error handling here.
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie()),
    acceptChannel_(loop, acceptSocket_.fd()),
    listenning_(false)
{
     
     
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.bindAddress(listenAddr);
  acceptChannel_.setReadCallback(
      boost::bind(&Acceptor::handleRead, this));
}

void Acceptor::listen()
{
     
     
  loop_->assertInLoopThread();
  listenning_ = true;
  acceptSocket_.listen();
  acceptChannel_.enableReading();
}

1.3 sockets::createNoneblockingOrDie()

Acceptor's constructor calls creatNoneblockingOrDie() to create a non-blocking socket, which can now be done in one step in Linux.

int sockets::createNonblockingOrDie()
{
     
     
  // 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;
}

1.4 Acceptor::handleRead()

The last step of Acceptor::listen() makes acceptChannel_ call Acceptor::handleRead() when the socket is readable, and the latter will accept (accept(2)) and call back newConnectionCallback_. Here the direct socket fdpass callback, the practice of this delivery int handle less than ideal .

  • In C++11, you can create a Socket object first . Then use move semantics to give the Socket object std::move() to the callback function to ensure the safe release of resources.
  • There is no consideration of the exhaustion of file descriptors. For the handling method of muduo, see Book & 7.7. ( 限制最大并发连接数的方法); There is another way, after getting connfd greater than or equal to 0, poll(2) non-blockingly to see if fd is readable and writable. Under normal circumstances, see if fd is readable and writable. Under normal circumstances poll(2) will return writeable, indicating that connfd is available. Otherwise, it is not available and the connection should be closed.

The strategy of Acceptor::handleRead() is very simple: ( 后面两种适合做短连接服务)

  • Accept(2) one socket at a time. There are also two implementation strategies:
    • One is to loop accept(2) each time until no new connection arrives
    • Second 每次尝试 accept(2) N 个新连接, the value of N is generally 10.
void Acceptor::handleRead()
{
     
     
loop_->assertInLoopThread();
InetAddress peerAddr(0);
//FIXME loop until no more
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0) {
     
     
  if (newConnectionCallback_) {
     
     
    newConnectionCallback_(connfd, peerAddr);
  } else {
     
     
    sockets::close(connfd);
  }
}
}

1.5 sockets::accept()

Linux's new system call can directly accept(2) to get a non-blocking socket in one step.

 >int sockets::accept(int sockfd, struct sockaddr_in* 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:
    // 这里区分暂时错误和致命错误,并区别对待。
    // 对于暂时错误,例如 EAGAIN、EINTR、ECONNABORTED
    case EOPNOTSUPP:
      // unexpected errors
      LOG_FATAL << "unexpected error of ::accept " << savedErrno;
      break;
    default:
      LOG_FATAL << "unknown error of ::accept " << savedErrno;
      break;
  }
}
return connfd;
}

Guess you like

Origin blog.csdn.net/qq_22473333/article/details/113697913