【问题标题】:Does "SO_REUSEPORT" (OS X) or "SO_REUSEADDR" (Linux) really allow the user to use recvfrom for multiple sockets?“SO_REUSEPORT”(OS X)或“SO_REUSEADDR”(Linux)真的允许用户将recvfrom用于多个套接字吗?
【发布时间】:2014-10-13 17:06:04
【问题描述】:

我正在 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 上相同。

【问题讨论】:

    标签: c linux macos sockets udp


    【解决方案1】:

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

    您看到的是两个线程收到已发送消息的通知,但只有线程 2 将消息出队,因为它是最后一个请求端口的线程。

    需要考虑的一些事情:

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

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

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

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

    【讨论】:

    • 感谢@Greyson 澄清SO_REUSEPORT 不是为了实现我的目的。至少对我来说,OS X 和 Linux 的手册页在这一点上并不清楚。请不要关心我的示例代码的细节。实际上,我并没有使用这种有点奇怪的客户端/服务器模型,实际上它在我的应用程序中不存在。
    猜你喜欢
    • 2019-01-30
    • 2023-03-03
    • 2017-05-31
    • 2015-12-16
    • 2012-09-14
    • 2021-11-16
    • 2013-01-01
    • 2014-07-07
    • 1970-01-01
    相关资源
    最近更新 更多