如何使用connect()获取内核分配给UDP套接字的本地地址?

问题描述

阅读https://www.amazon.com/Unix-Network-Programming-Sockets-Networking/dp/0131411551,章节

8.14 确定与 UDP 的传出接口

连接的 UDP 套接字也可用于确定将要发送的传出接口 用于特定目的地。这是因为连接的副作用 应用于 UDP 套接字时的功能:内核选择本地 IP 地址 (假设进程还没有调用 bind 来显式分配它)。这个本地 通过在路由表中搜索目标 IP 地址来选择 IP 地址,并且 然后将主 IP 地址用于生成的接口。

如果我尝试运行示例 (udpcli01.c):

#define BSIZE 256
#define SERV_PORT 9877

typedef struct sockaddr SA;

//argv[1] = ip address
int main(int argc,char **argv)
{
    int sockfd;
    socklen_t servlen,clilen;
    struct sockaddr_in cliaddr,servaddr;
    char ip[BSIZE];

    if (argc < 2)
    {
        die("usage: %s <ip>\n",argv[0]);
    }

    sockfd = socket(AF_INET,SOCK_DGRAM,0);

    servlen = sizeof(servaddr);
    memset(&servaddr,servlen);
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,argv[1],&servaddr.sin_addr);

    connect(sockfd,(SA *)&servaddr,servlen);

    clilen = sizeof(clilen);
    if (getsockname(sockfd,(SA *)&cliaddr,&clilen) < 0)
    {
        perror("getsockname");
    }

    inet_ntop(AF_INET,&cliaddr,ip,BSIZE);
    printf("address %s:%hd\n",cliaddr.sin_port);
}

现在,如果我在一个终端(地址为 INADDR_ANY 和端口为 9877)中运行服务器,然后运行上面的客户端:

terminal 1:
$ ./udpserv01

terminal 2:
$ #I will try localhost first
$ ./udpcli01 127.0.0.1
address 2.0.178.211:-11342
$ #did not work,Now my host ip 10.0.0.11
$ ./udpcli01 10.0.0.11
address 2.0.193.86:22209

即使客户端在打印其地址和端口之前,我总是得到一些垃圾,连接到正在侦听的服务器。如果我试图打印服务器地址和端口,那么它会工作(也就是说,它会分别打印 127.0.0.1:987710.0.0.11:9877 -> 我已经尝试过)。所以我知道 inet_ntop 工作正常并且还获得了端口号。那么客户端的问题在哪里?内核真的根据书中的connect()分配地址和端口吗?如果是这样,那么为什么我的示例会打印随机垃圾

uname -a:
Linux Shepherd 5.8.0-36-generic #40-Ubuntu SMP Tue Jan 5 21:54:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

解决方法

您正在将 struct sockaddr_in 的地址传递给 inet_ntop 函数。对于 AF_INET 套接字,它需要一个指向 struct in_addr 的指针。

所以不是这样:

inet_ntop(AF_INET,&cliaddr,ip,BSIZE);

你想要这个:

inet_ntop(AF_INET,&cliaddr.sin_addr,BSIZE);

另外,请务必在打印之前对 ntohs 成员调用 sin_port 以获得正确的值,并使用 %d 而不是 %hd

printf("address %s:%d\n",ntohs(cliaddr.sin_port));

并且 clilen 未正确初始化:

clilen = sizeof(clilen);

应该是:

clilen = sizeof(cliaddr);