"非法谋取"带插座时误差非空读缓冲流 [英] "Illegal seek" error when working with socket streams with non-empty read buffers

查看:127
本文介绍了"非法谋取"带插座时误差非空读缓冲流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前使用&LT上的的Linux x86_64的写一个服务器应用程序
接受通过)的连接接受(后,我用 fdopen()来检索插座包装成 FILE * 流。

I'm currently writing a server application on Linux x86_64 using <sys/socket.h>. After accepting a connection via accept(), I use fdopen() to wrap the retrieved socket into a FILE* stream.

写入和读取,即 FILE * 流通常工作得很好,但插座只要我写它成为unsusable,同时它有一个非空读取缓冲区。

Writing to, and reading from, that FILE* stream usually works quite well, but the socket becomes unsusable as soon as I write to it while it has a non-empty read buffer.

有关演示的目的,我已经写了一些code,它监听连接,然后使用龟etc()。如果线路太长入缓冲区,它不是完全读取,但下一次迭代过程中,而不是阅读。

For demonstration purposes, I've written some code that listens for a connection, then reads the input, line by line, into a read buffer using fgetc(). If the line is too long to fit into the buffer, it's not completely read, but instead read during the next iteration.

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

FILE* listen_on_port(unsigned short port) {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in name;
        name.sin_family = AF_INET;
        name.sin_port = htons(port);
        name.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0)
                perror("bind failed");
        listen(sock, 5);
        int newsock = accept(sock, 0, 0);
        return fdopen(newsock, "r+");
}

int main(int argc, char** argv) {
        int bufsize = 8;
        char buf[9];
        buf[8] = 0; //ensure null termination

        int data;
        int size;

        //listen on the port specified in argv[1]
        FILE* sock = listen_on_port(atoi(argv[1]));
        puts("New connection incoming");

        while(1) {
                //read a single line
                for(size = 0; size < bufsize; size++) {
                        data = fgetc(sock);
                        if(data == EOF)
                                break;
                        if(data == '\n') {
                                buf[size] = 0;
                                break;
                        }
                        buf[size] = (char) data;
                }

                //check if the read failed due to an EOF
                if(data == EOF) {
                        perror("EOF: Connection reset by peer");
                        break;
                } else {
                        printf("Input line: '%s'\n", buf);
                }

                //try to write ack
                if(fputs("ack\n", sock) == EOF)
                        perror("sending 'ack' failed"); 

                //try to flush
                if(fflush(sock) == EOF)
                        perror("fflush failed");        
        }

        puts("Connection closed");
}

在code应GCC编译没有任何特殊的参数。用该端口号作为参数和使用netcat的运行它在本地建立连接。

The code should compile in gcc without any special parameters. Run it with the port number as argument and use netcat to connect to it locally.

现在,如果你尝试发送的短于8个字符的字符串,这将完美运行。
但是,如果你发送一个包含超过10个字符的字符串,程序将失败。
此示例输入:

Now, if you try sending strings that are shorter than 8 characters, this will run flawlessly. But if you send a string containing more than 10 characters, the program will fail. This sample input:

ab
cd
abcdefghij

将创建此输出:

New connection incoming
Input line: 'ab'
Input line: 'cd'
Input line: 'abcdefgh'
fflush failed: Illegal seek
EOF: Connection reset by peer: Illegal seek
Connection closed

正如你所见,(正确地)只ABCDEFGH的前8个字符读取,但是当程序试图发送ACK字符串(客户端从不receves),然后刷新输出缓冲区,我们收到非法谋取的错误,并且接下来将调用fgetc()返回EOF。

As you see, (rightly) only the first 8 characters of abcdefgh are read, but when the program tries to send the 'ack' string (which the client never receves), and then flush the output buffer, we receive an Illegal seek error, and the next call to fgetc() returns EOF.

如果在 fflush()部分被注释掉,同样的错误仍然存​​在,但

