使用抽象套接字 AF_UNIX SOCK_DGRAM

4
我正在尝试修改原始的Michael Kerrisk AF_UNIX SOCK_DGRAM示例客户端/服务器程序。请单击以下两个链接(client, server)获取更多信息。该示例代码最初发表在他的书The Linux Programming Interface, chapter 57中。我的目标是基于Kerrisk的代码示例,将“文件套接字”定义替换为“抽象套接字”定义。
不幸的是,我无法建立我的版本的客户端和服务器之间的通信。当然,Kerrisk的版本可以正常工作
对我来说很明显,我做错了一些根本性的事情......但是......我想不出是什么。 =================== 服务器 =============================== 这是我版本的'server'的Kerrisk代码:
int main(int argc, char *argv[])
{
   struct sockaddr_un svaddr, claddr;
   int sfd, j;
   ssize_t numBytes;
   socklen_t len;
   char buf[BUF_SIZE];

   char *abstract_server;

   sfd = socket(AF_UNIX, SOCK_DGRAM, 0);       /* Create server socket */
   if (sfd == -1)
       errExit("socket");

   abstract_server = "viper_server";
   /* Construct well-known address and bind server socket to it */

   if (remove(abstract_server) == -1 && errno != ENOENT)
       errExit("remove-%s", abstract_server);  

   memset(&svaddr, 0, sizeof(struct sockaddr_un));
   svaddr.sun_family = AF_UNIX;
   strncpy(&svaddr.sun_path[1], abstract_server, strlen(abstract_server));

   if (bind(sfd, (struct sockaddr *) &svaddr, 
     sizeof(sa_family_t) + strlen(abstract_server) + 1) == -1)
       errExit("bind");

   /* Receive messages, convert to uppercase, and return to client */

   for (;;) {
       len = sizeof(struct sockaddr_un);
       numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
                           (struct sockaddr *) &claddr, &len);
       if (numBytes == -1)
           errExit("recvfrom");

       printf("Server received %ld bytes from %s\n", (long) numBytes,
               claddr.sun_path);

       for (j = 0; j < numBytes; j++)
       buf[j] = toupper((unsigned char) buf[j]);

       if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) != numBytes)
           fatal("sendto");
     }
 }

========================= 客户端 ============================

这是我对Kerrisk的'client'代码的版本:

int main(int argc, char *argv[])
{
    struct sockaddr_un svaddr, claddr;
    int sfd, j;
    size_t msgLen;
    ssize_t numBytes;
    char resp[BUF_SIZE];

    char *abstract_client;
    char *abstract_server;

    if (argc < 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s msg...\n", argv[0]);

    /* Create client socket; bind to unique pathname (based on PID) */

    sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sfd == -1)
        errExit("socket");

    abstract_client = "viper_client";
    abstract_server = "viper_server";

    memset(&claddr, 0, sizeof(struct sockaddr_un));
    claddr.sun_family = AF_UNIX;
    strncpy(&claddr.sun_path[1], abstract_client, strlen(abstract_client)); 

    if (bind(sfd, (struct sockaddr *) &claddr, 
      sizeof(sa_family_t) + strlen(abstract_client) + 1) == -1)
        errExit("bind");

    /* Construct address of server */

    memset(&svaddr, 0, sizeof(struct sockaddr_un));
    svaddr.sun_family = AF_UNIX;
    strncpy(&svaddr.sun_path[1], abstract_server, strlen(abstract_server));

    /* Send messages to server; echo responses on stdout */

    for (j = 1; j < argc; j++) {
        msgLen = strlen(argv[j]);       /* May be longer than BUF_SIZE */

       /* code FIX */
       if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
                 (sizeof(sa_family_t) + strlen(abstract_server) + 1) ) != msgLen) 
              fatal("sendto");

        /* original - non working code - replaced with the code FIX above 
        if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
                sizeof(struct sockaddr_un)) != msgLen)
        {
            fatal("sendto");
        } */

        numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
        /* Or equivalently: numBytes = recv(sfd, resp, BUF_SIZE, 0);
                        or: numBytes = read(sfd, resp, BUF_SIZE); */
        if (numBytes == -1)
            errExit("recvfrom");
        printf("Response %d: %.*s\n", j, (int) numBytes, resp);
    }

    remove(claddr.sun_path);            /* Remove client socket pathname */
    exit(EXIT_SUCCESS);
}

我如何启动测试:

> ./server &
> ./client  aa bb cc
> result:  ERROR: sendto

