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
对应物一样,似乎是多余的。
我是否正确处理了 vsnprintf
和 malloc
?
当您跟踪应用程序时(例如使用 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()
你没有正确处理。您的使用有两个问题:
您在传输中包含格式化字符串的空终止符。不要那样做,那不是 IRC 协议的一部分。
您没有考虑部分传输,因为 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
循环。请改用 while
或 do..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;
}
#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
对应物一样,似乎是多余的。
我是否正确处理了 vsnprintf
和 malloc
?
当您跟踪应用程序时(例如使用 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()
你没有正确处理。您的使用有两个问题:
您在传输中包含格式化字符串的空终止符。不要那样做,那不是 IRC 协议的一部分。
您没有考虑部分传输,因为
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
循环。请改用 while
或 do..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;
}