If the fflush() part is commented out, the same error still occurs, but the

fflush failed: Illegal seek

线从服务器输出丢失了。

line is missing from the server output.

如果在的fputs(ACK)部分被注释掉,似乎一切都按计划工作,但PERROR()从gdb的人工呼叫仍然会发送非法寻求错误。

If the fputs(ack) part is commented out, everything seems to work as intended, but a perror() manually called from gdb still reports an 'Illegal seek' error.

如果这两个的fputs(ACK) fflush()都注释掉,一切的确实工作按预期。

If both fputs(ack) and fflush() are commented out, everything does work as intended.

不幸的是,我一直没能找到任何好的文件,也没有在这个问题上的任何互联网的讨论,所以你的帮助会很大AP preciated。

Unfortunately, I've not been able to find any good documentation, nor any Internet discussions on this problem, so your help would be greatly appreciated.

修改

解决方案我终于尘埃落定了是的的使用 fdopen() FILE * ,因为似乎有一个套接字fd转换成 FILE * 可以在 R + 模式。
相反,我直接合作的套接字fd,写我自己更换code为的fputs fprintf中

The solution i finally settled for is to not use fdopen() and FILE*, since there seems to be no clean way of converting a socket fd into a FILE* that can reliably used in r+ mode. Instead, I directly worked on the socket fd, writing my own replacement code for fputs and fprintf.

如果有人需要它,这里是code

推荐答案

显然,R +(读/写)模式不会在此实现套接字上工作,无疑是因为底层code假定它必须做一个寻求阅读和写作之间切换。这是stdio流一般情况下(即你必须做某种同步操作),因为早在昏暗的时间,实际的标准输入输出的实现只有每个流一个计数器,这是任何一个字符数的反左通过 GETC 宏从流缓冲区中读取(读取模式下),或者可以安全通过 putc将宏(以写入模式)。为了这单一柜台重新设置,你必须做一个寻求式操作。

Clearly "r+" (read/write) mode does not work on sockets in this implementation, no doubt because the underlying code assumes that it must do a seek to switch between reading and writing. This is the general case with stdio streams (that you must do some kind of synchronizing operation), because back in the Dim Time, actual stdio implementations had only a single counter per stream, and it was either a counter of "number of characters left to read from stream buffer via getc macro" (in read mode) or "number of characters that can safely be written to stream buffer via putc macro (in write mode). To that that single counter re-set, you had to do a seek-type operation.

寻求不允许在管道和套接字(因为文件偏移量是没有意义的存在)。

Seeks are not allowed on pipes and sockets (since "file offset" is not meaningful there).

一个解决方案是完全不带包装STDIO插座。另外,可能更容易/你的目的好,是,没有一包,但的两个的标准输入输出流:

One solution is not to wrap a socket with stdio at all. Another, probably easier / better for your purposes, is to wrap it with, not one, but two stdio streams:

FILE *in = fdopen(newsock, "r");
FILE *out = fdopen(newsock, "w");

这里有另外​​一个缺陷,但因为当你去 FCLOSE 一个流,即关闭其他的文件描述符。要解决这一点,你需要在 DUP 套接字描述一次(在上述两种调用,它无关紧要哪一个)。

There's another flaw here though, because when you go to fclose one stream, that closes the other's file descriptor. To work around that, you need to dup the socket descriptor once (in either of the two calls above, it does not matter which one).

如果您打算使用选择调查或在某些时候在插座上相似,你通常应该去为解决不符合标准输入输出包,因为有跟踪标准输入输出缓冲没有很好的清洁可移植的方法。 (有具体的实现方式)。

If you intend to use select or poll or similar on the socket at some point, you should generally go for the "don't wrap with stdio" solution, since there's no nice clean portable way to track stdio buffering. (There are implementation-specific ways).

这篇关于&QUOT;非法谋取&QUOT;带插座时误差非空读缓冲流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