简体   繁体   English

使用getaddrinfo发送具有固定源端口号的UDP数据包并绑定

[英]Send UDP packet with fixed source port number using getaddrinfo and bind

Using BJ's talker.c code as a template: http://beej.us/guide/bgnet/examples/talker.c 使用BJ的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;
}

I have a requirement whereby the client UDP process that talks to the server must use a fixed, known source port number. 我有一个要求,与服务器通信的客户端UDP进程必须使用固定的已知source端口号。 In this case, assume it's SERVERPORT (4950). 在这种情况下,假设它是SERVERPORT (4950)。 The server then responds to that port number . 然后服务器响应该端口号 Yes, this is unusual as most servers respond to the ephemeral port number that the system assigns to the sender. 是的,这是不寻常的,因为大多数服务器都会响应系统分配给发件人的临时端口号。

After sending a packet using sendto() , I listen for a response using recvfrom() . 使用sendto()发送数据包后,我使用recvfrom()侦听响应。 That's the (pseudo)code I added in the above example. 这就是我在上面的示例中添加的(伪)代码。

All my searches online point to using bind() but that code is usually on the server side. 我所有的在线搜索都指向使用bind()但是该代码通常在服务器端。 I haven't found a way to bind on the client side using the modern getaddrinfo() method. 我还没有找到使用现代的getaddrinfo()方法在客户端进行绑定的方法。 I tried to add a bind() right after the socket() setup but that wouldn't work because p is a server-side structure (derived from the hints structure that uses the server IP address) and I get a bind Error: 我尝试在socket()设置之后立即添加bind()但由于p是服务器端结构(从使用服务器IP地址的提示结构派生而来socket() ,因此不起作用,并且出现绑定错误:

Error 99 (Cannot assign requested address)

code added: 添加的代码:

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

I want to do this in a way that will work for both IPv4 and IPv6. 我想以一种适用于IPv4和IPv6的方式来执行此操作。

I've seen other examples whereby a local/source sockaddr_in structure is filled out with the client's information and that is used in the bind, but those are IPv4 or IPv6 specific. 我已经看到其它实例中,由此局部/源SOCKADDR_IN结构是与客户端的信息填写在绑定使用的,但这些都是IPv4或IPv6特定。

Can someone please show me how to properly update the talker.c code to sendto() and recvfrom() a UDP server using a fixed source port number? 有人可以告诉我如何使用固定的端口号正确地将talker.c代码更新为sendto()recvfrom() UDP服务器吗? Assume that the server is immutable. 假设服务器是不可变的。

The server then responds to that port number . 然后服务器响应该端口号 Yes, this is unusual 是的,这很不寻常

There is nothing unusual about that. 没有什么不寻常的。 This is how most UDP servers are meant to work. 这就是大多数UDP服务器的工作方式。 They always respond to the sender's port. 他们总是响应发送者的端口。 They have no concept whether that port is fixed or ephemeral, that is for the sender to decide. 他们不知道该端口是固定端口还是临时端口,由发送方决定。 Unless a particular protocol dictates that responses are to be sent to a different port, which is not common. 除非特定协议规定将响应发送到其他端口,否则这种情况并不常见。

All my searches online point to using bind() 我所有的在线搜索都指向使用bind()

Correct, that is what you need in this situation. 正确,这是您在这种情况下需要的。

but that code is usually on the server side. 但是该代码通常在服务器端。

There is nothing preventing a client from using bind() . 没有什么可以阻止客户端使用bind()

I haven't found a way to bind on the client side using the modern getaddrinfo() method. 我还没有找到使用现代的getaddrinfo()方法在客户端进行绑定的方法。

It is the exact same as on the server side, except that you have to bind to a specific IP address, you can't bind to 0.0.0.0 or ::0 like you can with a server socket. 它与服务器端完全相同,只是必须绑定到特定的IP地址,而不能像使用服务器套接字那样绑定到0.0.0.0::0

I tried to add a bind() right after the socket() setup but that wouldn't work 我试图在socket()设置之后立即添加一个bind()但这不起作用

Yes, it does. 是的,它确实。 The problem is that you are using the SAME IP address for both binding and sending, and that will not work. 问题是您在绑定和发送时都使用了相同的 IP地址,这将不起作用。 You need to bind to the CLIENT's IP address and then send to the SERVER's IP address. 您需要绑定到CLIENT的 IP地址,然后发送到SERVER的 IP地址。

because p is a server-side structure (derived from the hints structure that uses the server IP address) 因为p是服务器端结构(源自使用服务器IP地址的hints结构)

You are misusing p . 您正在滥用p You can't bind() a client socket to the server's IP address (you need to use connect() for that instead). 您不能bind()客户端套接字bind()到服务器的IP地址(您需要使用connect()来代替)。 You need to bind() a client socket to an IP address that is local to the client's machine. 您需要bind()客户端套接字bind()到客户端计算机本地的IP地址。 Just like you have to bind() a server socket to an IP address that is local to the server machine. 就像您必须bind()服务器套接字bind()到服务器计算机本地的IP地址一样。

Remember, a socket is associated with a pair of IP addresses. 请记住,套接字与一对IP地址相关联。 bind() establishes the socket's LOCAL IP address. bind()建立套接字的本地 IP地址。 connect() establishes the socket's REMOTE IP address. connect()建立套接字的REMOTE IP地址。

I want to do this in a way that will work for both IPv4 and IPv6. 我想以一种适用于IPv4和IPv6的方式来执行此操作。

You can't create a single client socket for both protocols. 您不能为这两个协议创建单个客户端套接字。 You need separate sockets for each protocol (on the server side, you can create a single socket for both protocols, if your platform supports dual-stack sockets). 每个协议都需要单独的套接字(在服务器端,如果平台支持双栈套接字,则可以为两个协议创建一个套接字)。

I've seen other examples whereby a local/source sockaddr_in structure is filled out with the client's information and that is used in the bind, but those are IPv4 or IPv6 specific. 我还看到了其他示例,其中在本地/源sockaddr_in结构中填充了客户端的信息,并在绑定中使用了该信息,但这些结构特定于IPv4或IPv6。

Yes, because you will be sending a packet using EITHER IPv4 OR IPv6, you can't send a packet using both protocols at the same time (a dual-stack socket can receive packets from either protocol, though). 是的,因为您将使用EITHER IPv4 IPv6发送数据包,所以不能同时使用两种协议发送数据包(双堆栈套接字可以从任一协议接收数据包)。

Can someone please show me how to properly update the talker.c code to sendto() and recvfrom() a UDP server using a fixed source port number . 有人可以告诉我如何使用固定的源端口号正确地将talker.c代码更新为sendto()recvfrom() UDP服务器。 Assume that the server is immutable 假设服务器是不可变的

Try something like this: 尝试这样的事情:

#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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM