【问题标题】:Client side program won't move ahead客户端程序不会继续前进
【发布时间】:2020-11-25 17:47:04
【问题描述】:

我正在编写一个程序,客户端尝试从服务器下载文件。

以下是客户端的代码。

FILE * file_to_download;
file_to_download = fopen("CLIENT_file_downloaded", "ab");  

long int total_bytes = 0;
int bytes_received = 0;
char received_buffer[2048];

printf(">>> Downloading file...\n");
while ((bytes_received = read(connector, received_buffer, sizeof received_buffer)) > 0) { // keep receiving until data stops sending
    printf("=");
    total_bytes += bytes_received;
    fwrite(received_buffer, 1, bytes_received, file_to_download);
}

if (bytes_received < 0) {
    printf("\n>>> Read error\n");
    exit(1);
}

printf("\n>>> File downloaded (Bytes received: %ld)\n\n", total_bytes);

当我立即关闭套接字连接时,这非常有效,但是,如果我将它打开以执行其他一些功能(比如向服务器发送消息),它会在“>>>下载文件.. .”,但是,我可以在文件夹中看到下载的文件。此外,一旦我终止服务器端程序,它会打印出其他所有内容。

从其他 SO 线程来看,我认为这与套接字“阻塞”有关。

  1. 但是为什么我的文件被下载了(我可以在文件夹中看到它)? fwrite 函数负责这个在 while 循环内,但如果它在 while 循环内,为什么它不打印任何东西?
  2. 如何让服务器告诉客户端它已完成向其发送数据以及如何让客户端程序继续前进?

【问题讨论】:

  • 我认为缓冲区不知道何时停止等待,您最好询问文件大小,然后准确读取我认为的那么多字节
  • @WARhead 就是这样。 OP 拥有的唯一协议是连接打开/关闭,因此,如果没有它,recv() 调用将永远不会返回 0,while 循环将永远不会退出:(
  • WARhead 建议先发送文件的长度,这可能是解决问题的最简单方法。确保以具有已知字节序的固定宽度整数发送长度,因为如果您只使用int,您永远不知道接收器是否有 16 位、32 位、64 位或任何大小的int 或哪个字节订单正在使用中。
  • 首先发送文件大小就是这样一种协议,但请记住,这并不像乍看起来那么简单。你的整个 TCP 系统应该被设计/编写,即使每个读取数据的 recv() 调用只读取一个字节并返回 1,它仍然可以正常工作。
  • @Shreya 您应该研究现有的文件传输协议,如 FTP 和 HTTP。然后,如果您选择不使用现有协议,我强烈建议您记录您将要实现的协议,即使它只有几段。

标签: c network-programming


【解决方案1】:

您已经获得了 很多 个 cmets,但我会尝试将至少一些零碎的内容总结到一个地方。

首先,问题是:你现在正在做的事情,基本上你调用read可以返回三种方式:

  1. 它返回一个严格的正值(即至少 1),告诉您读取的字节数。
  2. 返回 0 表示套接字已关闭。
  3. 返回负值表示错误。

但是没有定义的方式让它返回并告诉你:“套接字仍然打开,没有错误,但没有等待读取的数据。”

因此,正如其他人所说,如果您要传输文件(或其他一些已定义的数据块),您通常需要在 TCP 之上定义一些应用程序级协议来支持它。最明显的起点是您首先发送文件的大小(通常作为单个固定大小的块,例如 4 或 8 个字节),然后是那么多字节的数据。

如果你这样做,你可以定义至少可以工作的东西。它可能会遗漏各种可能的错误,但至少如果一切正常,它就可以了。

除此之外的下一步通常是添加某种校验和/CRC 之类的东西,因此当您认为传输完成时,您可以验证数据以至少合理地保证它有效(即数据您收到了与发送的内容相匹配的内容)。

另一个一般需要考虑的方向是您的阅读方式。这里有几个选择。一种是避免调用read,直到您确定它可以/将会成功。如果您一次只处理一个(或几个)套接字,您可以调用select,它会告诉您您的套接字何时准备好读取,因此发出读取保证会很快成功。它的读数可能比您要求的,但它会返回而不是无限期地等待数据。如果您必须处理大量套接字,您可能更愿意查找 epoll,它的作用大致相同,但在您必须处理许多句柄时会减少开销。

解决此问题的另一种可能方法是为您的套接字设置O_NONBLOCK 选项。在这种情况下,当没有数据可用时尝试读取将被视为错误,因此它将立即返回错误EAGAINEWOUDLBLOCK(您必须做好准备)。这为您提供了一种相当简单的方法,至少在您没有更多可用数据的情况下继续进行,但对有效传输数据方面的任何其他困难没有任何作用。

正如其他人所指出的,有很多现有的协议可以做这样的事情并重新发明它可能不是您时间的最佳利用方式。另一方面,某些协议可能会有些痛苦(例如,ftp 的正常模式要求您打开/使用两个单独的套接字)。其他的非常复杂,您可能不想尝试自己实现它们,但是很难找到能够很好地支持它们的库。

就我个人而言,我发现 websockets 在很多这样的任务中工作得相当合理。它们包括框架(因此作为单个 websocket 写入发送的内容将通过单个 websocket 读取接收)。他们还使用 CRC 进行错误检查。因此,对于很多这样的情况,它或多或少会自动处理大部分细节。它还包括(并且在大多数情况下使用)他们所谓的 ping/pong 协议来检测连接丢失的速度比 TCP 通常单独执行的速度要快得多。

但如上所述,有 很多 替代方案,其中一些专为传输文件而设计(因此您收到的不仅仅是文件的内容,而是诸如名称和附加到该内容的其他元数据)。

【讨论】:

    猜你喜欢
    • 2012-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-05
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多