与插座工作时“非法寻求”错误与非空读取缓冲区流(“Illegal seek” error when

2019-09-16 15:37发布

就我目前正在写一个服务器应用程序Linux x86_64使用<sys/socket.h> 。 接受经由连接后accept()我使用fdopen()检索到的插座包装到一个FILE*流。

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

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

#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");
}

该代码应在GCC编译没有任何特殊的参数。 与端口号作为参数,并使用netcat的运行它在本地连接到它。

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

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),然后刷新输出缓冲区,我们收到一个Illegal seek错误,并在下次调用fgetc()返回EOF。

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

fflush failed: Illegal seek

线从服务器输出失踪。

如果fputs(ack)部分被注释掉了,似乎一切都按计划工作,但PERROR()从GDB人工呼叫仍然会报告“非法寻求”的错误。

如果双方fputs(ack)fflush()被注释掉了,一切都没有工作的打算。

不幸的是,我没能找到任何好的文件,也没有在这个问题上的任何互联网的讨论,所以你的帮助将不胜感激。

编辑

该解决方案,我终于尘埃落定的是使用fdopen()FILE* ,因为似乎是一个套接字fd转换成的免洗方式FILE*可以在可靠地使用r+模式。 相反,我直接制作的套接字fd,写我自己的替换代码fputsfprintf

如果有人需要它, 这里是代码 。

Answer 1:

显然,“R +”(读/写)模式不会在此实现插座的工作,无疑是因为底层代码假定它必须做一个努力的阅读和写作之间切换。 这与标准输入输出流一般情况下(即你必须做某种同步操作的),因为早在昏暗的时间,实际的标准输入输出的实现只有每个流一个计数器,这是不是的“字符数的反左通过从流缓冲区中读取getc宏“(读取模式下)或”可安全写入流通过缓冲区中的字符数putc宏(在写模式),为的是那一个计数器重新设置,你不得不做寻求型操作。

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

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

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

这里有另外一个缺陷,但因为当你去fclose一个流,封闭对方的文件描述符。 要解决这一点,你需要dup套接字描述一次(在上述两种调用,它并不重要的一个)。

如果你打算使用selectpoll或在某些时候在插座上相似,你通常应该去为“不换行与标准输入输出”的解决方案,因为有跟踪标准输入输出缓冲没有很好的清洁可移植的方法。 (有具体实现的方式)。



Answer 2:

不要使用fflush()对网络套接字。 它们是无缓冲流。

此外,这样的代码:

//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;
}

不读一行。 它只会读取到缓冲区大小,你定义为8 sock仍会有数据为您收到您应该写入流之前收到fputs 。 顺便说一句,你可以用所整块

fgets(buf, bufsize, sock);


Answer 3:

是的,你可以使用一个文件流处理您的插座,至少在Linux上。 但是,你应该要小心它:你必须只使用ferror()函数来测试错误。 我有一些代码,一个主要的法国网站在生产中使用这个和运行完美。

如果使用错误号或PERROR(),你会发现任何内部错误,该流会遇到,即使想将它藏到你。 而“非法谋”就是其中之一。

此外,为了测试实际EOF条件,你应该使用FEOF(),因为返回true,当它与FERROR互斥()返回一个非零值。 这是因为,使用龟etc()时,你没有任何手段从实际EOF条件区分错误。 所以,你应该可能会更好用fgets()作为另一个用户指出。

所以,你的测试:

if(data == EOF) {
    perror("EOF: Connection reset by peer");
    break;
} else {
    printf("Input line: '%s'\n", buf);
}

应该写成:

int sock_error = ferror(sock);
if (sock_error) {
    fprintf(stderr, "Error while reading: %s", strerror(sock_error));
} else {
    printf("Input line: '%s'\n", buf);
}


Answer 4:

尝试这个 :

#define BUFSIZE 88

FILE* listen_on_port(unsigned short port) {
 ... 
}

int main(int argc, char** argv) {
    int bufsize = BUFSIZE;
    char buf[ BUFSIZE ];


文章来源: “Illegal seek” error when working with socket streams with non-empty read buffers