Здравствуйте, уважаемые форумчане!
Некоторое время назад передо мной была поставлена задача – разработать GPS/ГЛОНАСС трекер с поддержкой специфичного протокола обмена данными поверх TCP (данный протокол уже реализован на удаленном узле на Linux, а я просто должен под него подстроиться). Основные настройки устройства должны осуществляться по USB (mass storage device + внутренняя flash-память МК + fatfs). Программа должна крутиться под управлением freeRTOS + lwip. В качестве МК был выбран st32f407vg (ф.STMicroelectronics).
Программа на данный момент рабочая, но осталось несколько нерешенных вопросов связанных с lwip стеком и, возможно, сетевым программированием. С чем, собственно, и прошу помочь. Заранее оговорюсь, с сетевым программированием столкнулся впервые в данном проекте. По ходу написания программы читал Стивенса (не от корки до корки, но общую суть, надеюсь, уловил).
Это было краткое введение, теперь суть проблемы. Работаю с блокирующими BSD сокетами. По протоколу на моем устройстве поднят сервер и клиент. Когда приходит на сервер FIN от удаленного узла, я закрываю и сервер, и клиента. Затем снова открываю. Клиент довольно быстро выполняет connect и начинает передавать данные. С сервером дела обстоят иначе. Ниже представлен кусок кода, как я поднимаю сервер (код целиком я прикреплю в конце поста).
recv_serverfd = socket(AF_INET, SOCK_STREAM, 0);
if (recv_serverfd < 0) {Error_Handler();}
memset(&recv_IpAddr, 0, sizeof(recv_IpAddr));
recv_IpAddr.sin_family = AF_INET;
recv_IpAddr.sin_port = htons(rs_port);
recv_IpAddr.sin_addr.s_addr = htonl(INADDR_ANY);
len = sizeof(lingr);
if (setsockopt(recv_serverfd, SOL_SOCKET, SO_LINGER, (void*)(&lingr), len) != 0)
{
#ifdef UART_PRINTF
printf("Setsockopt(recv_serverfd) error\r\n");
#endif //UART_PRINTF
}
do
{
status = bind(recv_serverfd, (struct sockaddr*)&recv_IpAddr, sizeof(recv_IpAddr));
}
while (status < 0);
listen(recv_serverfd, 1);
Функция bind в цикле раз за разом возвращает -1. И длиться это может по разному: от милисекунд до минут. Для меня последнее критично, т.к. слишком долго восстанавливается связь. Тут Вы скажете, что это нормально. Что сервер переоткрывается на том же порте, и bind будет висеть до тех пор пока закрытое соединение не передаст все данные из передающего буфера. Но есть одно «НО» - сервер у меня принимает данные, но не передает. Тогда откуда задержка? Это первый вопрос.
Логичным продолжением стало использование параметра SO_LINGER. Как написано у Стивенса, данный параметр позволяет изменить поведение функции close. По умолчанию функция close возвращает управление немедленно, но если в передающем буфере остались данные, система попытается доставить данные собеседнику (l_onoff = 0, l_linger = 0). Это как раз рассмотренный выше случай.
Я пробовал изменить параметр на l_onoff = 1, l_linger = 0. Т.е. соединение должно отправить сегмент RST и перейти в состояние CLOSED, минуя состояние TIME_WAIT. В итого, когда я закрываю сокет, то получаю либо зависание при освобождении памяти в ф-ии close, либо HardFault.
При изменении параметра на l_onoff = 1, l_linger = 1. Результат такой же, как и во втором случае.
Отсюда вопрос №2: может быть параметр SO_LINGER не работает должным образом в lwip? Или я что-то неправильно понимаю.
Ну и, наконец, подошли к третьему вопросу. После всех мытарств, решил закрывать не серверный сокет, а присоединенный сокет. И наткнулся на очередные грабли – функция accept вызывается ровно столько раз, сколько указано в параметре backlog функции listen. Т.е. если я пишу “listen(recv_serverfd, 5);”, то функция accept отрабатывает ровно 5 раз и потом зависает на веки вечные. У меня складывается впечатление, что когда я закрываю присоединенный сокет, функция close не освобождает память, и стек считает, что у меня дескриптор занят. Почему так происходит? Может быть кто-то сталкивался с подобным поведением и сможет пнуть меня в нужном направлении?
Я прикрепил файл, в котором представлен урезанный код процедуры EthernetTask. Целиком выкладывать не стал, т.к. там можно заблудиться...
Заранее спасибо.
Ethernet_task.txt