Отправьте пакет UDP с фиксированным номером порта источника, используя getaddrinfo и bind - PullRequest
0 голосов
/ 07 сентября 2018

Использование кода BJ's talker.c в качестве шаблона: http://beej.us/guide/bgnet/examples/talker.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVERPORT "4950"    // the port users will be connecting to

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;
    struct sockaddr_storage their_addr;
    socklen_t addr_len;
    addr_len = sizeof their_addr;

    if (argc != 3) {
        fprintf(stderr,"usage: talker hostname message\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("talker: socket");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "talker: failed to create socket\n");
        return 2;
    }

    if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0,
             p->ai_addr, p->ai_addrlen)) == -1) {
        perror("talker: sendto");
        exit(1);
    }

    freeaddrinfo(servinfo);

    printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);


//============== Added Code for recvfrom() (pseudocode-ish) =============


    if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN , 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) 
    {
        close(sockfd);
        perror("talker: recvfrom");    
        exit(1);
    }

    close(sockfd);

    printf("Got packet\n");

//============== End Added Code for recvfrom() =============


    close(sockfd);

    return 0;
}

У меня есть требование, согласно которому клиентский процесс UDP, который общается с сервером, должен использовать фиксированный, известный source номер порта. В этом случае предположим, что это SERVERPORT (4950). Затем сервер отвечает на этот номер порта . Да, это необычно, так как большинство серверов отвечают на эфемерный номер порта, который система назначает отправителю.

После отправки пакета, используя sendto(), я прослушиваю ответ, используя recvfrom(). Это (псевдо) код, который я добавил в приведенном выше примере.

Все мои поиски в Интернете указывают на использование bind(), но этот код обычно находится на стороне сервера. Я не нашел способа привязки на стороне клиента, используя современный метод getaddrinfo(). Я попытался добавить bind() сразу после установки socket(), но это не сработало, потому что p - это структура на стороне сервера (полученная из структуры подсказок, которая использует IP-адрес сервера), и я получил ошибку привязки :

Error 99 (Cannot assign requested address)

код добавлен:

bind(sockfd, p->ai_addr, p->ai_addrlen)

Я хочу сделать это так, чтобы это работало как для IPv4, так и для IPv6.

Я видел другие примеры, когда локальная / исходная структура sockaddr_in заполняется информацией о клиенте и , что используется в привязке, но они специфичны для IPv4 или IPv6.

Может кто-нибудь показать мне, как правильно обновить код talker.c до sendto() и recvfrom() на сервере UDP, используя фиксированный исходный * номер порта 1040 *? Предположим, что сервер является неизменным.

1 Ответ

0 голосов
/ 07 сентября 2018

Затем сервер отвечает на этот номер порта . Да, это необычно

В этом нет ничего необычного. Вот как большинство серверов UDP должны работать. Они всегда отвечают на порт отправителя. Они не имеют понятия, является ли этот порт фиксированным или эфемерным, и это должен решать отправитель. Если конкретный протокол не предписывает отправлять ответы на другой порт, что не является распространенным явлением.

Все мои поиски в Интернете указывают на использование bind()

Правильно, это то, что вам нужно в этой ситуации.

но этот код обычно находится на стороне сервера.

Ничто не мешает клиенту использовать bind().

Я не нашел способа привязки на стороне клиента, используя современный метод getaddrinfo().

Это то же самое, что и на стороне сервера, за исключением того, что вам нужно привязать к определенному IP-адресу, вы не можете привязать к 0.0.0.0 или ::0, как вы можете с помощью сокета сервера.

Я пытался добавить bind() сразу после установки socket(), но это не сработало

Да, это так. Проблема в том, что вы используете IP-адрес SAME для привязки и отправки, и это не будет работать. Вам нужно привязаться к IP-адресу *1040* КЛИЕНТА и затем отправить на IP-адрес SERVER.

, поскольку p - это структура на стороне сервера (полученная из структуры hints, которая использует IP-адрес сервера)

Вы неправильно используете p. Вы не можете bind() клиентский сокет с IP-адресом сервера (для этого вам нужно использовать connect()). Вам необходимо bind() клиентский сокет с IP-адресом, локальным для компьютера клиента. Точно так же, как вам нужно bind() сокет сервера для IP-адреса, который является локальным для сервера сервера.

Помните, что сокет связан с парой IP-адресов. bind() устанавливает сокет LOCAL IP-адрес. connect() устанавливает сокет REMOTE IP-адрес.

Я хочу сделать это так, чтобы это работало как для IPv4, так и для IPv6.

Невозможно создать один клиентский сокет для обоих протоколов. Для каждого протокола требуются отдельные сокеты (на стороне сервера вы можете создать один сокет для обоих протоколов, если ваша платформа поддерживает сокеты с двумя стеками).

