IRC 客户端没有 print/receive 完整响应

IRC client does not print/receive full response

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <unistd.h>

static char *host = "irc.libera.chat";
static char *port = "6667";
static char *chan = "#libera";
static char *nick = "nick";
static char *pass = NULL;

static int sock  = 0;

void
message(char *fmt, ...) {
    va_list ap;
    /* determine size */
    va_start(ap, fmt);
    int n = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed", stderr);
        exit(EXIT_FAILURE);
    }
    size_t size = n + 1;
    /* construct */
    char *msg = malloc(size);
    if (msg == NULL) {
        perror("malloc() failed");
        exit(EXIT_FAILURE);
    }
    va_start(ap, fmt);
    n = vsnprintf(msg, size, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed\n", stderr);
        free(msg);
        exit(EXIT_FAILURE);
    }
    /* send */
    ssize_t nsent = send(sock, msg, size, 0);
    free(msg);
    if (nsent == -1) {
        perror("send() failed");
        exit(EXIT_FAILURE);
    } else if ((size_t)nsent != size) {
        fprintf(stderr,
                "send() failed: expected to send %lu bytes, sent %ld instead\n",
                size, nsent);
        exit(EXIT_FAILURE);
    }
}

int
main(void) {
    /* initialize connection */
    struct addrinfo hints = {
        .ai_flags     = 0,
        .ai_family    = AF_UNSPEC,
        .ai_socktype  = SOCK_STREAM,
        .ai_protocol  = 0,
        .ai_addrlen   = 0,
        .ai_addr      = NULL,
        .ai_canonname = NULL,
        .ai_next      = NULL
    };
    struct addrinfo *res;
    int ret = getaddrinfo(host, port, &hints, &res);
    if (ret != 0) {
        fprintf(stderr, "getaddrinfo() failed: %s\n", gai_strerror(ret));
        return EXIT_FAILURE;
    }
    struct addrinfo *rp;
    for (rp = res; rp != NULL; rp = rp->ai_next) {
        sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sock == -1) {
            perror("socket() failed");
            continue;
        }
        if (connect(sock, rp->ai_addr, rp->ai_addrlen) == -1) {
            perror("connect() failed");
            close(sock);
            continue;
        }
        break;
    }
    freeaddrinfo(res);
    if (rp == NULL) {
        fprintf(stderr, "could not connect to %s:%s\n", host, port);
        return EXIT_FAILURE;
    }
    /* log in */
    if (pass)
        message("PASS %s\n", pass);
    message("NICK %s\n", nick);
    message("USER %s - - :%s\n", nick, nick);
    /* join channel */
    if (chan != NULL)
        message("JOIN %s\n", chan);
    /* print response */
    char buffer[4096];
    ssize_t nbyte;
loop:
    nbyte = recv(sock, buffer, 4095, 0);
    if (nbyte < 0) {
        fputs("recv() failed", stderr);
        return 1;
    } else if (nbyte == 0) {
        fputs("recv() failed: connection closed prematurely", stderr);
        return 1;
    }
    buffer[nbyte] = '[=11=]';
    printf("%s", buffer);
    goto loop;
    /* unreachable */
}

产出

:calcium.libera.chat NOTICE * :*** Checking Ident
:calcium.libera.chat NOTICE * :*** Looking up your hostname...
:calcium.libera.chat NOTICE * :*** Couldn't look up your hostname
:calcium.libera.chat NOTICE * :*** No Ident response
ERROR :Closing Link: 127.0.0.1 (Connection timed out)
recv() failed: connection closed prematurely

其他irc客户端进一步输出

:calcium.libera.chat 001 nick :Welcome to the Libera.Chat Internet Relay Chat Network nick
...

问题可能是错误处理?

例如,根据send(2)

On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set to indicate the error.

所以

} else if ((size_t)nsent != size) {
    fprintf(stderr,
            "send() failed: expected to send %lu bytes, sent %ld instead\n",
            size, nsent);
    exit(EXIT_FAILURE);
}

和它的 recv 对应物一样,似乎是多余的。 我是否正确处理了 vsnprintfmalloc

当您跟踪应用程序时(例如使用 strace),您将看到以下调用:

connect(3, {sa_family=AF_INET, sin_port=htons(6667), sin_addr=inet_addr("172.106.11.86")}, 16) = 0
sendto(3, "NICK nick\n[=10=]", 11, 0, NULL, 0) = 11
sendto(3, "USER nick - - :nick\n[=10=]", 21, 0, NULL, 0) = 21
sendto(3, "JOIN #libera\n[=10=]", 14, 0, NULL, 0) = 14

意思是当发送 NICK、USER 和 JOIN 时,这些字符串开始传输时末尾有一个额外的空字节,而另一端的服务器不喜欢这样。

这意味着在您的代码中 message() 方法是错误的,更具体地说是 size 变量的计算。如果我在 send() 调用之前使用递减的大小编译您的代码,则可以成功连接到 irc 服务器。

您处理 vsnprintf()malloc() 没问题。 send() 你没有正确处理。您的使用有两个问题:

  1. 您在传输中包含格式化字符串的空终止符。不要那样做,那不是 IRC 协议的一部分。

  2. 您没有考虑部分传输,因为 send() 可以 return 比请求的字节少,因此需要 send() 再次调用以发送任何未发送的字节。所以你需要在循环中调用 send() 。大于 0 但小于请求字节数的 return 值不是错误条件。唯一的错误条件是 return 值小于 0。

试试这个:

void
message(char *fmt, ...) {
    va_list ap;
    /* determine size */
    va_start(ap, fmt);
    int n = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed", stderr);
        exit(EXIT_FAILURE);
    }
    size_t size = n + 1;
    /* construct */
    char *msg = malloc(size);
    if (msg == NULL) {
        perror("malloc() failed");
        exit(EXIT_FAILURE);
    }
    va_start(ap, fmt);
    n = vsnprintf(msg, size, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed\n", stderr);
        free(msg);
        exit(EXIT_FAILURE);
    }
    /* send */
    char *curr = msg;
    --size; // don't sent null terminator!
    while (size > 0) {
        ssize_t nsent = send(sock, curr, size, 0);
        if (nsent < 0) {
            perror("send() failed");
            free(msg);
            exit(EXIT_FAILURE);
        }
        curr += nsent;
        size -= nsent;
    }
    free(msg);
}

就是说,您也不应该在 main() 中使用 goto 循环。请改用 whiledo..while 循环,例如:

int
main(void) {
    ...
    /* print response */
    char buffer[4096];
    int exitCode = 0;

    do {
        ssize_t nbyte = recv(sock, buffer, sizeof buffer, 0);
        if (nbyte < 0) { 
            perror("recv() failed");
            exitCode = 1;
        } else if (nbyte == 0) {
            fputs("connection closed by peer", stderr);
            exitCode = 1;
        } else {
            printf("%.*s", nbyte, buffer);
        }
    }
    while (exitCode == 0);

    close(sock);
    return exitCode;
}