【问题标题】:Issue with sending data over UDP using fread使用 fread 通过 UDP 发送数据的问题
【发布时间】:2019-07-13 08:47:06
【问题描述】:

我正在使用 UDP 实现“停止和运行”协议。我基本上使用 char 缓冲区来序列化我发送的任何数据,在需要的地方使用 ntoh 和 hton,并在我的数据到达另一端时反序列化。一切都很好,但是当我尝试发送文件时,我在新文件中得到随机的虚假字节。我将尝试总结这个问题的必要信息:

我的 char 缓冲区中的第一个字符表示正在发送的数据包的类型。我实现了一个小的 3 次握手来建立连接。服务器只需要一个特定的端口作为参数,客户端需要一个 ip、端口和文件名,然后从服务器请求这个文件。服务器响应文件的大小,然后客户端发送最终的 ACK 数据包,然后发送数据。我使用 fread() 一次发送数据 ~1kb,使用 1kb 缓冲区大小。一旦客户端获得这个数据包,它就会发送一个确认数据包,然后等待下一个数据包。以下是相关代码:

                else if (b == 'a')
                {
                        memcpy(&dacpkt.seqNum, buffer+sizeof(char), sizeof(int));
                        dacpkt.seqNum = ntohl(dacpkt.seqNum);           // convert from network to host data
                        datapkt.pktLen = 1017;
                        if (dacpkt.seqNum == datapkt.seqNum)
                        {
                                ++seq;
                                dacpkt.t = b;

                                datapkt.seqNum = seq;
                                fread(datapkt.data, datapkt.pktLen, 1, filereq);

                                memcpy(buffer, &datapkt.t, sizeof(char));                               // data packet type
                                off = sizeof(char);
                                datapkt.seqNum = htonl(datapkt.seqNum);
                                memcpy(buffer + off, &datapkt.seqNum, sizeof(int));     // data packet sequence#
                                off += sizeof(int);
                                datapkt.pktLen = htons(datapkt.pktLen);
                                memcpy(buffer + off, &datapkt.pktLen, sizeof(short));   // data packet size
                                off += sizeof(short);
                                memcpy(buffer + off, &datapkt.data, 1017);                              // data packet payload
                                n = sendto(sock, buffer, MAX, 0, (struct sockaddr*) &from, fromlen);
                                if (n < 0)
                                        error("sendto");
                        }

                        if(fread(datapkt.data, 1017, 1, filereq) != 1017)       // fread hit the end of the file
                        {
                                ++seq;
                                dacpkt.t = 'c';
                                dacpkt.seqNum = seq;
                                memcpy(buffer, &dacpkt.t, sizeof(char));                        // close packet
                                off = sizeof(char);
                                dacpkt.seqNum = htonl(dacpkt.seqNum);
                                memcpy(buffer + off, &dacpkt.seqNum, sizeof(int));      // close packet sequence#
                                n = sendto(sock, buffer, 5, 0, (struct sockaddr*) &from, fromlen);
                        }

                }

dacpkt 是我在从 recvfrom() 反序列化 char 缓冲区时填充的确认数据包,而 datapkt 是我构建的数据包,然后序列化并发送。我相信我的问题在于我对 fread 的使用。这是客户端的接收代码:

                        if (b == 'd')
                        {
                                //build the response packet and send
                                memset(buffer, 0, MAX);
                                //populate struct
                                dacpkt.t = 'a';
                                dacpkt.seqNum = htonl(sequence_num);

                                //copy to buffer
                                memcpy(buffer, &dacpkt.t, sizeof(char));
                                off = sizeof(char);
                                memcpy(buffer + off, &dacpkt.seqNum, sizeof(int));

                                //send the ACK
                                n = sendto(sock, buffer, MAX, 0, (struct sockaddr *) &server, length);
                                if (n < 0)
                                        error("sendto");

                                //receive as well before exiting while loop
                                memset(buffer, 0, MAX);
                                n = recvfrom(sock, buffer, MAX, 0, (struct sockaddr *) &from, &length);
                                if (n < 0)
                                        error("recvfrom");

                                datapkt.t = buffer[0];
                                off = sizeof(char);
                                memcpy(&datapkt.seqNum, buffer + off, sizeof(int));
                                datapkt.seqNum = ntohl(datapkt.seqNum);
                                off += sizeof(int);
                                memcpy(&datapkt.pktLen, buffer + off, sizeof(short));
                                datapkt.pktLen = ntohl(datapkt.pktLen);
                                off += sizeof(short);
                                strcat(datapkt.data, buffer + off);

                                //ensures we are receiving the next packet
                                if (datapkt.seqNum = (sequence_num + 1))
                                {
                                        n = fputs(datapkt.data, newfile); //we need buffer to be the exact size
                                        if (n < 0)
                                                error("writing to file");

                                        sequence_num = datapkt.seqNum;
                                }

                        }

                        fclose(newfile);
                }
                //post-receiving the file!
                memset(buffer, 0, MAX);
                dacpkt.t = 'c';
                dacpkt.seqNum = htonl(sequence_num);
                memcpy(buffer, &dacpkt.t, sizeof(char));
                memcpy(buffer + 1, &dacpkt.seqNum, sizeof(int));
                //send the ACK
                n = sendto(sock, buffer, MAX, 0, (struct sockaddr *) &server, length);
                if (n < 0)
                        error("sendto");

