2016-01-13 41 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認爲它綁定的相同。) –

相關問題