2016-01-13 36 views
1

在Linux平台上我想用2个进程间共享插座后一个停止接收消息。其中一个进程在套接字上发送数据,另一个接收数据。我读了这个网站(here),这是通过设置选项SO_REUSEADDR和/或SO_REUSEPORT完成。两个进程共享UDP套接字,正在重新启动

所以我设置测试方案与3个流程:

1)结合到本地主机侦听上127.0.0.1:44000消息的回声服务器。当它收到一条消息时立即回复发件人;

2)绑定到127.0.01:44001的发件人并向回显服务器发出周期性消息;

3)绑定到127.0.01:44001的接收器并侦听来自回显服务器的消息;

问题:Receiver停止接收回应服务器的回复。这取决于使用的插座选项:

使用SO_REUSEADDR: 如果发送方(2)在接收方(3)之后启动,则后者不会收到任何内容。如果接收器上次启动,但发件人重新启动,再次接收器停止接收。

随着SO_REUSEPORT(或SO_REUSEADDR一起): 的情况正好相反 - 接收器必须首先启动的东西的工作,如发件人最后,你可以重新启动发件人开始多次,只要你想,一切都会工作得很好。但是,如果您重新启动发件人(或只是最后一次启动),它将不会收到任何消息。

这是我使用的代码:

#define CC_LISTEN_PORT 44000 
#define DRN_LISTEN_PORT 44001 

static void runCC_EchoMode(struct sockaddr_in* ccaddr) 
{ 
    char buf[100]; 
    int s = socket(AF_INET, SOCK_DGRAM, 0); 

    struct sockaddr_in remaddr; 
    int recvlen, sentlen; 

    // bind 
    if(bind(s, (struct sockaddr *)ccaddr, sizeof(struct sockaddr_in)) < 0) 
    { 
     debug("%s: bind failed", __func__); 
     return; 
    } 

    /* now loop, receiving data and printing what we received */ 
    unsigned int addrlen = sizeof(remaddr); 
    int count = 0; 
    for (;;) { 
     debug("waiting on port %d\n", ntohs(ccaddr->sin_port)); 
     recvlen = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&remaddr, &addrlen); 
     debug("received %d bytes\n", recvlen); 
     if (recvlen > 0) { 
      buf[recvlen] = 0; 
      printf("received message: \"%s\"\n", buf); 

      // send echo back 
      sprintf(buf, "Echo #%d", count++); 
      sentlen = sendto(s, buf, strlen(buf), 0, (struct sockaddr *)&remaddr, sizeof(remaddr)); 
      debug("sent %d bytes to %s:%d\n", sentlen, 
        inet_ntoa(remaddr.sin_addr), ntohs(remaddr.sin_port)); 
     } 
    } 

    close(s); 
} 

static void runDrn_SendMode(struct sockaddr_in* ccaddr, struct sockaddr_in* drnaddr) 
{ 
    char buf[100]; 
    int s = socket(AF_INET, SOCK_DGRAM, 0); 
    int sentlen; 

    int one = 1; 
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) { 
     debug("setsockopt(SO_REUSEADDR) failed\n"); 
    } 
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(int)) < 0) { 
     debug("setsockopt(SO_REUSEPORT) failed\n"); 
    } 


    // bind 
    if(bind(s, (struct sockaddr *)drnaddr, sizeof(struct sockaddr_in)) < 0) 
    { 
     debug("%s: bind failed", __func__); 
     return; 
    } 

    int count = 0; 
    for (;;) { 
     sleep(2); 

     sprintf(buf, "Hello #%d", count++); 

     debug("sending \"%s\" to server...\n", buf); 
     sentlen = sendto(s, buf, strlen(buf), 0, (struct sockaddr *)ccaddr, sizeof(struct sockaddr_in)); 
     debug("sent %d bytes\n", sentlen); 
    } 

    close(s); 
} 

static void runDrn_RcvMode(struct sockaddr_in* ccaddr, struct sockaddr_in* drnaddr) 
{ 
    char buf[100]; 
    int s = socket(AF_INET, SOCK_DGRAM, 0); 

    struct sockaddr_in remaddr; 
    int recvlen; 

    int one = 1; 
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) { 
     debug("setsockopt(SO_REUSEADDR) failed\n"); 
    } 
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(int)) < 0) { 
     debug("setsockopt(SO_REUSEPORT) failed\n"); 
    } 

    // bind 
    if(bind(s, (struct sockaddr *)drnaddr, sizeof(struct sockaddr_in)) < 0) 
    { 
     debug("%s: bind failed", __func__); 
     return; 
    } 

    /* now loop, receiving data and printing what we received */ 
    unsigned int addrlen = sizeof(remaddr); 
    for (;;) { 
     debug("waiting on port %d\n", ntohs(drnaddr->sin_port)); 
     recvlen = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&remaddr, &addrlen); 
     debug("received %d bytes\n", recvlen); 
     if (recvlen > 0) { 
      buf[recvlen] = 0; 
      printf("received message: \"%s\"\n", buf); 
     } 
    } 

    close(s); 
} 

