【问题标题】:Printf makes program work in C, htonl and ntohl not working?Printf 使程序在 C 中工作,htonl 和 ntohl 不工作?
【发布时间】:2017-02-05 03:54:05
【问题描述】:

这是针对Linux系统的,用C语言编写的。它涉及网络编程。这是一个文件传输程序。

我一直遇到这个问题,这段代码无法预测。它要么完全成功,要么客户端中的 while 循环永远不会结束。我发现这是因为 fileLength 变量有时会是一个巨大的(负或正)值,我认为这是由于 ntohl 犯了一些错误。当我输入一个打印语句时,它似乎工作得很好,没有错误。

这是客户端代码:

        //...here includes relevant header files

        int main (int argc, char *argv[]) {
            //socket file descriptor
            int sockfd;

            if (argc != 2) {
                fprintf (stderr, "usage: client hostname\n");
                exit(1);
            }

            //...creates socket file descriptor, connects to server


            //create buffer for filename
            char name[256];
            //recieve filename into name buffer, bytes recieved stored in numbytes
            if((numbytes = recv (sockfd, name, 255 * sizeof (char), 0)) == -1) {
                perror ("recv");
                exit(1);
            }
            //Null terminator after the filename
            name[numbytes] = '\0';
            //length of the file to recieve from server
            long fl;
            memset(&fl, 0, sizeof fl);
            //recieve filelength from server
            if((numbytes = recv (sockfd, &fl, sizeof(long), 0)) == -1) {
                perror ("recv");
                exit(1);
            }

            //convert filelength to host format
            long fileLength = ntohl(fl);


            //check to make sure file does not exist, so that the application will not overwrite exisitng files
            if (fopen (name, "r") != NULL) {
                fprintf (stderr, "file already present in client directory\n");
                exit(1);
            }
            //open file called name in write mode
            FILE *filefd = fopen (name, "wb");
            //variable stating amount of data recieved
            long bytesTransferred = 0;
            //Until the file is recieved, keep recieving
            while (bytesTransferred < fileLength) {
                printf("transferred: %d\ntotal: %d\n", bytesTransferred, fileLength);
                //set counter at beginning of unwritten segment
                fseek(filefd, bytesTransferred, SEEK_SET);
                //buffer of 256 bytes; 1 byte for byte-length of segment, 255 bytes of data
                char buf[256];
                //recieve segment from server
                if ((numbytes = recv (sockfd, buf, sizeof buf, 0)) == -1) {
                    perror ("recv");
                    exit(1);
                }

                //first byte of buffer, stating number of bytes of data in recieved segment
                //converting from char to short requires adding 128, since the char ranges from -128 to 127
                short bufLength = buf[0] + 128;

                //write buffer into file, starting after the first byte of the buffer
                fwrite (buf + 1, 1, bufLength * sizeof (char), filefd);
                //add number of bytes of data recieved to bytesTransferred
                bytesTransferred += bufLength;

            }
            fclose (filefd);
            close (sockfd);

            return 0;
        }

这是服务器代码:

        //...here includes relevant header files

        int main (int argc, char *argv[]) {
            if (argc != 2) {
                fprintf (stderr, "usage: server filename\n");
                exit(1);
            }
            //socket file descriptor, file descriptor for specific client connections
            int sockfd, new_fd;


            //...get socket file descriptor for sockfd, bind sockfd to predetermined port, listen for incoming connections



            //...reaps zombie processes


            printf("awaiting connections...\n");

            while(1) {
                //...accepts any incoming connections, gets file descriptor and assigns to new_fd

                if (!fork()) {
                    //close socket file discriptor, only need file descriptor for specific client connection
                    close (sockfd);
                    //open a file for reading
                    FILE *filefd = fopen (argv[1], "rb");
                    //send filename to client
                    if (send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0) == -1)
                    { perror ("send"); }
                    //put counter at end of selected file, and  find length
                    fseek (filefd, 0, SEEK_END);
                    long fileLength = ftell (filefd);
                    //convert length to network form and send it to client

                    long fl = htonl(fileLength);
                    //Are we sure this is sending all the bytes??? TEST
                    if (send (new_fd, &fl, sizeof fl, 0) == -1)
                    { perror ("send"); }
                    //variable stating amount of data unsent
                    long len = fileLength;
                    //Until file is sent, keep sending
                    while(len > 0) {
                        printf("remaining: %d\ntotal: %d\n", len, fileLength);
                        //set counter at beginning of unread segment
                        fseek (filefd, fileLength - len, SEEK_SET);
                        //length of the segment; 255 unless last segment
                        short bufLength;
                        if (len > 255) {
                            len -= 255;
                            bufLength = 255;
                        } else {
                            bufLength = len;
                            len = 0;
                        }
                        //buffer of 256 bytes; 1 byte for byte-length of segment, 255 bytes of data
                        char buf[256];
                        //Set first byte of buffer as the length of the segment
                        //converting short to char requires subtracting 128
                        buf[0] = bufLength - 128;
                        //read file into the buffer starting after the first byte of the buffer
                        fread(buf + 1, 1, bufLength * sizeof(char), filefd);
                        //Send data too client
                        if (send (new_fd, buf, sizeof buf, 0) == -1)
                        { perror ("send"); }
                    }
                    fclose (filefd);
                    close (new_fd);
                    exit (0);
                }
                close (new_fd);
            }

            return 0;
        }

