Jump to content

Отслеживание ошибок сокетов TCP/UDP


photo

Recommended Posts

Добрый день!
Подскажите пожалуйста, как посмотреть что происходит с сокетом? Какие конкретно ошибки и где они возникают?
Работаю с проприетарным трекером положения, который в свою очередь передаёт свои данные через стороннее проприетарное ПО.
Используется TCP/UDP соединение и передача данных по следующей схеме:
Клиент -> TCP (просто соединение на укзанном порту)-> ПО
ответ ПО -> TCP  с указанием порта UDP, который надо начать слушать, чтобы получать данные
Клиент -> UDP по полученному выше порту -> получение данных.

К сожалению обойти этот "костыль" я никак не могу (хотя очень хочется конечно))).
Так вот. Реализация у меня есть и она работает. Вопрос возник в другом - а что делать, если вдруг стороннее ПО отвалится по той или иной причине? Хочется сделать так, чтобы в случае чего соединение восстанавливалось само.
Базовый запуск получения данных выглядит следующим образом:

void TrackerHandler::InitTracker() {

	InitHandlerTimer();
	InitDataBuffer();

	if (!InitTCPSocket()) return;  // Выполняет первое TCP соединение со сторонним ПО
	while (IsTCPinit() && _timer.endMilliseconds() < _INIT_DELAY_MS)
	{
		if (debug) {
			/* Временная простая заглушка, просто ждёт TrackerHandler::_INIT_DELAY_MS мс перед началом запуска UDP */
			Unigine::Log::message("wait %i ms before UDP initialization\n", _INIT_DELAY_MS);
		}
	}
	// RecieveUDPPort() - получает и дешифрует сообщение по TCP протоколу
    // Передает полученный порт в метод инициализации UDP сокета
	if (!InitUDPSocket(RecieveUDPPort())) return;
	ResetHandelerTimer();

	_is_init = true;
}

Важно. "Костыль" работает на постоянном TCP соединении, если отключиться то передача данных по UDP остановится. Таким образом оба сокета остаются активными.
Инициализация сокетов реализована следующим образом:

bool TrackerHandler::InitTCPSocket() {
	if (IsTCPinit()) return true;

	if (_tcp == nullptr) {
		_tcp = Unigine::Socket::create(Unigine::Socket::SOCKET_TYPE::SOCKET_TYPE_STREAM, _host.data(), _port);
	}

	if (debug) {
		Unigine::Log::message("Try TCP connection to %s:%i\n", _host.data(), _port);
	}

	if (!(_tcp_connect = _tcp->connect())) return _tcp_connect;
			
	if (!_tcp->nonblock()) {
		throw std::runtime_error("TrackerHandler::InitTCPSocket()::ERROR::TCP Socket nonblock setting is fault\n");
	}

	return (bool)_tcp;
}

		
bool TrackerHandler::InitUDPSocket(int port) {
	if (IsUDPinit()) return true;

	if (debug) {
		Unigine::Log::message("UDP port is %i\n", port);
	}

	if (port == -1) return false;
			
	if (_udp == nullptr) {
		_udp = Unigine::Socket::create(Unigine::Socket::SOCKET_TYPE::SOCKET_TYPE_DGRAM);
	}

	if (!(_upd_open = _udp->open(port))) return false;
		
	if (!_udp->recv((int)GetBufferSize())) {
		throw std::runtime_error("TrackerHandler::InitUDPSocket()::ERROR::Recieve buffer size setting error\n");
	}

	if (!_udp->bind())  {
		throw std::runtime_error("TrackerHandler::InitUDPSocket()::ERROR::UDP Socket bind is fault\n");
	}

	if (!_udp->nonblock())  {
		throw std::runtime_error("TrackerHandler::InitUDPSocket()::ERROR::UDP Socket nonblock setting is fault\n");
	}

	return (bool)_udp;
}


Если "костыль" изначально запущен или его таки запускают через какое-то время, то благодаря update() происходит инициализация сокетов, данные начинают поступать и всё работает. Соответственно если вдруг он отваливается, то поток останавливается. Стал копать какие есть у сокетов флаги состояния.

bool TrackerHandler::IsTCPinit() const {
// Для UDP почти то же самое сделал
	if (debug) {
		if (!_tcp) {
			Unigine::Log::message("TCP Socket ptr is not init\n");
		}
		else {
			Unigine::Log::message("TCP Socket debug information\n");
			Unigine::Log::message("TCP Socket is valid               - %s\n", _tcp->isValid() ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is opened              - %s\n", _tcp->isOpened() ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is connect             - %s\n", _tcp_connect ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is avalible            - %s\n", _tcp->isAvailable() ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is ready to read       - %s\n", _tcp->isReadyToRead() ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is ready to write      - %s\n", _tcp->isReadyToWrite() ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is deleted             - %s\n", _tcp->isDeleted() ? "TRUE" : "FALSE");
			Unigine::Log::message("TCP Socket is error               - %s\n\n", _tcp->isError() ? "TRUE" : "FALSE");
		}
	}

	return _tcp != nullptr && _tcp_connect;
}

