В настоящее время я пишу серверное приложение на 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
Как видите, (правильно) читаются только первые 8 символов abcdefgh, но когда программа пытается отправить строку 'ack' (которую клиент никогда не получает), а затем сбросить буфер вывода, мы получаем ошибку Illegal seek
, и следующий вызов fgetc()
возвращает EOF.
Если часть fflush()
закомментирована, та же ошибка возникает, но
fflush failed: Illegal seek
строка отсутствует в выводе сервера.
Если часть fputs(ack)
закомментирована, кажется, что все работает как задумано, но perror(), вызываемый вручную из gdb, по-прежнему сообщает об ошибке «Недопустимый поиск».
Если и fputs(ack)
, и fflush()
закомментированы, все действительно работает как задумано.
К сожалению, мне не удалось найти ни хорошей документации, ни обсуждений этой проблемы в Интернете, поэтому ваша помощь будет очень признательна.
изменить
Решение, на котором я остановился, состоит в том, чтобы не использовать fdopen()
и FILE*
, поскольку, похоже, не существует чистого способа преобразования сокета fd в FILE*
, который можно было бы надежно использовать в режиме r+
. Вместо этого я работал непосредственно над сокетом fd, написав собственный код замены для fputs
и fprintf
.
Если кому нужно, вот код.