mic*_*c_e 6 c sockets linux fdopen
我正在编写一个Linux x86_64使用的服务器应用程序<sys/socket.h>.接受连接后accept(),我fdopen()用来将检索到的套接字包装成FILE*流.
写入和读取该FILE*流通常可以很好地工作,但是当它写入时,套接字变为不可用,同时它具有非空的读取缓冲区.
出于演示目的,我编写了一些侦听连接的代码,然后逐行读取输入到读缓冲区中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");
}
Run Code Online (Sandbox Code Playgroud)
代码应该在没有任何特殊参数的gcc中编译.使用端口号作为参数运行它,并使用netcat在本地连接到它.
现在,如果你尝试发送短于8个字符的字符串,这将运行完美.但是如果发送包含10个以上字符的字符串,程序将失败.此示例输入:
ab
cd
abcdefghij
Run Code Online (Sandbox Code Playgroud)
将创建此输出:
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
Run Code Online (Sandbox Code Playgroud)
如你所见,(正确地)只读取abcdefgh的前8个字符,但是当程序试图发送'ack'字符串(客户端从不接收),然后刷新输出缓冲区时,我们收到Illegal seek错误,并且下一次调用fgetc()返回EOF.
如果fflush()部件被注释掉,仍会出现相同的错误,但是
fflush failed: Illegal seek
Run Code Online (Sandbox Code Playgroud)
服务器输出中缺少行.
如果fputs(ack)部件被注释掉,一切似乎都按预期工作,但是从gdb手动调用的perror()仍会报告"非法搜索"错误.
如果同时fputs(ack)和fflush()被注释掉了,一切都没有像预期的那样.
不幸的是,我找不到任何好的文档,也没有关于这个问题的任何互联网讨论,所以非常感谢你的帮助.
编辑
该解决方案,我终于尘埃落定的是不使用fdopen()和FILE*,因为似乎是一个套接字fd转换成的免洗方式FILE*,可以可靠地使用r+模式.相反,我直接在socket fd上工作,为fputs和编写自己的替换代码fprintf.
如果有人需要它,这里是代码.
显然,在此实现中,“ r +”(读/写)模式不适用于套接字,这毫无疑问,因为底层代码假定它必须设法在读和写之间进行切换。这是stdio流的一般情况(您必须执行某种同步操作),因为回到Dim Time时,实际的stdio实现每个流只有一个计数器,并且要么是“剩余字符数”的计数器getc“ 通过宏从流缓冲区读取”(在读取模式下)或“可以通过putc宏安全地写入流缓冲区的字符数(在写模式下)。要重置该单个计数器,您必须执行一次搜索-类型操作。
不允许在管道和套接字上进行查找(因为“文件偏移”在那里没有意义)。
一种解决方案是根本不使用stdio包装套接字。为了您的目的,另一种可能(更容易/更好)的方法是将它包装成不是两个,而是两个 stdio流:
FILE *in = fdopen(newsock, "r");
FILE *out = fdopen(newsock, "w");
Run Code Online (Sandbox Code Playgroud)
但是,这里还有另一个缺陷,因为当您转到fclose一个流时,这会关闭另一个流的文件描述符。要解决此问题,您只需dup一次使用套接字描述符(在上述两个调用中的任何一个中,哪个都不重要)。
如果您打算在某个时候在套接字上使用select或poll类似的方法,通常应该选择“不要用stdio包装”解决方案,因为没有很好的干净的便携式方法来跟踪stdio缓冲。(有特定于实现的方式)。
| 归档时间: |
|
| 查看次数: |
12865 次 |
| 最近记录: |