注意:我已经稍微简化了代码,希望它更清晰。 任何以 //... 开头的东西都代表一堆代码

【问题讨论】:

  • 我没有看到任何同步机制。对等点交换原始二进制数据,而没有任何指示该数据是什么。所以你的name 的一部分可以很容易地解释为长度。你需要定义一个protocol
  • numbytes, sin_size 类型未知,因为未在帖子中声明。 AKAIK,他们有一种导致问题的类型。最好发布他们的声明。示例socklen_t sin_size = sizeof their_addr;
  • 在这种情况下,这将如何导致问题? (抱歉,我是新手。)我告诉它以正确的顺序以正确的大小发送和接收数据,不是吗?
  • recv 正在接收当前可用的尽可能多的数据。想想如果在调用recv 时只有第一条消息的一部分可用会发生什么。
  • 随着post change,当send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0)被调用时new_fd的值是多少?它似乎未初始化。

标签: c linux network-programming printf endianness


【解决方案1】:

您似乎假设每个send() 要么传输指定的全部字节数,要么出错,并且每个recv() 都将与另一侧的recv() 完美配对,这样@987654323 @ 准确接收 send() 发送的字节数(或错误输出),不多也不少。这些不是安全的假设。

您没有显示用于设置网络连接的代码。如果您使用的是基于数据报的协议(即 UDP),那么您更有可能获得与您期望的匹配的发送/接收边界,但您需要考虑数据包丢失或损坏的可能性。如果您使用的是基于流的协议(即 TCP),那么您不必太担心数据丢失或损坏,但您完全没有理由期待边界匹配行为。

你至少需要三样东西:

  • 网络层之上的应用层协议。您已经掌握了其中的一部分,例如您如何首先传输文件长度以告知客户需要多少内容,但是您需要对所有传输的非预定、固定长度的数据执行类似操作。或者,发明另一种方式来传达数据边界。

  • 每个旨在传输多个字节的 send() / write() 都必须在循环中执行,以适应被分成多个部分的传输。返回值告诉您传输了多少请求的字节(或至少有多少已移交给网络堆栈),如果少于请求,您必须循环返回以尝试传输其余字节。

  • 每个旨在传输多个字节的recv() / read() 都必须在循环中执行,以适应被分成多个部分的传输。我建议按照send() 中描述的相同方式构建它,但您也可以选择接收数据,直到看到预先安排的分隔符。然而,基于分隔符的方法更复杂,因为它需要在接收端进行额外的缓冲。

如果没有这些措施,您的服务器和客户端很容易不同步。其中可能的结果是客户端将文件名的一部分或文件内容的一部分解释为文件长度。

【讨论】:

  • +1 知道文件长度损坏的合理原因是一种解脱。谢谢你的好组织。
  • 等等,我想我应该提到文件名永远不会被破坏,只是文件长度。为什么会这样?
【解决方案2】:

即使您从该代码中删除了它,我也会做出有根据的猜测,并假设您在这里使用的是 TCP 或其他一些流协议。这意味着服务器发送的数据是字节流,recv 调用与它们通过send 调用获得的数据量不对应。

您的第一次recv 调用只获取一个字节的数据同样合法,因为它获取文件名、文件大小和文件的一半。

你说

当我输入打印语句时,

但你没有说在哪里。我将在这里进行另一个有根据的猜测,并猜测您是在发送文件长度之前在服务器上完成的。这恰好使事情发生了很大的变化,以至于在连接上发送的数据量只是意外地与您在客户端上的期望相匹配。

您需要定义一个协议。也许从文件名的长度开始,然后是文件名,然后是文件的长度。或者总是为文件名发送 256 个字节,不管它有多长。或者将文件名作为以 0 结尾的字符串发送并尝试从中找出数据。但是你永远不能仅仅因为你用 X 个字节调用 send 就假设 recv 调用会得到 X 个字节。

