以muduo为
例,在类Acceptor
中,成员变量监听套接字acceptSocket_
在Acceptor构造时调用socket::createNonblockingOrDie()
, 它的实现如下:
int sockets::createNonblockingOrDie(sa_family_t family)
{
#if VALGRIND
int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
setNonBlockAndCloseOnExec(sockfd);
#else
int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
#endif
return sockfd;
}
其中又调用了setNonBlockAndCloseOnExec()
,注意其中设置套接字的方法,先通过fcntl()获取原属性,然后原属性 | 上O_NONBLOCK,再通过fcntl()设置修改后的新属性。
#if VALGRIND || defined (NO_ACCEPT4)
void setNonBlockAndCloseOnExec(int sockfd)
{
// 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
(void)ret;
}
#endif
} // namespace
监听套接字就被设置成了非阻塞。
当有新的连接请求到来时,套接字可读,触发调用Acceptor::handleRead()
,其实现如下:
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
//FIXME loop until no more
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
// string hostport = peerAddr.toIpPort();
// LOG_TRACE << "Accepts of " << hostport;
if (newConnectionCallback_)
{
newConnectionCallback_(connfd, peerAddr);
}
else
{
sockets::close(connfd);
}
}
else
{
LOG_SYSERR << "in Acceptor::handleRead";
// Read the section named "The special problem of
// accept()ing when you can't" in libev's doc.
// By Marc Lehmann, author of libev.
if (errno == EMFILE)
{
::close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
::close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}
其中又调用了sockets::accept(int sockfd, struct sockaddr_in6* addr)
,其实现如下:
int sockets::accept(int sockfd, struct sockaddr_in6* addr)
{
socklen_t addrlen = static_cast<socklen_t>(sizeof *addr);
#if VALGRIND || defined (NO_ACCEPT4)
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;
}
从其实现来看,监听套接字为非阻塞时,对其调用accept()
返回的套接字应该还是为阻塞的,不然也不会接着调用setNonBlockAndCloseOnExec()
。如果要使accept()
返回的套接字直接就是非阻塞的,可以调用accept4()
[如果支持的话],它比accept()
多了一个参数[SOCK_NONBLOCK | SOCK_CLOEXEC],可以通过设置该参数使返回的连接套接字直接就是非阻塞的。