任何帮助|建议|解决方案|RTFM都非常欢迎。
伊戈尔
附言:由于我的声誉不到10,我只能发布2个链接(请参见此帖子顶部的Kerrisk的客户端URL,Kerrrisk的服务器URL)。因此,我对“如何使用抽象套接字”的Kerrisk C代码片段进行了“剪切和粘贴”。它在他的书中发布,位于第1176页,列表57-8。
int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_un addr;
    char *str;

    memset(&addr, 0, sizeof(struct sockaddr_un));  /* Clear address structure */
    addr.sun_family = AF_UNIX;                     /* UNIX domain address */

    /* addr.sun_path[0] has already been set to 0 by memset() */

    str = "xyz";        /* Abstract name is "\0abc" */
    strncpy(&addr.sun_path[1], str, strlen(str));

    // In early printings of the book, the above two lines were instead:
    //
    // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2);
    //            /* Abstract name is "xyz" followed by null bytes */

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1)
        errExit("socket");

    if (bind(sockfd, (struct sockaddr *) &addr,
            sizeof(sa_family_t) + strlen(str) + 1) == -1)
        errExit("bind");

    // In early printings of the book, the final part of the bind() call
    // above was instead:
    //        sizeof(struct sockaddr_un)) == -1)

    sleep(60);

    exit(EXIT_SUCCESS);
}

你能否加入一些头文件,使代码能够编译通过。我建议使用perror()函数打印sendto()函数的错误信息。 - thuovila
我已经使用@user2760751和@thuovila提出的修复方案修复了上面的C代码。@user2760751的解决方案中有一个拼写错误。他写道 sizeof(sa_family_t) + strlen(abstract_client) + 1;而不是sizeof(sa_family_t) + strlen(abstract_server) + 1; - Igor
2个回答

3
if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
            sizeof(struct sockaddr_un)) != msgLen)

最后一个参数无效,sockaddr_un地址的长度应该为
sizeof(sa_family_t) + strlen(abstract_client) + 1;

顺便提一下,当从glibc API出现错误返回时,您应该打印“errno”值以精确定位发生了什么。在上述情况中,“errno”的值为“111”,表示无法到达服务器地址。


好的,在再次阅读您的帖子后,我认为我明白了您的意思。

当您使用非抽象的Unix套接字(sun_path [0]!= 0)时,内核使用strlen(sun_path)计算服务器名称长度。因此,最后一个参数实际上并没有被使用。

net/unix/af_unix.c:unix_mkname

if (sunaddr->sun_path[0]) {
    /* ... */
    ((char *)sunaddr)[len] = 0;
    len = strlen(sunaddr->sun_path)+1+sizeof(short);
    return len;
}  

但是当您使用抽象的Unix套接字(sun_path [0] == 0)时,服务器名称长度是您的“bind”或“sendto”的最后一个参数。

net / unix / af_unix.c:unix_mkname:

*hashp = unix_hash_fold(csum_partial(sunaddr, len, 0));

当您将套接字绑定到具有长度的地址时,
if (bind(sfd, (struct sockaddr *) &svaddr,
    sizeof(sa_family_t) + strlen(abstract_server) + 1) == -1)

内核认为服务器名称是一个具有该长度的数组。

当您尝试发送到一个地址时,

if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
            sizeof((struct sockaddr)) != msgLen)

内核认为服务器名称是具有最后一个参数长度的数组。

因为,

sizeof((struct sockaddr)) != sizeof(sa_family_t) + strlen(abstract_server) + 1

内核不认为地址相同,因此sendto返回errno 111。

你的理解有一部分是正确的,我认为你理解了代码的原始问题。然而,“最后一个参数实际上没有被使用”这个说法有点不准确。例如,考虑原始链接的tlpi示例。如果在sockaddr_un中传递了正确的空终止sun_path,则客户端sendto()调用中的长度可以是15到108之间的任何值。长度小于15会在sendto中产生错误。长度在内核代码中显然被使用。 - thuovila

2
您的服务器绑定的地址与客户端在sendto()中使用的地址不同。这是因为您在客户端和服务器中计算svaddr的长度方式不同。
服务器: sizeof(sa_family_t) + strlen(abstract_server) + 1) 客户端: sizeof(struct sockaddr_un) Unix套接字地址是struct sockaddr_un.sun_path中描述的路径的完整给定长度。将大小计算更改为与客户端和服务器相匹配即可解决问题。
编辑: 将abstract_client改为abstract_server。似乎我和其他答案一样复制粘贴出了错。

我不确定我是否正确理解了你的评论。这是从服务器代码库中的'bind'语句的“剪切和粘贴”。abstract_server = "viper_server"; if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(sa_family_t) + strlen(abstract_server) + 1) == -1)从客户端代码库中的“剪贴板”:abstract_server = "viper_server"; strncpy(&svaddr.sun_path[1], abstract_server, strlen(abstract_server)); if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) != msgLen) - Igor
我想说的是 sizeof(sa_family_t) + strlen(abstract_server) + 1 不等于 sizeof(struct sockaddr_un),因此这两个使用的 "服务器地址" 不同。在 sendto() 中给出 addrlen 时,请更改客户端代码以表示 sizeof(sa_family_t) + strlen(abstract_server) + 1 - thuovila

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接