是" SO_REUSEPORT" (OS X)或" SO_REUSEADDR" (Linux)真的允许用户使用recvfrom用于多个套接字吗?

时间:2014-10-13 17:06:05

标签: c linux macos sockets udp

我正在OS X(10.9.4)上编写一个非常初学者的UDP套接字代码,该代码使用绑定到同一IP地址和端口的多个UDP套接字。我认为使用SO_REUSEPORT将允许我通过多个套接字接收数据包。但在下面的示例中,只有两个套接字中的一个可以接收数据(即client2,它在client1之后将套接字绑定到端口。

#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <unistd.h>
#include <string>
#include <iostream>

class UDP
{
private:
  uint32_t           fSocket;
  struct sockaddr_in fSourceSocketAddress;
  struct sockaddr_in fDestinationSocketAddress;

public:
  UDP(const std::string& source_host, uint16_t source_port,
      const std::string& dest_host,   uint16_t dest_port);

  void Send(const std::string& str);
  std::string Receive();

};

//______________________________________________________________________________
UDP::UDP(const std::string& source_host, uint16_t source_port,
         const std::string& dest_host,   uint16_t dest_port)
{
  fSocket = socket(AF_INET, SOCK_DGRAM, 0);

  int on = 1;
  errno = 0;
  setsockopt(fSocket, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));

  memset(&fDestinationSocketAddress, 0, sizeof(fDestinationSocketAddress));
  fDestinationSocketAddress.sin_addr.s_addr = inet_addr(dest_host.c_str());
  fDestinationSocketAddress.sin_port = htons(dest_port);
  fDestinationSocketAddress.sin_family = AF_INET;

  memset(&fSourceSocketAddress, 0, sizeof(fSourceSocketAddress));
  fSourceSocketAddress.sin_addr.s_addr = inet_addr(source_host.c_str());
  fSourceSocketAddress.sin_port = htons(source_port);
  fSourceSocketAddress.sin_family = AF_INET;

  errno = 0;
  bind(fSocket, (struct sockaddr*)&fSourceSocketAddress, sizeof(fSourceSocketAddress));
}

//______________________________________________________________________________
void UDP::Send(const std::string& str)
{
  char buffer[100];
  strcpy(buffer, str.c_str());
  sendto(fSocket, buffer, str.length(), 0, (struct sockaddr*)&fDestinationSocketAddress, sizeof(fDestinationSocketAddress));
}

//______________________________________________________________________________
std::string UDP::Receive()
{
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(fSocket, &fds);

  struct timeval tv = {1, 0}; 
  int nselect = select(fSocket + 1, &fds, NULL, NULL, &tv);
  if(nselect == 0 or !FD_ISSET(fSocket, &fds)){
    return "";
  } // if

  char buffer[100];
  recvfrom(fSocket, buffer, 99, 0, NULL, NULL);
  buffer[99] = '\0';

  return std::string(buffer);
}

//______________________________________________________________________________
int main()
{
  UDP client1("127.0.0.1", 8000, "127.0.0.1", 8001);
  UDP client2("127.0.0.1", 8000, "127.0.0.1", 8001);
  UDP server ("127.0.0.1", 8001, "127.0.0.1", 8000);

  for(int i = 0; i < 10; i++){
    server.Send("Hello");
  } // i

  for(int i = 0; i < 10; i++){
    std::cout << "Client 1 received: " << client1.Receive() << std::endl;
    std::cout << "Client 2 received: " << client2.Receive() << std::endl;
  } // i

  return 0;
}

以下是此示例代码的结果。

$ ./a.out         
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello
Client 1 received: 
Client 2 received: Hello

但我的预期结果如下。

$ ./a.out         
Client 1 received: Hello
Client 2 received: Hello
Client 1 received: Hello
Client 2 received: Hello
Client 1 received: Hello
Client 2 received: Hello
Client 1 received: Hello
Client 2 received: Hello
Client 1 received: Hello
Client 2 received: Hello
Client 1 received: 
Client 2 received: 
(snip)

如何才能通过这两个插座接收“Hello”?我还想使代码与Linux(内核2.4)兼容。我在Linux上尝试使用SO_REUSEADDR而不是SO_REUSEPORT,但我得到的结果与OS X相同。

1 个答案:

答案 0 :(得分:1)

SO_REUSEPORTSO_REUSEADDR用于允许其他应用程序(进程)在程序完成后立即使用相同的地址;它们与同一程序中的多线程无关。

您所看到的是两个线程获取已发送消息的通知,但只有线程2正在消息,因为它是最后一个请求该端口的消息。

要考虑的一些事情:

服务器通常只侦听一个端口。没有理由指定远程地址,因为可以从客户端消息中获取。如果您要从指定希望接收消息的客户端的地址和端口,那么您将无法在任何地方处理来自任何其他客户端的请求。

客户端通常(某些特殊情况,如FTP和NFS使用特定客户端端口作为安全措施)打开一个新端口与服务器通信。指定要连接的端口限制了可以在同一台计算机上运行的客户端数量。

此外,对于UDP(不包括多播),没有连接前导码,因此在客户端向服务器发送消息之前,服务器无法知道客户端是否存在,更不用说它的地址了。

我希望这会有所帮助;没有代码可以直接解决你的问题,因为它基于一般的UDP和套接字编程的一些基本误解,但反转你的工作(让客户端与服务器通信,服务器单独响应每个客户端)应该有助于使其更加清晰。