【问题标题】:IRC client does not print/receive full responseIRC 客户端不打印/接收完整响应
【发布时间】:2026-01-16 14:40:01
【问题描述】:
#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] = '\0';
    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)

成功时,这些调用会返回发送的字节数。出错时返回 -1,并设置 errno 以指示错误。

所以

} 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 是否正确?

【问题讨论】:

  • 每个帖子一个问题。
  • Checking Ident ... Looking up your hostname ... Couldn't look up your hostname ... No Ident response - 您的代码中没有 IDENT 服务器。大多数 IRC 服务器会在端口 113 上连接回客户端的 IP 并查询其身份以证明客户端就是它声称的身份。因此,您需要在代码中实现一个 IDENT 服务器,或者至少让一个 IDENT 服务器与您的客户端并行运行。
  • @RemyLebeau 但这不是问题,因为其他 irc 客户端在输出其他有用的东西之前也会给出相同的四行。
  • 另外,不,您没有正确处理send()。如果它返回 > 0,但小于请求的字节数,则必须再次调用 send() 以完成剩余未发送字节的发送。返回值与请求的大小不同不是错误。唯一的错误条件是返回值 -1。您需要循环调用send(),直到发送完所有数据。
  • loop: ... goto loop;?哇。好几年没见过像 THAT 这样的代码了 ;)

标签: c sockets irc


【解决方案1】:

当您跟踪应用程序(例如使用 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\0", 11, 0, NULL, 0) = 11
sendto(3, "USER nick - - :nick\n\0", 21, 0, NULL, 0) = 21
sendto(3, "JOIN #libera\n\0", 14, 0, NULL, 0) = 14

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

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

【讨论】:

  • 哇,我自己永远也想不通。顺便说一句,当我做 strace ./myprog 时,那些 sendtos 看起来不同,更复杂
【解决方案2】:

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

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

  2. 您没有考虑部分传输,因为send() 可以返回比请求更少的字节,因此需要再次调用 send() 以发送任何未发送的字节。所以你需要循环调用send()。大于 0 但小于请求字节数的返回值不是错误条件。唯一的错误条件是返回值小于 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;
}

【讨论】:

  • 非常感谢!
最近更新 更多