我正在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相同。
答案 0 :(得分:1)
SO_REUSEPORT
和SO_REUSEADDR
用于允许其他应用程序(进程)在程序完成后立即使用相同的地址;它们与同一程序中的多线程无关。
您所看到的是两个线程获取已发送消息的通知,但只有线程2正在消息,因为它是最后一个请求该端口的消息。
要考虑的一些事情:
服务器通常只侦听一个端口。没有理由指定远程地址,因为可以从客户端消息中获取。如果您要从指定希望接收消息的客户端的地址和端口,那么您将无法在任何地方处理来自任何其他客户端的请求。
客户端通常(某些特殊情况,如FTP和NFS使用特定客户端端口作为安全措施)打开一个新端口与服务器通信。指定要连接的端口限制了可以在同一台计算机上运行的客户端数量。
此外,对于UDP(不包括多播),没有连接前导码,因此在客户端向服务器发送消息之前,服务器无法知道客户端是否存在,更不用说它的地址了。
我希望这会有所帮助;没有代码可以直接解决你的问题,因为它基于一般的UDP和套接字编程的一些基本误解,但反转你的工作(让客户端与服务器通信,服务器单独响应每个客户端)应该有助于使其更加清晰。