为什么在这个 UDP client/server 示例中不需要绑定客户端套接字?

Why is it unnecessary to bind the client socket in this UDP client/server example?

我在学习套接字编程的同时,通过示例和阅读 material 拼凑了以下代码。这是一个简单的 UDP client/server 示例:

client.cpp

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <ctime>
#include <cstring>
#include <unistd.h>
#include <cstdlib>

int main( int argc, char* argv[] )
{
  errno = 0;
  int retval = 0;

  // Populate server sockaddr_in.
  sockaddr_in svr_si;
  svr_si.sin_family = AF_INET;
  const char* svr_addr = "127.0.0.3";
  svr_si.sin_addr.s_addr = inet_addr( svr_addr );
  svr_si.sin_port = htons( 9090 );

  sockaddr_in peer_si;                      // Peer sockaddr_in (populated by recv())
  socklen_t peer_addr_len = sizeof peer_si; // Peer sockaddr_in length

  srand( time( NULL ) );

  // Create socket.
  int sd = socket( PF_INET, SOCK_DGRAM, 0 );

  char buf[ 1024 ]; // Receive buffer
  ssize_t bytes;    // Sent/received bytes
  ssize_t msg_len;  // Length of sent message

  bool cont = true;
  while ( cont )
  {
    // Create random message of length 10 or "quit"
    if ( ! ( rand() % 7 ) )
    {
      std::strcpy( buf, "quit" );
    }
    else
    {
      msg_len = rand() % 10;
      for ( int i = 0; i < msg_len; ++i )
      {
        buf[ i ] = rand() % ('z' - 'A' + 1) + 'A';
      }
      buf[ msg_len ] = '[=11=]';
    }
    std::cout << "Client sending \"" << buf << "\" to ip(" << svr_addr <<
                 ") port(" << ntohs( svr_si.sin_port ) << ")." << std::endl;

    // Send message to server.
    if ( -1 == ( bytes = sendto( sd, buf, strlen( buf ), 0,
                                 (struct sockaddr*)&svr_si,
                                 sizeof( svr_si ) ) ) )
    {
      std::cout << "send() failed." << std::endl;
    }

    // Receive (blocking) on unbound socket; save sender address to peer_si.
    bytes = recvfrom( sd, buf, sizeof buf - 1, 0, (sockaddr*)&peer_si,
                      &peer_addr_len );
    if ( bytes >= 0 )
    {
      buf[ bytes ] = '[=11=]';

      std::cout << "Client received " << bytes << " bytes from addr(" <<
        inet_ntoa( peer_si.sin_addr ) << ") port(" << peer_si.sin_port <<
        "): [" << buf << "]" << std::endl;
      std::cout << "Server acked." << std::endl;
      if ( ! std::strcmp( buf, "quit" ) )
      {
        std::cout << "Client quitting." << std::endl;
        cont = false;
      }
    }
    else
    {
      std::cout << "recvfrom() failed: " << errno << "(" << strerror( errno )
        << ")." << std::endl;
      retval = 1;
      cont = false;
    }
  }

  if ( -1 != sd )
  {
    close( sd );
  }

  return retval;
}

server.cpp

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>

