我可以延长接受服务器上的TCP握手持续时间吗?

时间:2017-12-02 02:23:36

标签: c++ linux sockets select tcp

这个问题来自Why is nonblocking socket writable before connect() or accept()?

以下代码生成一个侦听TCP连接的线程。主线程连接到服务器正在侦听的地址。

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <pthread.h>
#include <semaphore.h>

class SafeSocket
{
public:

  /** Ctor.
   * Creates a nonblocking socket at the specified IP in the AF_INET family and
   * at a dynamic port.
   */
  SafeSocket( const std::string& ip )
  {
    in_addr_t host_ip = inet_network( ip.c_str() );
    if ( ( socket_ = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
      std::cout << "socket() failed: " << errno << " " << strerror( errno )
                << std::endl;
      socket_ = -1;
    }
    sockaddr_in si;
    memset( &si, 0, sizeof( si ) );
    si.sin_family = AF_INET;
    si.sin_port = 0; // Dynamic port
    si.sin_addr.s_addr = htonl( host_ip );
    if ( bind( socket_, (sockaddr*)&si, sizeof si ) )
    {
      std::cout << "bind() failed: " << errno << " " << strerror( errno )
                << std::endl;
      close( socket_ );
      socket_ = -1;
    }
    // Make the socket do nonblocking connect().
    int flags = fcntl( socket_, F_GETFL, 0 );
    fcntl( socket_, F_SETFL, flags | O_NONBLOCK );
  }

  ~SafeSocket()
  {
    if ( socket_ >= 0 )
    {
      shutdown( socket_, SHUT_RDWR );
      close( socket_ );
    }
  }

  operator int() const
  {
    return socket_;
  }

private:

  int socket_;
};

int connectToClient( const SafeSocket& sock, const std::string& clientIp,
                     const int clientPort )
{
  struct sockaddr_in clientAddr;
  memset( &clientAddr, 0, sizeof clientAddr );
  inet_pton( AF_INET, clientIp.c_str(), &clientAddr.sin_addr );
  clientAddr.sin_family = AF_INET;
  clientAddr.sin_port = htons( clientPort );
  return connect( sock, (sockaddr*)&clientAddr, sizeof clientAddr );
}

std::string serverIp( "127.0.0.200" );
int serverPort = 9099; // Random, hopefully unused.
sem_t listenSem;

/** Entry point to pthread.
 */
void* acceptConnection( void* arg )
{
  int listenSock = socket( PF_INET, SOCK_STREAM, 0 );
  if ( listenSock < 0 )
  {
    std::cout << "socket() failed: " << errno << " " << strerror( errno )
              << std::endl;
    return NULL;
  }

  sockaddr_in si;
  si.sin_family = AF_INET;
  inet_aton( serverIp.c_str(), &si.sin_addr );
  si.sin_port = htons( serverPort );

  int optval = 1;
  setsockopt( listenSock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

  int result = bind( listenSock, (sockaddr*)&si, sizeof si );
  if ( result )
  {
    std::cout << "bind() failed: " << errno << " " << strerror( errno )
              << std::endl;
    close( listenSock );
    return NULL;
  }

  std::cout << "listening on socket " << listenSock << std::endl;
  if ( listen( listenSock, 3 ) )
  {
    std::cout << "listen() failed: " << errno << " " << strerror( errno )
              << std::endl;
    close( listenSock );
    return NULL;
  }

  sem_post( &listenSem );

  fd_set readfds;
  FD_ZERO( &readfds );
  FD_SET( listenSock, &readfds );
  struct timeval ts = { 5, 0 };
  if ( -1 != select( listenSock + 1, &readfds, NULL, NULL, &ts ) )
  {
    if ( FD_ISSET( listenSock, &readfds ) )
    {
      sockaddr_in peerSi;
      socklen_t peerAddrLen = sizeof peerSi;
      memset( &peerSi, 0, peerAddrLen );
      sleep( 3 );
      int acceptSock = accept( listenSock, (sockaddr*)&peerSi, &peerAddrLen );
      if ( acceptSock > 0 )
      {
        std::cout << "accepted connection on socket " << acceptSock
                  << std::endl;
        close( acceptSock );
      }
    }
    else
    {
      std::cout << "did not receive a connection to accept." << std::endl;
    }
  }
  close( listenSock );
  return NULL;
}

int main( int argc, char* argv[] )
{
  sem_init( &listenSem, 0, 0 );

  SafeSocket s( "127.0.0.100" );
  std::cout << "Created socket " << s << std::endl;

  pthread_t tid;
  pthread_create( &tid, NULL, acceptConnection, NULL );

  timespec listenTimeout;
  clock_gettime( CLOCK_REALTIME, &listenTimeout );
  listenTimeout.tv_sec += 5;
  sem_timedwait( &listenSem, &listenTimeout );

  fd_set readFds;
  fd_set writeFds;

  FD_ZERO( &readFds );
  FD_ZERO( &writeFds );

  FD_SET( s, &writeFds );

  timeval timeout = { 5, 0 };
  int result = connectToClient( s, serverIp, serverPort );
  std::cout << "connectToClient() returned " << result << " "
            << errno << " " << strerror( errno ) << std::endl;

  if ( -1 == select( s+1, &readFds, &writeFds, NULL, &timeout ) )
  {
    std::cout << "select() failed: " << errno << " " << strerror( errno )
              << std::endl;
  }

  if ( FD_ISSET( s, &writeFds ) )
  {
    std::cout << s << " is writable!" << std::endl;
    int result = -1;
    socklen_t result_len = sizeof result;
    getsockopt( s, SOL_SOCKET, SO_ERROR, &result, &result_len );
    std::cout << "result: " << result << " " << strerror( result ) << std::endl;
  }

  pthread_join( tid, NULL );
  return 0;
}

输出:

>g++ --version
g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-7)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

>g++ -g main.cpp 
>
>./a.out 
Created socket 3                                             // Immediate
listening on socket 4                                        // Immediate
connectToClient() returned -1 115 Operation now in progress  // Immediate
3 is writable!                                               // Immediate
result: 0 Success                                            // Immediate
accepted connection on socket 5                              // Delayed
评论at the noted question的EJP注意到,在这种情况下,只要“目标TCP正在握手”,select()就会阻塞。

那么,有没有办法延长这种“TCP握手”流程?即我想延长select()阻塞等待套接字s变为可写的时间。可以这样做吗?您可能会注意到我尝试在接受之前添加sleep(),但这不起作用。

1 个答案:

答案 0 :(得分:2)

握手遵循以下阶段:

  • 第1阶段:正在客户端上运行connect。这从您调用connect到至少客户端从服务器获取SYN + ACK数据包开始运行。我说至少是因为客户端内核可能需要一段时间来处理该数据包。我的理解是套接字在第1阶段结束之前不可写。

  • 第2阶段:套接字在客户端上是可写的;连接完成。服务器尚未从accept返回。这包括服务器等待从客户端接收ACK的时间以及接收该ACK和调用accept的服务器应用程序之间的任何时间(以及等待对accept的调用进行处理的时间) )

  • 第3阶段:双方都已完成。

我已经完成了这些阶段:我所绘制的区别旨在让您轻松讨论您的问题,而不是遵循TCP状态机的任何方面。

没有任何有用的方法来延长第1阶段,当然也没办法让第1阶段包含第2阶段。你可以在客户端的内核中添加一些延迟,但这是任意的,与之无关网络上发生的任何事情。

第1阶段和第2阶段之间的时间差异都与网络延迟和服务器计算机内的处理有关。没有触发客户可以采取行动。

对于许多应用程序,只要套接字可写,就可以开始写入。如果不是,普遍接受的解决方案是让您的协议让服务器向客户端发送初始问候语。然后,等待套接字可读并在执行之前处理该问候语。