int main(int argc, char *argv[]) 
{ 
    int mode; 

    if (argc < 3) { 
     fprintf(stderr, "Usage: %s <host> <mode>\n", argv[0]); 
     exit(EXIT_FAILURE); 
    } 

    printf("Starting process with PID: %d\n", getpid()); 

    // this is a simple wrapper of getaddrinfo() 
    AddressResolver resv1(argv[1]); 
    resv1.print(); 

    struct sockaddr_in ccaddr, drnaddr; 

    ccaddr = *(resv1.getAddress(0)); 
    ccaddr.sin_port = htons(CC_LISTEN_PORT); 

    drnaddr = *(resv1.getAddress(0)); 
    drnaddr.sin_port = htons(DRN_LISTEN_PORT); 


    mode = atoi(argv[2]); 
    switch(mode) { 
    case 0: // cc 
     runCC_EchoMode(&ccaddr); 
     break; 

    case 1: // drone sender 
     runDrn_SendMode(&ccaddr, &drnaddr); 
     break; 

    case 2: // drone receiver 
     runDrn_RcvMode(&ccaddr, &drnaddr); 
     break; 

    default: 
     debug("Mode is not available\n"); 
     break; 
    } 

    return 0; 
} 

而且我这是怎么开始的3个进程:

./testUDP localhost 0 
./testUDP localhost 1 
./testUDP localhost 2 

这是一个测试运行的输出:

./testUDP localhost 0 

Starting process with PID: 10651 
IP: 127.0.0.1 
waiting on port 44000 
received 8 bytes 
received message: "Hello #0" 
sent 7 bytes to 127.0.0.1:44001 
waiting on port 44000 
received 8 bytes 
received message: "Hello #1" 
sent 7 bytes to 127.0.0.1:44001 
waiting on port 44000 
received 8 bytes 
received message: "Hello #2" 
sent 7 bytes to 127.0.0.1:44001 
waiting on port 44000 
^C 

...

./testUDP localhost 1 

Starting process with PID: 10655 
IP: 127.0.0.1 
sending "Hello #0" to server... 
sent 8 bytes 
sending "Hello #1" to server... 
sent 8 bytes 
sending "Hello #2" to server... 
sent 8 bytes 
^C 

...

./testUDP localhost 2 

Starting process with PID: 10652 
IP: 127.0.0.1 
waiting on port 44001 
received 7 bytes 
received message: "Echo #0" 
waiting on port 44001 
received 7 bytes 
received message: "Echo #1" 
waiting on port 44001 
received 7 bytes 
received message: "Echo #2" 
waiting on port 44001 
^C 
+0

您没有正确使用SO_REUSE *选项,它们不是针对您正在尝试执行的操作而设计的。 – SergeyA

+0

好的,除了所有的说法,我仍然无法找到差异,仍然没有指定它们中的任何一个导致第二个进程(发送者或接收者)不会通过bind()调用。这就是为什么我不得不在任何情况下使用它们中的任何一种,我想打开相同的IP:端口。 – shondll

回答

2

两个不同的过程收听相同的接口和端口上的行为是不确定的:它会由操作系统,内核版本,以及其它因素而有所不同。

通常,一个端口旨在与单个进程和套接字关联。 SO_REUSE旨在用于UDP多播接收或TCP连接后下降绕过WAIT状态。虽然有些系统将让你绑定一个端口到多个插座,线,或用于其他目的的过程中,行为过于多样化是有益的。

您可能正在寻找的是某种包重复或循环分布。 SO_REUSE不保证。

+0

所以这样的功能不是正确的路要走吗?有没有办法处理一个可执行文件的发送和另一个可执行文件的接收? – shondll

+0

我不是很清楚你想要完成什么。通常,每个进程都应绑定到不同的端口。如果进程A想要发送到进程B,它将发送到进程B绑定的端口。如果B想要回复,它会发回它正在回复的消息的源端口。 (这很重要:回复到源端口,并不总是与端口A认为它绑定的相同。) –

相关问题