非阻塞connect的正确使用方式

问题

在使用非阻塞connect时,发现在服务端没有运行时,连接成功,发现是对于connect返回为-1时,对错误码没有做正确的处理,只是对于errno为EINTR做了处理,其它错误码时使用select判断是否可写,可写就将套接字添加到reactor中,这种处理方式是有问题的,应该是在errno为EINPROGRESS时,才使用select判断是否可写,由于出错时,套接字也会变为可写,所以在判断为可写时,需要调用getsockopt(s, SOL_SOCKET, SO_ERROR,…)判断没有出错,才认为是连接成功的。

asio的异步连接处理

linux的处理

首先创建reactive_socket_connect_op操作,调用start_connect_op,在套接字为非阻塞,或者将套接字设置为非阻塞,调用connect发起连接,如果错误码为in_progress或者would_block,将reactive_socket_connect_op操作添加到事件循环中,在套接字变为可写时,会执行reactive_socket_connect_op_base基类的do_perform方法,其会调用socket_ops#non_blocking_connect方法

template <typename Handler>
  void async_connect(implementation_type& impl,
      const endpoint_type& peer_endpoint, Handler& handler)
  {
    bool is_continuation =
      boost_asio_handler_cont_helpers::is_continuation(handler);

    // Allocate and construct an operation to wrap the handler.
    typedef reactive_socket_connect_op<Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      boost_asio_handler_alloc_helpers::allocate(
        sizeof(op), handler), 0 };
    p.p = new (p.v) op(impl.socket_, handler);

    BOOST_ASIO_HANDLER_CREATION((p.p, "socket", &impl, "async_connect"));

    start_connect_op(impl, p.p, is_continuation,
        peer_endpoint.data(), peer_endpoint.size());
    p.v = p.p = 0;
  }
};