Я видел другие примеры, когда локальная / исходная структура sockaddr_in заполняется информацией о клиенте и используется в привязке, но они специфичны для IPv4 или IPv6.

Да, поскольку вы будете отправлять пакет, используя ЛИБО IPv4 ИЛИ IPv6, вы не можете отправлять пакет, используя оба протокола одновременно (сокет с двумя стеками) может получать пакеты от любого протокола, хотя).

Может кто-нибудь показать мне, как правильно обновить код talker.c на sendto() и recvfrom() сервер UDP, используя фиксированный номер порта источника. Предположим, что сервер является неизменным

Попробуйте что-то вроде этого:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdbool.h>

#define LOCALPORT  "4950"     // the port users will be sending from
#define SERVERPORT "4950"    // the port users will be connecting to
#define MAXBUFLEN  65535

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *myinfo, *servinfo, *pserv, *plocal;
    int rv;
    int numbytes;
    char buf[MAXBUFLEN]; 
    char ipstr[INET6_ADDRSTRLEN];
    fd_set readfds;
    struct timeval tv;
    bool stop = false;

    if (argc < 3) {
        fprintf(stderr, "usage: talker destaddr message [localaddr]\n");
        return 1;
    }

    // get all of the server addresses
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 2;
    }

    // loop through all the server addresses
    for(pserv = servinfo; (pserv != NULL) && (!stop); pserv = pserv->ai_next) {

        memset(ipstr, 0, sizeof(ipstr));
        switch (pserv->ai_family)
        {
            case AF_INET:
                inet_ntop(AF_INET, &(((struct sockaddr_in*)pserv->ai_addr)->sin_addr), ipstr, INET_ADDRSTRLEN);
                break;
            case AF_INET6:
                inet_ntop(AF_INET6, &(((struct sockaddr_in6*)pserv->ai_addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN);
                break;
        }

        printf("talker: trying to send message to %s\n", ipstr);

        // get all of the matching local addresses
        memset(&hints, 0, sizeof hints);
        hints.ai_family = pserv->ai_family;
        hints.ai_socktype = pserv->ai_socktype;
        hints.ai_protocol = pserv->ai_protocol;

        if ((rv = getaddrinfo(argc > 3 ? argv[3] : NULL, LOCALPORT, &hints, &myinfo)) != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
            continue;
        }

        // loop through all the local addresses, sending the
        // message from each one until a reply is received
        for(plocal = myinfo; (plocal != NULL) && (!stop); plocal = plocal->ai_next) {

            if ((sockfd = socket(plocal->ai_family, plocal->ai_socktype, plocal->ai_protocol)) == -1) {
                perror("socket");
                continue;
            }

            memset(ipstr, 0, sizeof(ipstr));
            switch (plocal->ai_family)
            {
                case AF_INET:
                    inet_ntop(AF_INET, &(((struct sockaddr_in*)plocal->ai_addr)->sin_addr), ipstr, INET_ADDRSTRLEN);
                    break;
                case AF_INET6:
                    inet_ntop(AF_INET6, &(((struct sockaddr_in6*)plocal->ai_addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN);
                    break;
            }

            printf("talker: binding to %s\n", ipstr);

            if (bind(sockfd, plocal->ai_addr, plocal->ai_addrlen) == -1) {
                perror("bind");
                close(sockfd);
                continue;
            }

            // make sure this server address is the only one we talk to
            if (connect(sockfd, pserv->ai_addr, pserv->ai_addrlen) == -1) {
                perror("connect");
                close(sockfd);
                continue;
            }

            if ((numbytes = send(sockfd, argv[2], strlen(argv[2]), 0)) == -1) {
                perror("send");
                close(sockfd);
                continue;
            }

            printf("talker: sent %d bytes\n", numbytes);

            FD_ZERO(&readfds);
            FD_SET(sockfd, &readfds);

            tv.tv_sec = 5;
            tv.tv_usec = 0;

            rv = select(sockfd+1, &readfds, NULL, NULL, &tv);
            if (rv == -1)
            {
                perror("select");
                close(sockfd);
                continue;
            }

            if (rv == 0)
            {
                printf("talker: no reply for 5 seconds\n");
                close(sockfd);
                continue;
            }

            if ((numbytes = recv(sockfd, buf, MAXBUFLEN, 0)) == -1) 
            {
                perror("recv");
                close(sockfd);
                continue;
            }

            printf("talker: received %d bytes\n", numbytes);

            close(sockfd);

            stop = true;
            break;
        }

        freeaddrinfo(myinfo);
    }

    freeaddrinfo(servinfo);

    close(sockfd);

    if (!stop) {
        fprintf(stderr, "talker: failed to communicate with server\n");
        return 3;
    }

    return 0;
}
...