Сейчас я пишу серверное приложение на Linux x86_64
, используя
. После принятия соединения через accept()
я использую fdopen()
, чтобы обернуть полученный сокет в поток FILE*
.
Запись и чтение из этого FILE*
потока обычно работает довольно хорошо, но сокет становится непригодным для использования, как только я пишу в него, когда он имеет не-пустой буфер чтения.
В демонстрационных целях я написал некоторый код, который прослушивает соединение, а затем считывает ввод, строка за строкой, в буфер чтения, используя fgetc()
. Если строка слишком длинная, чтобы поместиться в буфер, она не считывается полностью, а считывается во время следующей итерации.
#include
#include
#include
#include
#include
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
.
Если кому надо, вот код .