如果我提供的信息有点模棱两可,我很抱歉,但我相当肯定我的问题在于我对文件的读/写。例如,我运行程序尝试传输每行包含字母字符 (a-z) 的文本文件,共 600 行。原始文件为 16200 字节,而发送数据创建的新文件只有 3069 字节。新文件只有 113 行,随机字节出现如下:

 36 abcdefghijklmnopqrstuvwxyz
 37 abcdefghijklmnopqrstuvwxyz
 38 abcdefghijklmnopqr8ze$ü^?abcdefghijklmnopqrstuvwxyz
 39 abcdefghijklmnopqrstuvwxyz

值得注意的是,在文件末尾:

111 abcdefghijklmnopqrstuvwxyz
112 abcdefghijklmnopqrstuvwxyz
113 abcdefghi8ze$ü^?

关于如何判断何时结束文件传输,我想我不明白 fread() 的工作原理。如果有人对我如何更好地实现这一点有想法,我会全力以赴。我要做的就是一次发送一个 kb 数据包中的数据,直到到达文件末尾,此时我发送这个最终的部分数据包,然后发送一个信号以关闭客户端。

【问题讨论】:

    标签: c sockets fread


    【解决方案1】:

    两件事立即发生。

    • fread 返回它读取的项目数fread(datapkt.data, 1017, 1, filereq) 尝试读取 一个 大小为 1017 的项目。此调用不可能返回 1017。它可能只返回 0 或 1。测试

          if (fread(....) != 1017)
      

      总是失败。但是,已经读取了 1017 个字节,并且由于测试失败,被丢弃。这就解释了巨大的数据丢失。

      交换参数的顺序,看看有多少1字节的项目被读取了:

          size_t bytes = fread(datapkt.data, 1, 1017, filereq);
      

      并使用此bytes 作为有效负载的大小。 bytes &lt; 1017 可能表示文件结束。当然,请致电feofferror

    • 服务器流程:

          if (dacpkt.seqNum == datapkt.seqNum) {
              ....
              fread(...);
              ....
          }
          if (fread(....) != 1017) {
              ....
          }
      

      意味着有一次 两次 调用 fread 一次 seqNum 匹配,如果不匹配,则调用一次。我无法真正遵循数据流,但确实感觉很奇怪。在收到确认之前,服务器不应继续发送新的seqNum

      在任何情况下,都不能盲目发送MAX字节。第一个fread 还必须检查它获得了多少字节,并相应地修复memcpysendto 中的大小。


    要 _detect EOF 并同时读取数据`,尝试类似

        size_t bytes = fread(datapkt.data, 1, 1017, filereq);
        if (bytes == 0 && feof(filereq)) {
            handle_end_of_file();
        } else {
            memcpy(memcpy(buffer + off, &datapkt.data, bytes);
            send_the_packet();
        }
    

    【讨论】:

    • 我明白你对 1017 问题的看法。我的(错误)印象是 fread 返回读取的字节。至于另一个问题,我对那条线(如果是恐惧的话)有一种潜在的不好的感觉。我试图用英语做的是在 1024 大小的字符缓冲区中发送一个 1017 字节 + 一些标头信息的数据包。 SeqNum 是迭代的服务器端,因为我计划实现一个检测丢包的计时器。基本上,如果客户端正在等待 seNum 5,它不应接受 4(已经拥有)或 6(跳过一个,数据包被丢弃)。我的问题:如何只用一个 fread 调用来检测 EOF?
    • 详细说明^我的意思是,检测EOF并同时读取数据,而不是使用两次对fileread()的调用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多