int main( int argc, char* argv[] )
{
  errno = 0;
  int retval = 0;

  // Create a socket.
  int sd = socket( PF_INET, SOCK_DGRAM, 0 );

  // Initialize a sockaddr_in with this' (the server's) address.
  sockaddr_in si;
  si.sin_family = AF_INET;
  si.sin_addr.s_addr = inet_addr( "127.0.0.3" );
  si.sin_port = htons( 9090 );

  sockaddr_in peer_si;                        // Peer sockaddr_in
  socklen_t peer_addr_len = sizeof peer_si;   // Peer sockaddr length
  memset( &peer_si, 0, peer_addr_len );

  // Bind address to socket.
  if ( ! bind( sd, (sockaddr*)&si, sizeof si ) )
  {
    char buf[ 1024 ];                           // Receive buffer
    ssize_t bytes;                              // Bytes received
    bool cont = true;

    while ( cont )
    {
      // Receive (blocking) on bound socket.
      bytes = recvfrom( sd, buf, sizeof buf - 1, 0, (sockaddr*)&peer_si,
                        &peer_addr_len );
      if ( bytes >= 0 )
      {
        buf[ bytes ] = '[=12=]';

        std::cout << "Server received " << bytes << " bytes from addr(" <<
                     inet_ntoa( peer_si.sin_addr ) << ") port(" <<
                     peer_si.sin_port << ") family(" << peer_si.sin_family <<
                     "): [" << buf << "]" << std::endl;
        std::cout << "Server acking." << std::endl;

        if ( ! std::strcmp( buf, "quit" ) )
        {
          std::cout << "Server quitting." << std::endl;
          cont = false;
        }
        else
        {
          // Send "ack" back to whatever was read into peer_si.
          bytes = sendto( sd, "ack", 3, 0, (struct sockaddr*)&peer_si,
                          peer_addr_len );
          if ( 3 != bytes )
          {
            std::cout << "sendto() failed: " << errno << "(" <<
                         strerror( errno ) << ")" << std::endl;
            retval = 1;
            cont = false;
          }
        }
      }
      else
      {
        std::cout << "recvfrom() failed: " << errno << "(" << strerror( errno )
          << ")." << std::endl;
        retval = 1;
        cont = false;
      }
    }
  }
  else
  {
    std::cout << "bind() failed: " << errno << "(" << strerror( errno ) << ")."
              << std::endl;
    retval = 1;
  }

  if ( -1 != sd )
  {
    if ( 0 != peer_si.sin_addr.s_addr )
    {
      sendto( sd, "quit", 4, 0, (struct sockaddr*)&peer_si, peer_addr_len );
    }
    close( sd );
  }

  return retval;
}

编译执行如下:

>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 client.cpp -o client
>g++ -g server.cpp -o server

>./server &
[1] 19106
>./client 
Client sending "vw" to ip(127.0.0.3) port(9090).
Server received 2 bytes from addr(127.0.0.1) port(12004) family(2): [vw]
Server acking.
Client received 3 bytes from addr(127.0.0.3) port(33315): [ack]
Server acked.
Client sending "M" to ip(127.0.0.3) port(9090).
Server received 1 bytes from addr(127.0.0.1) port(12004) family(2): [M]
Server acking.
Client received 3 bytes from addr(127.0.0.3) port(33315): [ack]
Server acked.
Client sending "quit" to ip(127.0.0.3) port(9090).
Server received 4 bytes from addr(127.0.0.1) port(12004) family(2): [quit]
Server acking.
Server quitting.
Client received 4 bytes from addr(127.0.0.3) port(33315): [quit]
Server acked.
Client quitting.
[1]+  Done                    ./server

我的问题:

似乎 bind() 客户端上的套接字是不必要的 - 为什么?您会在 client.cpp 中注意到,我所做的只是在发送消息之前创建套接字。服务器似乎可以很好地接收消息,而且能够将消息发送回它从 recvfrom().

填充的地址获得的地址

似乎不​​需要 bind() 客户端套接字与自动将套接字绑定到 INADDR_ANY 有什么关系吗?如果是这样,为什么客户端套接字绑定的结果 IP 地址恰好是 127.0.0.1?它会不会是一个不同的 "available" IP,例如127.0.0.2?

第一次发送时,如有必要,会自动绑定。可能是先发送还是先接收,不确定。

Why does the resulting IP address that the client's socket is bound to happen to be 127.0.0.1

因为您要发送到本地地址。它将根据到达目的地的静态 IP 路由表选择正确的本地 IP 地址。

Could it ever be a different "available" IP, e.g. 127.0.0.2

帮不上忙,抱歉。