```cpp
void reactive_socket_service_base::start_connect_op(
    reactive_socket_service_base::base_implementation_type& impl,
    reactor_op* op, bool is_continuation,
    const socket_addr_type* addr, size_t addrlen)
{
  if ((impl.state_ & socket_ops::non_blocking)
      || socket_ops::set_internal_non_blocking(
        impl.socket_, impl.state_, true, op->ec_))
  {
    if (socket_ops::connect(impl.socket_, addr, addrlen, op->ec_) != 0)
    {
      if (op->ec_ == boost::asio::error::in_progress
          || op->ec_ == boost::asio::error::would_block)
      {
        op->ec_ = boost::system::error_code();
        reactor_.start_op(reactor::connect_op, impl.socket_,
            impl.reactor_data_, op, is_continuation, false);
        return;
      }
    }
  }

  reactor_.post_immediate_completion(op, is_continuation);
}

class reactive_socket_connect_op_base : public reactor_op
{
public:
  reactive_socket_connect_op_base(socket_type socket, func_type complete_func)
    : reactor_op(&reactive_socket_connect_op_base::do_perform, complete_func),
      socket_(socket)
  {
  }

  static bool do_perform(reactor_op* base)
  {
    reactive_socket_connect_op_base* o(
        static_cast<reactive_socket_connect_op_base*>(base));

    return socket_ops::non_blocking_connect(o->socket_, o->ec_);
  }

private:
  socket_type socket_;
};

windows的处理

template <typename Handler>
  void async_connect(implementation_type& impl,
      const endpoint_type& peer_endpoint, Handler& handler)
  {
    // Allocate and construct an operation to wrap the handler.
    typedef win_iocp_socket_connect_op<Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      boost_asio_handler_alloc_helpers::allocate(
        sizeof(op), handler), 0 };
    p.p = new (p.v) op(impl.socket_, handler);

    BOOST_ASIO_HANDLER_CREATION((p.p, "socket", &impl, "async_connect"));

    start_connect_op(impl, impl.protocol_.family(), impl.protocol_.type(),
        peer_endpoint.data(), static_cast<int>(peer_endpoint.size()), p.p);
    p.v = p.p = 0;
  }
};

void win_iocp_socket_service_base::start_connect_op(
    win_iocp_socket_service_base::base_implementation_type& impl,
    int family, int type, const socket_addr_type* addr,
    std::size_t addrlen, win_iocp_socket_connect_op_base* op)
{
  // If ConnectEx is available, use that.
  if (family == BOOST_ASIO_OS_DEF(AF_INET)
      || family == BOOST_ASIO_OS_DEF(AF_INET6))
  {
    if (connect_ex_fn connect_ex = get_connect_ex(impl, type))
    {
      union address_union
      {
        socket_addr_type base;
        sockaddr_in4_type v4;
        sockaddr_in6_type v6;
      } a;

      using namespace std; // For memset.
      memset(&a, 0, sizeof(a));
      a.base.sa_family = family;

      socket_ops::bind(impl.socket_, &a.base,
          family == BOOST_ASIO_OS_DEF(AF_INET)
          ? sizeof(a.v4) : sizeof(a.v6), op->ec_);
      if (op->ec_ && op->ec_ != boost::asio::error::invalid_argument)
      {
        iocp_service_.post_immediate_completion(op, false);
        return;
      }

      op->connect_ex_ = true;
      update_cancellation_thread_id(impl);
      iocp_service_.work_started();

      BOOL result = connect_ex(impl.socket_,
          addr, static_cast<int>(addrlen), 0, 0, 0, op);
      DWORD last_error = ::WSAGetLastError();
      if (!result && last_error != WSA_IO_PENDING)
        iocp_service_.on_completion(op, last_error);
      else
        iocp_service_.on_pending(op);
      return;
    }
  }

  // Otherwise, fall back to a reactor-based implementation.
  reactor& r = get_reactor();
  update_cancellation_thread_id(impl);

  if ((impl.state_ & socket_ops::non_blocking) != 0
      || socket_ops::set_internal_non_blocking(
        impl.socket_, impl.state_, true, op->ec_))
  {
    if (socket_ops::connect(impl.socket_, addr, addrlen, op->ec_) != 0)
    {
      if (op->ec_ == boost::asio::error::in_progress
          || op->ec_ == boost::asio::error::would_block)
      {
        op->ec_ = boost::system::error_code();
        r.start_op(reactor::connect_op, impl.socket_,
            impl.reactor_data_, op, false, false);
        return;
      }
    }
  }

  r.post_immediate_completion(op, false);
}

class win_iocp_socket_connect_op_base : public reactor_op
{
public:
  win_iocp_socket_connect_op_base(socket_type socket, func_type complete_func)
    : reactor_op(&win_iocp_socket_connect_op_base::do_perform, complete_func),
      socket_(socket),
      connect_ex_(false)
  {
  }

  static bool do_perform(reactor_op* base)
  {
    win_iocp_socket_connect_op_base* o(
        static_cast<win_iocp_socket_connect_op_base*>(base));

    return socket_ops::non_blocking_connect(o->socket_, o->ec_);
  }

  socket_type socket_;
  bool connect_ex_;
};

其中win_iocp_socket_connect_op_base 和reactive_socket_connect_op_base 都会调用non_blocking_connect,其内部对window和linux作了区分处理,window是调用select,其设置的超时时间为0,将套接字添加到可写以及异常集合中上。linux是调用poll,添加POLLOUT事件,超时时间设置为0。两个都是立即返回。如果返回的事件数为0,说明还在尝试连接中。否则调用getsockopt判断是否出错,没有出错说明连接成功了

bool non_blocking_connect(socket_type s, boost::system::error_code& ec)
{
  // Check if the connect operation has finished. This is required since we may
  // get spurious readiness notifications from the reactor.
#if defined(BOOST_ASIO_WINDOWS) \
  || defined(__CYGWIN__) \
  || defined(__SYMBIAN32__)
  fd_set write_fds;
  FD_ZERO(&write_fds);
  FD_SET(s, &write_fds);
  fd_set except_fds;
  FD_ZERO(&except_fds);
  FD_SET(s, &except_fds);
  timeval zero_timeout;
  zero_timeout.tv_sec = 0;
  zero_timeout.tv_usec = 0;
  int ready = ::select(s + 1, 0, &write_fds, &except_fds, &zero_timeout);
#else // defined(BOOST_ASIO_WINDOWS)
      // || defined(__CYGWIN__)
      // || defined(__SYMBIAN32__)
  pollfd fds;
  fds.fd = s;
  fds.events = POLLOUT;
  fds.revents = 0;
  int ready = ::poll(&fds, 1, 0);
#endif // defined(BOOST_ASIO_WINDOWS)
       // || defined(__CYGWIN__)
       // || defined(__SYMBIAN32__)
  if (ready == 0)
  {
    // The asynchronous connect operation is still in progress.
    return false;
  }

  // Get the error code from the connect operation.
  int connect_error = 0;
  size_t connect_error_len = sizeof(connect_error);
  if (socket_ops::getsockopt(s, 0, SOL_SOCKET, SO_ERROR,
        &connect_error, &connect_error_len, ec) == 0)
  {
    if (connect_error)
    {
      ec = boost::system::error_code(connect_error,
          boost::asio::error::get_system_category());
    }
    else
      ec = boost::system::error_code();
  }

  return true;
}

qt的非阻塞连接处理

是通过QAbstractSocket::waitForConnected
首先开启QElapsedTimer定时器,调用QAbstractSocketPrivate::_q_startConnecting发起连接,内部调用QAbstractSocketPrivate::_q_connectToNextAddress,调用QNativeSocketEngine::connectToHost,通过调用QNativeSocketEnginePrivate::nativeConnect即connect来发起连接 ,在连接没有成功时,会设置套接字的可写socketEngine->setWriteNotificationEnabled(true);会为套接字添加写处理器QWriteNotifier,当事件触发并且当前连接状态为ConnectingState时,再次发起连接,如果errno为EISCONN则更新状态为ConnectedState。

bool QWriteNotifier::event(QEvent *e)
{
    if (e->type() == QEvent::SockAct) {
        if (engine->state() == QAbstractSocket::ConnectingState)
            engine->connectionNotification();
        else
            engine->writeNotification();
        return true;
    }
    return QSocketNotifier::event(e);
}

void QNativeSocketEngine::connectionNotification()
{
    Q_D(QNativeSocketEngine);
    Q_ASSERT(state() == QAbstractSocket::ConnectingState);

    connectToHost(d->peerAddress, d->peerPort);
    if (state() != QAbstractSocket::ConnectingState) {
        // we changed states
        QAbstractSocketEngine::connectionNotification();
    }
}

bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port)
{
    Q_D(QNativeSocketEngine);
    Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false);

    if (!d->checkProxy(address))
        return false;

    Q_CHECK_STATES3(QNativeSocketEngine::connectToHost(), QAbstractSocket::BoundState,
                   QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false);

    d->peerAddress = address;
    d->peerPort = port;
    bool connected = d->nativeConnect(d->adjustAddressProtocol(address), port);
    if (connected)
        d->fetchConnectionParameters();

    return connected;
}

nativeConnect会调用qt_safe_connect(qnet_unix_p.h)
同时会调用waitForWrite等待可写事件
对于windows系统,如果返回的事件为不为0,则表示连接成功,否则调用getsockopt(d->socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize)获取错误原因。
对于 linux系统,当事件触发并且当前连接状态为ConnectingState时,再次发起连接,如果errno为EISCONN则更新状态为ConnectedState。waitForWrite时,如果没有超时,并且当前状态为ConnectingState,则发起重连,同时调用QAbstractSocketPrivate::_q_testConnection判断连接状态。

bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut)
{
    Q_D(QNativeSocketEngine);
    Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false);
    Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForWrite(),
                      QAbstractSocket::UnconnectedState, false);

    if (timedOut)
        *timedOut = false;

    int ret = d->nativeSelect(msecs, false);
    // On Windows, the socket is in connected state if a call to
    // select(writable) is successful. In this case we should not
    // issue a second call to WSAConnect()
#if defined (Q_OS_WIN)
    if (state() == QAbstractSocket::ConnectingState) {
        if (ret > 0) {
            setState(QAbstractSocket::ConnectedState);
            d_func()->fetchConnectionParameters();
            return true;
        } else {
            int value = 0;
            int valueSize = sizeof(value);
            if (::getsockopt(d->socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) {
                if (value == WSAECONNREFUSED) {
                    d->setError(QAbstractSocket::ConnectionRefusedError, QNativeSocketEnginePrivate::ConnectionRefusedErrorString);
                    d->socketState = QAbstractSocket::UnconnectedState;
                    return false;
                } else if (value == WSAETIMEDOUT) {
                    d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::ConnectionTimeOutErrorString);
                    d->socketState = QAbstractSocket::UnconnectedState;
                    return false;
                } else if (value == WSAEHOSTUNREACH) {
                    d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::HostUnreachableErrorString);
                    d->socketState = QAbstractSocket::UnconnectedState;
                    return false;
                }
            }
        }
    }
#endif

    if (ret == 0) {
        if (timedOut)
            *timedOut = true;
        d->setError(QAbstractSocket::SocketTimeoutError,
                    QNativeSocketEnginePrivate::TimeOutErrorString);
        d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions
        return false;
    } else if (state() == QAbstractSocket::ConnectingState || (state() == QAbstractSocket::BoundState && d->socketDescriptor != -1)) {
        connectToHost(d->peerAddress, d->peerPort);
    }

    return ret > 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kgduu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值