6

我目前正在编写一个关于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");
}

代码应在 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' 字符串(客户端从未收到),然后刷新输出缓冲区时,我们收到一个Illegal seek错误,并且下一次调用fgetc()返回 EOF。

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

fflush failed: Illegal seek

服务器输出中缺少行。

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

如果两者都fputs(ack)fflush()注释掉,那么一切都会预期工作。

不幸的是,我没有找到任何好的文档,也没有关于这个问题的任何 Internet 讨论,因此非常感谢您的帮助。

编辑

我最终解决的解决方案是使用fdopen()and FILE*,因为似乎没有干净的方法可以将套接字 fd 转换为FILE*可以在r+模式下可靠使用的 a 。fputs相反,我直接在套接字 fd 上工作,为and编写自己的替换代码fprintf

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

4

4 回答 4

5

显然,“r+”(读/写)模式在这个实现中不适用于套接字,毫无疑问,因为底层代码假定它必须在读和写之间进行切换。这是 stdio 流的一般情况(您必须执行某种同步操作),因为回到昏暗时间,实际的 stdio 实现每个流只有一个计数器,它要么是“剩余字符数”的计数器通过getc宏从流缓冲区中读取”(在读取模式下)或“可以通过宏安全地写入流缓冲区的字符数putc(在写入模式下)。要重置单个计数器,您必须进行查找-类型操作。

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

一种解决方案是根本不使用 stdio 包装套接字。对于您的目的,另一个可能更容易/更好的方法是用两个stdio 流而不是一个来包装它:

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

不过这里还有另一个缺陷,因为当您转到fclose一个流时,会关闭另一个流的文件描述符。要解决这个问题,您需要dup一次套接字描述符(在上面的两个调用中的任何一个中,哪一个都没有关系)。

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

于 2012-04-21T07:33:14.067 回答
1

是的,您可以使用一个文件流来处理您的套接字,至少在 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);
}
于 2015-04-21T04:54:41.057 回答
1

不要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);
于 2012-04-21T03:07:42.693 回答
0

尝试这个 :

#define BUFSIZE 88

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

int main(int argc, char** argv) {
    int bufsize = BUFSIZE;
    char buf[ BUFSIZE ];
于 2014-12-15T10:48:04.483 回答