Странное поведение TCP сокетов (С++ / Qt / WinSock)

Решил ознакомиться с сетевым программированием на С++, и разобраться в работе сокетов. Начал с Winsock, написал простеший чат, и по началу мне показалось что всё более менее понятно. А именно, я понял что у нас есть блокирующий метод accept, который принимает подключения и создает сокет клиента, и есть блокирующий метод recv, который ожидает пока с другой стороны в сокет будут записаны данные. Таким образом я мог, например, сделать вот такое

// Получить длину сообщения
size_t msgSize = 0;
recv(clientSocket, reinterpret_cast<char*>(&msgSize), sizeof(size_t), 0);

// Выделить строчку под сообщение нужной длины и получить сообщение
char* msg = new char[msgSize];
connected = (recv(clientSocket, msg, msgSize, 0) != SOCKET_ERROR);

Получается что я как бы 2 раза блокирую поток методом recv, и ожидаю пока данные будут отправлены клиентом при помощи метода send, который тоже вызывается 2 раза. Вроде всё логично, и все вполне предсказуемо работало.

И тут я решил проделать нечто похожее на Qt

А там всё оказалось несколько запутанее. Насколько я понял (поправьте, если не прав) все сокеты там асинхронные, и заточены под работу со слотами и сигналами. Но тем не менее, если есть необходимость использовать именно блокирующий подход без Qt'шного event' loop'а, на этот случай там вроде тоже есть методы (такие как waitForReadyRead). Ну и я решил попробовать провернуть следующее (на стороне сервера):

// Размер и буфер
size_t msgSize = 0;
char* msg = nullptr;

// Получить длину сообщения (ждать бесконечно пока данные не придут)
clientSocket->waitForReadyRead(-1);
clientSocket->read(reinterpret_cast<char*>(&msgSize),sizeof(size_t));

// Получить само сообщение
clientSocket->waitForReadyRead(-1);
clientSocket->read(msg,msgSize);

На стороне клиента же было примерно следующее:

// Отправка длинны сообщения (добавляем единицу для завершающего нуля)
size_t size = message.size()+1;
clientSocket.write(reinterpret_cast<char*>(&size), sizeof(size_t));
clientSocket.waitForBytesWritten(-1);

// Отправка сообщения
clientSocket.write(message.c_str(), size);
clientSocket.waitForBytesWritten(-1);

Насколько я понял, чтобы что-то отправилось от клиента, и чтобы сервер мог воспринять это как "получение данных", по каким-то причинам необходимо вызвать waitForBytesWritten, иначе отправка вообще не произойдет. Как это работает и что происходит в момент вызова waitForBytesWritten для меня пока что загадка, но не это сейчас главное.

Главное в том, что при таком подходе программа на стороне сервера просто виснет. И виснет она на втором вызове waitForReadyRead(-1), будто бы данные о самом сообщении не приходят, и она остается на этом втором вызове waitForReadyRead ожидать их.

В начале я просто решил сделать таймаут поменьше (например, 10 миллисекунд), и всё заработало! Но по сути поток на втором вызове waitForReadyRead все равно подвисал (будто данных нет, хотя они слались клиентом сразу же после размера), просто подвисал не на долго, а потом спокойно себе читал из сокета кусок полученного ранее размера.

И тут я заподозрил, что по каким-то мистическим причинам Qt воспринимает ту отправку 2 раза подряд, как ОДНУ отправку. Я решил просто убрать второй waitForReadyRead и посмотреть, как это всё будет работать - и всё работало замечательно! Работало замечательно, покуда я не решил протестировать это на другом компьютере (с Windows7). На Windows7 по каким-то мистическим причинам в том месте, где шло второе чтение из сокета - данных просто не было.. что вообще за чертовщина?!

В итоге я решил попробовать поменять отправку, и после первого write убрал waitForBytesWritten(-1), оставив его только после второго write. И о чудо, всё заработало!

Только вот остались вопросы :

  • Что это вообще было? Почему есть такая разница при работе с сокетами в Qt на разных ОС?
  • Если использовать event loop, сигналы и слоты, то у сокета сигнал readyRead() срабатывает один раз, даже если отправка с другой стороны была произведена несколько раз подряд (с вызовом waitForBytesWritten), НО если между отправками была какая-то пауза, тогда сигнал срабатывает столько раз, сколько было отправок. Почему так?
  • Какая должна быть пауза между отправками, чтобы они считались отдельными?
  • В некоторых примерах я натыкался на использование метода readAll, который.. читает всё. Я думал, что для того чтобы отправлять сообщения любой длины, получатель должен знать эту самую длину, чтобы считывать именно столько, сколько было отправлено.. Каким образом работает readAll, если данных о том сколько этого all у получателя нет? Чем вообще ограничивается это кол-во?

Вероятно я упускаю какие-то важные моменты в понимании того, как вообще работают TCP сокеты и обмен данными при помощи них на более низком уровне, что и порождает эти глупые вопросы. Буду рад пояснениям. За ранее спасибо.


Ответы (0 шт):