处理具有非空读取缓冲区的套接字stream时出现“非法查找”错误

我目前正在使用<sys/socket.h>Linux x86_64上编写一个服务器应用程序。 通过accept()接受连接后,我使用fdopen()将检索到的套接字封装到FILE*stream中。

写入和读取FILE*stream通常工作得很好,但是一旦我写入了它,套接字变得不可用,而它有一个非空的读取缓冲区。

为了演示目的,我写了一些代码来监听连接,然后使用fgetc()将input逐行读入到读缓冲区中。 如果该行太长而不能放入缓冲区,则不能完全读取,而是在下一次迭代中读取。

 #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个字符的string,这将完美无瑕地运行。 但是,如果您发送一个包含10个以上字符的string,程序将会失败。 此示例input:

 ab cd abcdefghij 

将创build这个输出:

 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'string(客户端永远不会收到),然后刷新输出缓冲区,我们收到一个Illegal seek错误,下一次调用fgetc()返回EOF。

如果fflush()部分被注释掉,同样的错误仍然会发生,但是

 fflush failed: Illegal seek 

从服务器输出中缺less一行。

如果fputs(ack)部分被注释掉了,一切似乎都按预期工作,但是从gdb手动调用的perror()仍然会报告“非法查找”错误。

如果fputs(ack)fflush()都被注释掉了,那么一切按预期工作。

不幸的是,我一直没有find任何好的文档,也没有任何关于这个问题的互联网讨论,所以你的帮助将不胜感激。

编辑

我最终解决的解决scheme是使用fdopen()FILE* ,因为似乎没有将套接字fd转换为可以在r+模式下可靠使用的FILE*干净方法。 相反,我直接在socket fd上编写了fputsfprintf的replace代码。

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

Solutions Collecting From Web of "处理具有非空读取缓冲区的套接字stream时出现“非法查找”错误"

显然,在这个实现中,“r +”(读/写)模式在套接字上不起作用,毫无疑问,因为底层代码假设它必须在读和写之间进行切换。 这是stdio流的一般情况(您必须执行某种同步操作),因为在Dim Time中,实际的stdio实现每个流只有一个计数器,并且它是一个“剩余字符数通过getc宏“(在读取模式下)或”可以通过putc宏(在写入模式下)安全地写入流缓冲区的字符数来读取流缓冲区。为了这个单个计数器重新设置,你必须做一个寻求型的操作。

搜索不允许在管道和套接字上(因为“文件偏移量”在那里是没有意义的)。

一个解决方案是不要用stdio包装一个套接字。 另一个,可能更容易/更好的目的,是包装,而不是一个,但两个 stdio流:

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

这里还有一个缺点,因为当你去关闭一个流,关闭另一个文件描述符。 要解决这个问题,你需要dup套接字描述符一次(在上面的两个调用中,哪一个都没有关系)。

如果您打算在某个时间点使用selectpoll或类似的套接字,通常应该选择“不要包装stdio”解决方案,因为没有很好的便捷方式来跟踪stdio缓冲。 (有特定于实现的方法)。

不要在网络套接字上使用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的缓冲区大小。在使用fputs写入流之前, sock将仍然有数据让您接收您应该接收的数据。 顺便说一句,你可以用整个块代替

 fgets(buf, bufsize, sock); 

尝试这个 :

 #define BUFSIZE 88 FILE* listen_on_port(unsigned short port) { ... } int main(int argc, char** argv) { int bufsize = BUFSIZE; char buf[ BUFSIZE ]; 

是的,您可以使用一个文件流来处理您的套接字,至少在Linux上。 但是你应该小心:你只能使用ferror()来测试错误。 我有一些使用这个代码,并在一个主要的法国网站上生产完美无瑕。

如果你使用errno或perror(),你会发现流会遇到的任何内部错误,即使它想隐藏给你。 而“非法寻求”就是其中之一。

此外,为了测试真正的EOF条件,你应该使用feof(),因为当返回true时,它与ferror()互斥,返回一个非零值。 这是因为,当使用fgetc()时,你没有任何意义来区分真正的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); }