【讨论】:

  • 是的,你是对的,它是 TCP。您能否详细说明/简化您所说的使用流协议“意味着服务器发送的数据是字节流,并且 recv 调用与它们通过 send 调用获得的数据量不对应”。此外,我在收到文件后并在通过 ntohl() 后收到了 prinf 语句。但我认为它不应该改变你所说的。
【解决方案3】:

我相信这个问题实际上是你和其他人所说的一切的复合体。在服务器代码中,您可以像这样发送文件的名称:

send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0);

并像这样在客户端接收它:

recv (sockfd, name, 255 * sizeof (char), 0);

当文件名长度小于 255 时,这将导致问题。由于 TCP 是流协议(如 @Art 所述),sends 和 recvs 之间没有真正的界限,这可能会导致您在意想不到的地方收到数据。

我的建议是先发送文件名的长度,例如:

// server
long namelen = htonl(strlen(argv[1]));
send (new_fd, &namelen, 4, 0);
send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0);
// client
long namelen;
recv (sockfd, &namelen, 4, 0);
namelen = ntohl(namelen);
recv (sockfd, name, namelen * sizeof (char), 0);

这将确保您始终知道您的文件名的确切长度,并确保您不会从文件中间的某个位置意外读取文件长度(这是我预计目前正在发生的情况)。

编辑。

另外,发送大小数字时要小心。如果您对它们使用sizeof 调用,您可能会发送和接收不同的大小。这就是为什么我在 sendrecv 中对名称长度的大小进行硬编码,这样两边都不会混淆。

【讨论】:

  • 我认为你可能是对的,我可能误解了我所看到的(可能没有被卡在 while 循环中)。也许客户端的第一个recv(对于文件名)有时会同时获得文件名和文件长度的字节。所以有时我运行它,它会很好,而其他时候它会在客户端的第二个 recv 上阻塞。
【解决方案4】:

好吧,经过一些测试,我发现导致问题的问题确实与 htonl() 有关,尽管我在开始时仍然错误地读取了数据。并不是 htonl() 根本不起作用,而是我没有意识到“长”的长度取决于系统架构(感谢@tofro)。也就是说,在 32 位和 64 位操作系统上,一个“长”整数的长度分别为 4 个字节和 8 个字节。以及用于 4 字节整数的 htonl() 函数(来自 arpa/inet.h)。我使用的是 64 位操作系统,这解释了为什么该值被捏造。我通过使用 int32_t 变量(来自 stdint.h)来存储文件长度来解决这个问题。所以这种情况下的主要问题不是它变得不同步(我认为)。但至于每个人对开发实际协议的建议,我想我知道你的意思,我绝对理解它为什么重要,我目前正在努力实现它。谢谢大家的帮助。

编辑:现在已经好几年了,我知道的更多了,我知道这种解释没有意义。 long 比我预期的要大(8 个字节而不是 4 个)会导致的所有结果是正在进行一些隐式转换。我在原始代码中使用了sizeof(long),而不是对其进行硬编码以假设 4 个字节,因此我的特定(错误)假设不应该产生我看到的错误。

问题几乎可以肯定是其他人所说的:对recv 的一次调用并未获得代表文件长度的所有字节。当时我怀疑这是我看到的行为的真正原因,因为我发送的文件名(任意长度)从未部分发送(即客户端总是创建一个正确文件名的文件)。只有文件长度搞砸了。我当时的假设是 recv 主要尊重消息边界,虽然 recv 可能 只发送部分数据,但更有可能它正在发送所有数据并且还有另一个我的代码中的错误。我现在知道这根本不是真的,TCP doesn't care.

我有点好奇为什么我也没有看到其他意外行为(例如接收端的文件名错误),我想进一步调查,但尽管设法找到了文件,我现在似乎无法重现该问题。我想我永远不会知道,但至少我了解这里的主要问题。

【讨论】:

  • 确实,long 的大小——以及所有整数类型——可能因系统而异。 C 为这些建立最小逻辑大小,而不是精确大小。但是请注意,没有将这些类型的大小与系统架构相关联的规则。 32 位机器上的 C 实现可以提供 64 位 ints,64 位机器上的实现可以提供 32 位 longs。跨度>
猜你喜欢
  • 2016-08-23
  • 1970-01-01
  • 2014-07-20
  • 2019-07-23
  • 1970-01-01
  • 2013-03-29
  • 1970-01-01
  • 2019-01-12
  • 2019-07-01
相关资源
最近更新 更多