草庐IT

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

coder 2023-09-19 原文

这个问题来自 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

EJP,评论者at the noted question ,注意到在这种情况下,只要“目标 TCP 正在握手”,select() 就会阻塞。

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

最佳答案

握手遵循以下阶段:

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

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

  • 第 3 阶段:双方完成。

我已经完成了这些阶段:我画出的区别旨在让讨论您的问题变得容易,而不是遵循 TCP 状态机的任何方面。

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

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

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

关于c++ - 我可以延长接受服务器的 TCP 握手持续时间吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47603936/

有关c++ - 我可以延长接受服务器的 TCP 握手持续时间吗?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  5. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  6. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  7. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  8. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  9. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  10. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

随机推荐