【问题标题】:TCP socket read/write freezes serverTCP 套接字读/写冻结服务器
【发布时间】:2024-07-27 12:20:01
【问题描述】:

我想在 C 中使用 TCP 套接字来实现协议,它的工作方式是这样的:

  1. 客户端连接到服务器并发送它想要的文件名 下载
  2. 服务器读取该值并检查它是否是有效的文件名(它是否存在于服务器上)+ 向客户端发送ACCEPTFAILURE 状态
  3. 客户端读取该状态并准备自己下载 + 将READY 状态发送到服务器
  4. 服务器发送文件并关闭连接

服务器代码

char response[128];
int bytes_read;
while ((bytes_read = read(info.socket, response, 128)) > 0) {}

if (valid_request(files, files_count, response)) {
    write(info.socket, MC_ACCEPT, 4);
} else {
    write(info.socket, MC_FAILURE, 4);
}

客户端代码

int w_status = write(sck, requested_file, strlen(requested_file));
if (w_status < 0) {
    fprintf(stderr, "Error writing to socket. Status: %d", w_status);
    exit(1);
}

char status[4];
while ((resp = read(sck, status, 4)) > 0) {}

if (strcmp(status, MC_ACCEPT) == 0) {
    printf("ACCEPTED!\n");
} else if (strcmp(status, MC_ACCEPT) == 0) {
    printf("FAILURE\n");
} else {
    printf("DONT KNOW\n");
}

close(sck);

问题是服务器在read() 部分冻结了自己。看起来客户端发送文件名并等待服务器响应(带有状态),但服务器在read() 冻结。

我是否以某种方式阻塞了 TCP 套接字?我的推理有什么问题?

【问题讨论】:

  • @Barmar 我的错误,我的真实代码中没有它
  • 您是否尝试过抓包以查看消息是否正在发送到服务器?
  • @Barmar 当我第一次运行服务器时有趣的是,然后客户端挂起,但之后我用 CTRL + C 杀死客户端进程,服务器似乎得到了正确的消息,但直到客户端进程还活着
  • 您的服务器代码除了等待请求什么都不做。您是否考虑过发送要求的文件?
  • @EJP 我在此处发布的此代码不完整。我稍后发送文件

标签: c tcp


【解决方案1】:

在服务器端:

char response[128];
int bytes_read;
while ((bytes_read = read(info.socket, response, 128)) > 0) {}

您尝试在多个 read 调用中读取这 128 个字符。但它会永远阻塞,直到客户端关闭套接字(它是 TCP 连接的,除非对等端关闭连接,否则总会有一些东西要读取)。

如果数据以超过 1 个块的形式到达,则您的代码不正确,因为第一个块将被下一个块覆盖,依此类推。您必须更改缓冲区的偏移量,并且不要每次都尝试读取 128 个字节,否则您会卡住。

int bytes_read = 0;
while (bytes_read < 128)
{
  int currently_read = read(info.socket, response + currently_read, 128-bytes_read);
  bytes_read += currently_read;
}

在客户端,同样的问题:

您似乎在等待 4 个字符。

您尝试读取第一个 read 中的这 4 个字符。但是您不检查是否实际读取了 4 个字符(丢弃返回代码)。

之后,您使用循环读取,直到获得 0 个字节。但是由于连接没有结束,你就被困在那里了。

您想要的是在执行其他操作之前准确读取 4 个字节。

并增加您的缓冲区大小并以空值终止您的字符串,否则strcmp 将失败。

char status[5];
status[4] = '\0';
int nb_read = 0;
while (nb_read < 4)
{
  int currently_read = read(sck, status + nb_read, 4-nb_read);
  nb_read += currently_read;
}

【讨论】:

  • 您的解释是理解我的问题的关键。谢谢。
【解决方案2】:

问题是您没有在 while 循环内处理请求:

while ((bytes_read = read(info.socket, response, 128)) > 0) {}

这会一直循环直到read() 返回0,这发生在客户端关闭连接或收到错误并返回-1 时。在它从客户端读取请求后,它会返回并再次调用read()。由于客户端没有发送任何其他内容,因此阻塞。

这就是为什么杀死客户端会使它解开。这将关闭连接,因此它会获得 EOF 并且 read() 返回 0

你需要在循环内处理输入:

while ((bytes_read = read(info.socket, response, sizeof response -1)) > 0) {
    response[bytes_read] = '\0'; // add string null terminator
    if (valid_request(files, files_count, response)) {
        write(info.socket, MC_ACCEPT, 4);
    } else {
        write(info.socket, MC_FAILURE, 4);
    }
}

【讨论】:

  • 但不能read(info.socket, response, sizeof response -1)返回小于sizeof response -1
  • 是的,可以。他实际上需要设计协议,以便服务器可以通过某种方式告诉请求在哪里结束,或者使用长度前缀或分隔符,然后继续阅读,直到他得到整个消息。如果没有这个,他能做的最好的事情就是希望read() 会返回整个消息,这很有可能。
最近更新 更多