И вот что привлекло моё внимание:

... выдержка из консоли ...
Script loading "core/unigine.usc" 54ms
World loading "cpp-unigine-components.world" (Time: 32.3ms, Memory: 915.5KB)
TCP Socket ptr is not init                          <--- соекта еще нет, создаём
Try TCP connection to 127.0.0.1:40124               <--- выполняем подключение (оно успешно, так как "костыль" работает)
TCP Socket debug information                        <--- статусы выводятся из-за ожидания в цикле while (запуск пошаговый, время прошло)
TCP Socket is valid               - TRUE
TCP Socket is opened              - TRUE
TCP Socket is connect             - TRUE
TCP Socket is avalible            - FALSE
TCP Socket is ready to read       - TRUE
TCP Socket is ready to write      - TRUE
TCP Socket is deleted             - FALSE
TCP Socket is error               - FALSE           <--- нет ошибок!!!

Try read TCP socket                                 <--- читаем сообщение от стороннего ПО       
Try resolve UDP port                                <--- получаем порт
UDP Socket ptr is not init                          <--- создаём сокет
UDP port is 58447                                   <--- вывод в консоль полученного порта от TCP сокета
TCP Socket debug information
TCP Socket is valid               - TRUE
TCP Socket is opened              - TRUE
TCP Socket is connect             - TRUE
TCP Socket is avalible            - FALSE
TCP Socket is ready to read       - FALSE
TCP Socket is ready to write      - TRUE
TCP Socket is deleted             - FALSE
TCP Socket is error               - TRUE            <--- откуда ошибка?! что пошло не так? что случилось?

UDP Socket debug information                        <--- UDP порт работает никаких нареканий
UDP Socket is valid               - TRUE
UDP Socket is opened              - TRUE
UDP Socket is avalible            - FALSE
UDP Socket is ready to read       - TRUE
UDP Socket is ready to write      - TRUE
UDP Socket is deleted             - FALSE
UDP Socket is error               - FALSE           <--- UDP нет ошибок

... далее выводятся статусы уже по TCP и UDP и у обоих сокетов есть ошибки, хотя передача данных идёт корректно и сбоев в работе нет

Для получения данных использую метод read(), в который передаю буфер void* предварительно очищенный от предыдущих данных.

Вопрос 1 - откуда, почему и как возникают ошибки? Ошибки на обоих сокетах.
Вопрос 2 - если не с помощью флага ошибки, то как отследить потерю соедиения?
Спасибо!

Link to comment

1. по поводу методов для сокета

isValid/isDeleted - методы указателя, не сокета. показывают жив ли экземпляр объекта или уже удален. 

isOpened - проверяет открыт ли сокет. после закрытия вернем false. 

isAvaliable - для сокета всегда возвращает 0
isReadyToRead/isReadyToWrite выполняет соответствующий select на сокет 

isError - вот тут интереснее. я не уверен что этот метод работает корректно, потому что вместе с выставлением ошибки в сокете должно писаться сообщение об ошибке в консоль. везде кроме пары мест: 

size_t res = write / size;
if (res != nmemb)
    is_error = 1;


когда методы read/write считали или записали меньше или больше элементов чем планировалось (*!) тут надо смотреть конкретно логику записи/чтения.

к сожалению после этого этот флаг не снимается и до конца жизни будет возвращать true даже не смотря на то, что все работает. поэтому не стоит обращать на него внимания, выглядит что в этом месте просто баг, не влияющий на работу.


2. по поводу потери соединения - к сожалению протоколы tcp/udp не подразумевают уведомления о непредвиденном разрыве. Если внезапно кто-то непредвиденно пропал - сокет будет висеть активным еще какое то время, так называемые полуоткрытые сокеты, время жизни которых определяется ОС
вот тут можно найти подробнее
https://habr.com/ru/articles/173415/
https://ru.wikipedia.org/wiki/Полуоткрытое_TCP/IP-соединение

Единственный способ обнаруживать такой разрыв — это попытаться что-то отправить в сокет. Возможно, слышали слово heartbeat — это периодическая отправка небольшого сообщения серверу (клиенту) и ожидание ответа. Делается именно для того, чтобы обнаруживать полуоткрытые соединения.


* для анализа сетевого трафика я рекомендую использовать програмку wireshark - чтобы понять что именно летает по сокетам и почему возникает ошибка чтения/записи. 

  • Like 1
Link to comment

Спасибо за подсказки!
Посмотрю что можно придумать. У меня ситуёвина отягощается тем, что я не знаю как именно работает "костыль".
Всё что мне сказали:
"при подключении по TCP тебе присылается уведомление с UDP портом, на который высылаются данные". Ну и формат данных сказали. На этом всё)
Потому я даже не знаю, есть ли какие-то еще команды для проверки работоспособности? нету?
Ну и плюс моя неопытность конечно же.. может быть я вообще задаю вопросы, которые "и так всем понятны и известны")

Link to comment
×
×
  • Create New...