muduo库学习之设计与实现05——实现TCP网络库

东阳的学习笔记

到目前为之,Reactor 事件处理框架已初具规模,本文开始我们用它逐步实现一个非阻塞TCP网络编程库。

从 poll(2)返回到再次调用 poll(2)阻塞称为一次事件循环。

  • 传统的 Reactor 实现一般会把 timers 做成循环中单独的一步。

在这里插入图片描述

一、Acceptor class

先定义 Acceptor class,用于 accept(2) 新TCP连接,并通过回调通知使用者;供 TcpServer 使用,生命期由后者控制。

下面是Acceptor接口:

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 Acceptor 的数据成员

Acceptor的数据成员包括 Socket、Channel等

  • 其中 Socket 是一个 RAII handle,封装了 socket 文件描述符的生命期;Acceptor的 socket 是 listen socket
  • Channel 用于观察此 socket 上的 readable 事件,并回调 Acceptor::handleRead()
    • handleRead会调用 accept(2) 来接受新连接,并回调用户 callback
 private:
  void handleRead();

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

1.2 构造函数和Acceptor::listen()

Acceptor 的构造函数和 Acceptor::listen()执行创建TCP服务端的传统步骤

  • 即调用socket(2) -> bind() -> listen(2) 等 Socket API,其中任何一个步骤出错都会造成程序终止,因此这里并没有对错误进行处理。
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的构造函数调用 creatNoneblockingOrDie() 来创建非阻塞的 socket,现在的 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()

Acceptor::listen() 的最后一步让 acceptChannel_ 在 socket 可读的时候调用 Acceptor::handleRead(),后者会接受(accept(2))并回调 newConnectionCallback_。这里直接把 socket fd传给 callback,这种传递 int 句柄的做法不够理想

  • 在 C++11 中可以先创建 Socket 对象。 再用移动语义把 Socket 对象 std::move() 给回调函数,确保资源的安全释放。
  • 这里没有考虑文件描述符耗尽的情况,muduo 的处理办法见 书本 &7.7。(限制最大并发连接数的方法);还有一个方法,在拿到大于等于0的 connfd 之后,非阻塞地 poll(2) 一下,看看 fd 是否可读写。正常情况下,看看 fd 是否可读写。正常情况下 poll(2) 会返回 writeable,表明 connfd 可用。反之,不可用,应关闭连接。

Acceptor::handleRead() 的策略很简单:后面两种适合做短连接服务

  • 每次 accept(2) 一个 socket。另外还有两种实现策略:
    • 一是每次循环 accept(2),直至没有新的连接到达
    • 二是每次尝试 accept(2) N 个新连接,N的值一般是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 新增的系统调用可以直接 accept(2) 一步得到非阻塞的 socket。

 >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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东阳z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值