两个进程共享UDP套接字,一个进程在重新启动后停止接收消息

时间:2016-01-13 10:55:31

标签: linux sockets udp

在Linux平台上,我想在两个进程之间使用套接字共享。其中一个进程在套接字上发送数据,另一个进程接收数据。我在这个网站(here)上读到这是通过设置SO_REUSEADDR和/或SO_REUSEPORT选项来完成的。

所以我设置了一个包含3个过程的测试场景:

1)绑定到localhost的echo服务器侦听127.0.0.1:44000上的消息。收到消息后,它会立即回复发件人;

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

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

问题:Receiver停止接收来自echo服务器的回复。这取决于使用的套接字选项:

使用SO_REUSEADDR: 如果发送方(2)在接收方(3)之后启动,则接收方(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

1 个答案:

答案 0 :(得分:2)

侦听同一接口和端口的两个不同进程的行为是不确定的:它会因操作系统,内核版本和其他因素而异。

通常,端口旨在与单个进程和套接字关联。 SO_REUSE用于UDP多播接收或TCP在连接断开后绕过WAIT状态。虽然某些系统允许您将单个端口绑定到多个套接字,线程或进程以用于其他目的,但行为太多而无法使用。

您可能正在寻找的是某种数据包复制或循环分发。 SO_REUSE不保证。