Linux 处理具有非空读取缓冲区的套接字流时出现“非法搜索”错误
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10255663/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
"Illegal seek" error when working with socket streams with non-empty read buffers
提问by mic_e
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.
我目前正在编写一个Linux x86_64
使用<sys/socket.h>
. 通过 接受连接后accept()
,我使用fdopen()
将检索到的套接字包装到FILE*
流中。
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.
写入和读取该FILE*
流通常工作得很好,但是一旦我写入它,套接字就会变得不可用,而它有一个非空的读取缓冲区。
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.
出于演示目的,我编写了一些代码来侦听连接,然后使用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");
}
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.
代码应该在没有任何特殊参数的情况下在 gcc 中编译。使用端口号作为参数运行它,并使用 netcat 在本地连接到它。
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:
现在,如果您尝试发送少于 8 个字符的字符串,这将完美运行。但是如果你发送一个包含超过 10 个字符的字符串,程序就会失败。此示例输入:
ab
cd
abcdefghij
Will create this output:
将创建此输出:
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
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.
如您所见,(正确地)仅读取 abcdefgh 的前 8 个字符,但是当程序尝试发送 'ack' 字符串(客户端从不接收),然后刷新输出缓冲区时,我们收到Illegal seek
错误,并且下一次调用fgetc()
返回 EOF。
If the fflush()
part is commented out, the same error still occurs, but the
如果fflush()
部分被注释掉,同样的错误仍然发生,但是
fflush failed: Illegal seek
line is missing from the server output.
服务器输出中缺少行。
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)
部分被注释掉,一切似乎都按预期工作,但是从 gdb 手动调用的 perror() 仍然报告“非法搜索”错误。
If both fputs(ack)
and fflush()
are commented out, everything doeswork as intended.
如果同时fputs(ack)
和fflush()
被注释掉了,一切都做工作按预期。
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.
不幸的是,我找不到任何好的文档,也找不到关于此问题的任何 Internet 讨论,因此非常感谢您的帮助。
edit
编辑
The solution i finally settled for is to notuse 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
.
我最终解决的解决方案是不使用fdopen()
and FILE*
,因为似乎没有干净的方法可以将套接字 fd 转换为FILE*
可以在r+
模式下可靠使用的套接字。相反,我直接在套接字 fd 上工作,为fputs
和编写自己的替换代码fprintf
。
If anyone needs it, here is the code.
如果有人需要它,这是代码。
采纳答案by torek
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.
显然,“r+”(读/写)模式在这个实现中不适用于套接字,毫无疑问,因为底层代码假定它必须在读和写之间进行切换。这是 stdio 流的一般情况(您必须执行某种同步操作),因为在 Dim Time 中,实际的 stdio 实现每个流只有一个计数器,并且它是“剩余字符数”的计数器通过getc
宏从流缓冲区读取”(在读取模式下)或“可以通过putc
宏安全写入流缓冲区的字符数(在写入模式下)。要重置单个计数器,您必须进行搜索-类型操作。
Seeks are not allowed on pipes and sockets (since "file offset" is not meaningful there).
管道和套接字上不允许查找(因为“文件偏移量”在那里没有意义)。
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 twostdio streams:
一种解决方案是根本不使用 stdio 包装套接字。另一个,可能更容易/更好地满足您的目的,是用不是一个,而是两个stdio 流来包装它:
FILE *in = fdopen(newsock, "r");
FILE *out = fdopen(newsock, "w");
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).
不过,这里还有另一个缺陷,因为当您进入fclose
一个流时,会关闭另一个的文件描述符。要解决这个问题,您需要dup
调用一次套接字描述符(在上述两个调用中的任何一个中,哪个都无关紧要)。
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).
如果您打算在某个时候在套接字上使用select
或poll
类似,您通常应该选择“不使用 stdio 包装”解决方案,因为没有很好的干净的便携式方法来跟踪 stdio 缓冲。(有特定于实现的方法)。
回答by smocking
Don't use fflush()
on network sockets. They are unbuffered streams.
不要fflush()
在网络套接字上使用。它们是无缓冲的流。
Also, this code:
另外,这段代码:
//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;
}
does not read a single line. It only reads up to the buffer size, which you defined as 8. sock
will still have data for you to receive which you should receive beforewriting to the stream with fputs
. BTW you can replace that whole block with
不读取一行。它只会读取到缓冲区大小,你定义为8.sock
仍会有数据为您收到,你应该接受前书面向与该流fputs
。顺便说一句,你可以用
fgets(buf, bufsize, sock);
回答by bill
Try this :
尝试这个 :
#define BUFSIZE 88
FILE* listen_on_port(unsigned short port) {
...
}
int main(int argc, char** argv) {
int bufsize = BUFSIZE;
char buf[ BUFSIZE ];
回答by Bunch
Yes, you can use one file stream to handle your socket, at least on Linux. But you should be careful with it: you must only use ferror() to test for errors. I have some code that use this and run flawlessly in production on a major French site.
是的,您可以使用一个文件流来处理您的套接字,至少在 Linux 上是这样。但是你应该小心:你必须只使用 ferror() 来测试错误。我有一些使用它的代码,并在一个主要的法国网站上完美地在生产环境中运行。
If you use errno or perror() you'll catch any internal error that the stream will encounter, even if it wants to hide it to you. And "Illegal seek" is one of them.
如果您使用 errno 或 perror() ,您将捕获流将遇到的任何内部错误,即使它想向您隐藏它。而“非法搜索”就是其中之一。
Also, to test for real EOF conditions, you should use feof(), since when returning true it's mutually exclusive with ferror() returning a non-zero value. It's because, when using fgetc() you don't have any mean to differentiate error from real EOF conditions. So you should probably better use fgets() as another user pointed out.
此外,要测试真实的 EOF 条件,您应该使用 feof(),因为当返回 true 时,它与返回非零值的 ferror() 互斥。这是因为,在使用 fgetc() 时,您没有任何方法可以将错误与真实的 EOF 条件区分开来。因此,正如另一位用户指出的那样,您可能应该更好地使用 fgets()。
So, your test:
所以,你的测试:
if(data == EOF) {
perror("EOF: Connection reset by peer");
break;
} else {
printf("Input line: '%s'\n", buf);
}
Should be written as:
应该写成:
int sock_error = ferror(sock);
if (sock_error) {
fprintf(stderr, "Error while reading: %s", strerror(sock_error));
} else {
printf("Input line: '%s'\n", buf);
}