I am currently writing a Linux x86_64
server application using <sys/socket.h>
. After accepting the connection via accept()
I use fdopen()
to wrap the extracted socket into a FILE*
stream.
Writing and reading from this FILE*
stream usually works quite well, but the socket becomes inappropriate as soon as I write to it while it has a non-empty read buffer.
For demo purposes, I wrote code that listens for the connection, then reads line by line input into the read buffer using fgetc()
. If the line is too long to fit into the buffer, it is not fully read, but instead is read during the next iteration.
#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 an argument and use netcat to connect to it locally.
Now, if you try to send lines less than 8 characters long, this will work flawlessly. But if you send a string containing more than 10 characters, the program will end with an error. This input example:
ab cd abcdefghij
Generates 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 can see, (by right) only the first 8 characters of abcdefgh are read, but when the program tries to send the string “ack” (which the client never receives), and then clears the output buffer, we get Illegal seek
, and the next call to fgetc()
returns EOF .
If the fflush()
commented out, the same error still occurs, but
fflush failed: Illegal seek
There is no line at the server output
.
If the fputs(ack)
commented out, everything seems to work as intended, but the perror (), called manually from gdb, still reports an Illegal Search error.
If both fputs(ack)
and fflush()
commented out, everything works as planned.
Unfortunately, I could not find any good documentation or any online discussions on this issue, so your help would be greatly appreciated.
change
The solution that I finally decided was not to use fdopen()
and FILE*
, because there seems to be no clean way to convert the fd socket to FILE*
, which can be reliably used in r+
mode. Instead, I worked directly on the fd socket, writing my own replacement code for fputs
and fprintf
.
If someone needs this, here is the code .