LINUX.ORG.RU

C socket fdopen вечный/долгий fgets при запросе с chrome android

 , , ,


0

2

Столкнулся со странным (непонятным) поведением fgets при чтении данных с сокета посредством fdopen. При запросе с десктопной версии браузера всё хорошо, но когда запрос идет с мобильного браузера chrome, то fgets виснет.

С чем это связано? fgets не находит символа новой строки/EOF? Так fgets все равно должен завершиться при достижении предела num.

char *fgets(char *str, int num, FILE *stream);

Тогда, получается, сам мобильный chrome шлет непрерывные данные в каком-то запросе? Я правильно понимаю (баг chrome? почему fgets при достижении предела num не завершился?)? Или в чем причина?

Да, при использовании read такое поведение не замечено.

ssize_t read(int fd, void *buf, size_t count);

Минималку из своего pet-проекта делать не стал, вот отсюда (https://ycpcs.github.io/cs365-spring2017/lectures/lecture15.html) тупо (для теста, вдруг я где накосячил) взял пример - то же самое поведение.

// This is like server.c, but uses fdopen to allow communication
// via stdio functions rather than Unix system calls

#include <unistd.h>
#include <stdio.h> // for perror()
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>

#include <netinet/in.h> // for struct sockaddr_in
#include <arpa/inet.h> // for inet_pton

#include <string.h>

#define PORT 40002
#define BUFSIZE 2000

int main(void)
{
	int server_sock_fd;
	int rc;
	char buf[BUFSIZE];
	int done = 0;

	server_sock_fd = socket(PF_INET, SOCK_STREAM, 0);
	if (server_sock_fd < 0) {
		perror("Couldn't create socket");
		exit(1);
	}

	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	inet_pton(AF_INET, "0.0.0.0", &server_addr.sin_addr);

	rc = bind(
		server_sock_fd,
		(struct sockaddr *) &server_addr,
		sizeof(server_addr));
	if (rc < 0) {
		perror("Couldn't bind server socket");
		exit(1);
	}

	rc = listen(server_sock_fd, 5);
	if (rc < 0) {
		perror("Couldn't listen on server socket");
		exit(1);
	}

	while (!done) {
		int client_socket_fd;
		struct sockaddr_in client_addr;
		socklen_t client_addr_size = sizeof(client_addr);

		client_socket_fd = accept(
			server_sock_fd,
			(struct sockaddr*) &client_addr,
			&client_addr_size);
		if (client_socket_fd < 0) {
			perror("Couldn't accept connection");
			exit(1);
		}

		// Wrap the socket file descriptor using a FILE*,
		// for reading only.
		FILE *client_socket_fh = fdopen(client_socket_fd, "r");

		printf("before fgets\n");

		// Read one line from the client
		fgets(buf, BUFSIZE, client_socket_fh);

		printf("after fgets\n");

		// Close the client socket
		fclose(client_socket_fh);

		// Write the received message to stdout
		printf("%s", buf);

                // Отсутствовало в примере, добавил
                close(client_socket_fd);

		if (strcmp(buf, "quit\r\n") == 0) {
			done = 1;
		}
	}

	close(server_sock_fd);

	return 0;
}

Т.е. c десктопного chrome вывод в консоль: before fgets after fgets

C мобильного под android: before fgets <висим до закрытия вкладки/браузера> after fgets



Последнее исправление: Stack77 (всего исправлений: 5)

Ответ на: комментарий от byko3y

Не понимаю, а какая разница клиент соединение или нет? У меня и в примере выше соединение принудительно разрывается после, на стороне сервера, close вызывается (должен). Только вот всё дело виснет на fgets (похоже, что на последнем запросе к серверу, т.к. первые данные я получаю, close срабатывает, а на последнем запросе виснет). Как тогда данные читать? Пусть fgets не находит символа новой строки или EOF, но почему он не завершается после чтение BUFSIZE символов?

Я, действительно, не могу понят, что за фигня такая?! Если мне мат. часть подтянуть стоит - разжуйте немного где я заблуждаюсь?

Stack77
() автор топика
Ответ на: комментарий от Stack77

* какая разница закрыл клиент соединение или нет? ** fclose (ну, Вы поняли)

Stack77
() автор топика
Ответ на: комментарий от Stack77

fdopen() это stdio, поэтому предположу что присутствует буфферизация, и пока нет явного EOL/EOF, fgets() ничего не получит. И, видимо, в этом разница между десктопным/мобильным хромом, один шлёт, другой нет (может tcpdump посмотреть?).

xtouqh
()
Ответ на: комментарий от Stack77

Фигня такая, что ты некорретно читаешь соединения. Есть более одного варианта решения проблемы: читать соединения в потоках, в форках, асинхронно. AF_INET не поддерживает SOCK_SEQPACKET, так что заставить систему бить передаваемые данные на пакеты не получится.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Переписал с форком, та же ерунда. При запросе с мобильного виснет на fgets и плодятся дочерние процессы, с десктопа всё ок:

	while (!done) {
		int client_socket_fd;
		struct sockaddr_in client_addr;
		socklen_t client_addr_size = sizeof(client_addr);

		client_socket_fd = accept(
			server_sock_fd,
			(struct sockaddr*) &client_addr,
			&client_addr_size);
		if (client_socket_fd < 0) {
			perror("Couldn't accept connection");
			exit(1);
		}

		pid_t pid = fork();

		if(pid == -1) {
			perror("Error fork");
			exit(1);
		} else if (pid == 0) {
			// Wrap the socket file descriptor using a FILE*,
			// for reading only.
			FILE *client_socket_fh = fdopen(client_socket_fd, "r");
			//setvbuf(client_socket_fh, NULL, _IONBF, 0);

			printf("before fgets\n");

			// Read one line from the client
			fgets(buf, BUFSIZE, client_socket_fh);

			printf("after fgets\n");

			// Close the client socket
			fclose(client_socket_fh);

			// Write the received message to stdout
			printf("%s", buf);

			close(client_socket_fd);
			done = 1;
		} else {
			close(client_socket_fd);
		}
	}

P.S. В тестовом коде честно сдертом с просторов Интернета в топике темы, там действительно клиентский сокет не закрывался close(client_socket_fd), глаз замылен, упустил из виду. Собственно, «зависает» то на операции чтения, до close очередь в любом случае не доходит.

Stack77
() автор топика
Ответ на: комментарий от Stack77

fgets ждёт данных? Возможно приходит данных меньше чем BUFSIZE-1, и в них нету ни EOF ни \n.

Раз речь о мобильном хроме, может из-за другой реализации libc в данных отсутствует EOF/Newline.

RisuX3
()
Ответ на: комментарий от RisuX3

TCP-клиент может присылать любыми порциями, хоть по одному байту.

level1 ★★
()
Ответ на: комментарий от level1

иногда это оправдано, правда, судя по задаваемым вопросам - он не знает что зачем и почему творит.

deep-purple ★★★★★
()

Столкнулся со странным (непонятным) поведением fgets при чтении данных с сокета посредством fdopen.

Использовать FILE для сокета — так себе идея. Ты ещё много граблей по дороге соберёшь.

С чем это связано?

Например, fgets() внутри пытается заполнить буфер, а клиент не присылает достаточного количества данных, чтобы заполнить буфер. Не вполне ясно. Нужно вникать в подробности работы потокового ввода-вывода в конкретной реализации libc.

баг chrome?

LOL

символа новой строки/EOF?

Кстати, нет такой штуки, как «символ EOF». Это абстракция для функций вроде fgetc(). Функция может вернуть байт, который от 0 до 255, а может вернуть значение макроса EOF, если нет байта, который можно было бы вернуть. В самих файлах никаких EOF не хранится, и по сети тоже EOF не посылается.

Отсутствовало в примере, добавил

fdopen() забирает файловый дескриптор к себе в управление. Дескриптор будет закрыт в fclose(), поэтому вызов close() не нужен.


Не пользуйся потоками из stdio.h для сетевых соединений. Используй read() и write(). Потоки больше ориентированы на файлы, где все данные уже есть и в случае чего их можно дочитать в буфер с диска. При соединении по сети неизвестно, когда данные будут доступны, и будут ли доступны вообще.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Ясно, понятно. Читать тогда ридом в цикле и выходить по нужному мне условию (например, символ EOL) + проверят возвращаемое значение read на 0 (EOF) и 1 (ошибка)? Верно? По сколько в буфер оптимально писать, есть какие-то performance?

Знаю, что fgets обертка над fgetc, но почему-то думал, что если ей скормить строку длиной меньше, чем BUFSIZE - она не впадет в ступор. Хорошо, посмотрю реализацию. Видимо, там все очень примитивно…

Везде, просто, примеры с fdopen (вроде такого http://www.cs.cmu.edu/afs/cs/academic/class/15213-s00/www/class28/tiny.c), вот и подумал, что так будет работать без проблем. Ок, спасибо за разъяснения!

P.S. За EOF в курсе, что это не символ :) А по поводу «fdopen() забирает файловый дескриптор к себе в управление» не знал, благодарю!

P.P.S. А когда говорят EOL (end of line) подразумевается же напрямую символ \n (или + символ возврата каретки)? Или так не стоит говорить (например, теоретически в своем протоколе или т.п. в качестве EOL можно использовать что душе угодно, верно)?

Stack77
() автор топика
Ответ на: комментарий от Stack77

Читать тогда ридом в цикле и выходить по нужному мне условию

Ну… да. Так вполне можно: читаешь, сколько получится, собираешь в буфер, пока не найдёшь \n. Потом буфер парсишь. Или можно прямо сразу полученными данными парсер кормить, если он состояние сохраняет между вызовами.

По сколько в буфер оптимально писать, есть какие-то performance?

Эксперимент покажет.

но почему-то думал, что если ей скормить строку длиной меньше, чем BUFSIZE - она не впадет в ступор

Хм. Какая магия помогает libc понять, что с другой стороны соединения закончили посылать данные, и больше их не пришлют?

Везде, просто, примеры с fdopen

Я вот только сегодня увидел пример заворачивания сокета в fdopen. Никогда такая идея в голову не приходила. И примеров таких никогда не видел.

Там, кстати, написано: «Neither robust, secure, nor modular.»

А когда говорят EOL (end of line) подразумевается же

Без понятия. В HTTP предполагается, что строки заголовка заканчиваются \r\n, хотя многие серверы принимают \n без проблем. Видимо, для совместимости с какими-то древними клиентами.

i-rinat ★★★★★
()

дык неблокирующийся сокет делай.

anonymous
()
Ответ на: комментарий от i-rinat

Хм. Какая магия помогает libc понять, что с другой стороны соединения закончили посылать данные, и больше их не пришлют?

Я немного не о том. Вот read по ману получает EOF при разрыве соединения и возвращает 0. Уже если клиент соединение разорвал (а по tcpdump браузер, что мобильный, что десктопный, в конце принудительно завершает соединение, по крайней мере в такой простой реализации без keep-alive), то бесконечного цикла не возникнет. Думал, что и fgets примерно так же.

Ну, это я на найденные примеры ориентировался (давно когда-то пробовал на С работать с сокетом под Linux, но как видите всё успешно забыл), теперь мне понятно, что есть хорошо, а что плохо. По крайней мере в основных моментах разобрался.

Всем спасибо большое за разъяснения и помощь! Хорошего дня!

P.S. Не знаю кому писать по поводу улучшения функционала ЛОР-а, напишу здесь. Небольшой padding в 5-10px в textarea отправки сообщения очень бы был кстати. Делаешь Enter и курсор пропадает где-то за border. Делов css поправить 2 минуты, а пользы было бы ого-го :)

Stack77
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.