LwIP Raw/TCP |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Перед вызовом любой TCP-функции обязательно должна быть вызвана функция lwip_init(). void tcp_tmr(void); После этого вызова Вы должны вызывать tcp_tmr() через каждый интервал TCP_TMR_INTERVAL в миллисекундах (по умолчанию 250 мс). Однако с некоторого времени (версии 1.4.0), нужно вызывать одну функцию для обработки всех таймеров для всех протоколов в стеке. Добавьте вызов этой функции в главный цикл main или что-то аналогичное: sys_check_timeouts(); [Идентификация соединения] Функция tcp_arg() задает аргумент, который будет передан во все callback-функции для соединения. void tcp_arg (struct tcp_pcb * pcb, void * arg); Аргумент pcb задает блок управления соединением TCP (Protocol Control Block, сокращенно PCB), и аргумент arg это указатель на некие Ваши собственные данные. Этот аргумент можно использовать в приложении для любой цели; чаще всего Вы будете использовать этот аргумент, чтобы идентифицировать этот определенный экземпляр своего приложения. [Настройка соединения TCP] Соединение TCP идентифицируется через PCB. Есть 2 способа настройки соединения. Пассивное соединение (прослушивание порта, Listen). Пассивное соединение это сервер TCP, который ждет подключения клиентов. Процесс по шагам: 1. Вызовите tcp_new, чтобы создать PCB. Опционально вызовите tcp_arg, чтобы связать специфическое для приложения значение с этим pcb. Активное соединение. Запускает клиента TCP, который соединяется с сервером. Процесс по шагам: 1. Вызовите tcp_new, чтобы создать PCB. Опционально вызовите tcp_arg, чтобы связать специфическое для приложения значение с этим pcb. [Функции соединения TCP] struct tcp_pcb * tcp_new(void); Создает новый PCB соединения. Изначально соединение находится в состоянии "закрыто" (closed). Если не хватает памяти для создания нового PCB, то будет возвращен NULL. err_t tcp_bind(struct tcp_pcb * pcb, struct ip_addr * ipaddr, u16_t port); Привязывает PCB к локальному IP-адресу и номеру порта. IP-адрес может быть указан как IP_ADDR_ANY, чтобы привязать соединение ко всем локальным адресам IP. Если для порта указано значение 0, то функция выберет доступный (свободный) порт. Соединение должно быть в состоянии "closed". Если другое соединение уже привязано к тому же самому порту, то функция вернет ERR_USE, иначе вернет ERR_OK. struct tcp_pcb * tcp_listen(struct tcp_pcb * pcb); Эта функция устанавливает локальный порт для прослушивания локальных соединений. Параметр pcb указывает соединение, которое должно быть в состоянии "closed", и должно быть привязано к локальному порту функцией tcp_bind(). Функция tcp_listen() вернет новый PCB соединения, и тот PCB, который передавался ранее как аргумент в функцию, будет освобожден. Причина такого поведения - нужно меньше памяти для соединения, которое прослушивается, Таким образом, tcp_listen () освобождает память, необходимую для исходного соединения, и выделяет новый меньший блок памяти для прослушивающего соединения. После вызова tcp_listen() нужно вызвать tcp_accept(). Пока этого не сделаете, приходящие соединения для этого порта буду сбрасываться (abort). Функция tcp_listen() может вернуть NULL, если нет доступной памяти для прослушивающего соединения. Если это произошло, то не будет освобождена память, связанная с переданным в качестве аргумента pcb в вызов tcp_listen(). struct tcp_pcb * tcp_listen_with_backlog(struct tcp_pcb * pcb, u8_t backlog); То же самое, что и tcp_listen(), но ограничивает количество ожидающих соединений в очереди прослушивания значением, указанным аргументом ожидания backlog. Чтобы использовать это, нужно установить TCP_LISTEN_BACKLOG=1 в файле настроек lwipopts.h. void tcp_accept(struct tcp_pcb * pcb, err_t (* accept)(void * arg, struct tcp_pcb * newpcb, err_t err)); Дает команду для PCB запустить прослушивание входящих соединений. Перед этим должна быть вызвана tcp_listen(). Когда новое соединение поступило на локальный порт, будет вызвана указанная в аргументе функция с PCB для нового соединения. Backlog: до 2.0 был необходим callback для подтверждения соединения. void tcp_accepted(struct tcp_pcb * pcb); Информирует lwIP, что входящее соединение было принято. Обычно это вызывается из called-функции принятия соединения (accept callback). Это позволило lwIP выполнять задачи обслуживания, например, разрешить поставить дальнейшие входящие соединения в очередь ожидания прослушивания (backlog). Параметр pcb это прослушивающий PCB, не новое соединение. Это устарело в пользу реализации новой схемы соединения. err_t tcp_connect(struct tcp_pcb * pcb, struct ip_addr * ipaddr, u16_t port, err_t (* connected)(void * arg, struct tcp_pcb * tpcb, err_t err)); Настраивает PCB для соединения с сетевым хостом, и посылает начальный сегмент SYN, открывающий соединение. Если соединение не было уже привязано к локальному порту, то для этого будет назначен локальный порт. Функция tcp_connect() вернет управление немедленно, она не ждет, пока соединение будет должным образом установлено. Вместо этого она вызовет функцию, указанную как четвертый аргумент (аргумент connected), когда соединение установится. Если соединение не установилось должным образом (либо из-за отказа другого хоста, либо из-за того, что другой хост не ответил), то будет вызвана функция обработки ошибки с соответствующим образом установленным аргументом err. Функция tcp_connect() может возвратить ERR_MEM, если не было доступной памяти для постановки в очередь сегмента SYN. Если SYN был действительно успешно поставлен в очередь, то функция tcp_connect() вернет ERR_OK. [Отправка данных TCP] Чтобы отправить данные на соединении TCP: 1. Вызовите tcp_sent(), чтобы указать callback-функцию для подтверждений. u16_t tcp_sndbuf(struct tcp_pcb * pcb); Вернет количество места для байт, доступного в очереди вывода (output queue). err_t tcp_write(struct tcp_pcb * pcb, void * dataptr, u16_t len, u8_t apiflags); Ставит в очередь данные, на которые указывает аргумент dataptr. Длина данных передается через параметр len. Аргумент apiflags может иметь один из следующих бит: TCP_WRITE_FLAG_COPY показывает, что lwIP должна выделить новый блок памяти и скопировать туда данные. Если этот бит не указан, то новая память не будет выделена, и к данным будет осуществляться обращение только по указателю. TCP_WRITE_FLAG_MORE показывает, что в сегменте TCP не должен быть установлен флаг push. Функция tcp_write() потерпит сбой и вернет ERR_MEM, если длина данных превышает размер текущего буфера отправки, или если дина очереди исходящего сегмента больше, чем верхний предел, заданный в файле опций lwipopts.h (TCP_SND_QUEUELEN). Если функция вернула ERR_MEM, то приложение должно подождать, пока некоторые поставленные ранее в очередь данные не будут успешно приняты другим хостом (при этом место в очереди передачи освободится), после чего сделать новую попытку. err_t tcp_output(struct tcp_pcb * pcb); Принуждает немедленно отправить все поставленные в очередь данные. void tcp_sent(struct tcp_pcb * pcb, err_t (* sent)(void * arg, struct tcp_pcb * tpcb, u16_t len)); Указывает callback-функцию, которая должна быть вызвана, когда данные были подтверждены другим хостом. Аргумент len, переданный в callback-функцию, дает количество байт, которое было подтверждено в последнем подтверждении. [Прием данных TCP] Получение данных TCP основано на вызове callback-функции. Эта функция специфична для приложения, она будет вызвана, когда поступят новые данные. Протокол TCP задает окно, которое говорит хосту отправки, сколько данных он может послать через соединение. Размер окна для всех соединений определено константой TCP_WND, которая может быть переназначена в файле опций lwipopts.h. Когда приложение обработало поступившие данные, оно должно вызвать функцию tcp_recved() чтобы показать, что TCP может увеличить окно приема. void tcp_recv(struct tcp_pcb * pcb, err_t (* recv)(void * arg, struct tcp_pcb * tpcb, struct pbuf * p, err_t err)); Установит callback-функцию, которая будет вызвана при поступлении новых данных. Если не было ошибок, и callback-функция вернула ERR_OK, то она отвечает за освобождение pbuf. Иначе она не должна освобождать pbuf, чтобы код ядра lwIP мог хранить его. Если другой хост закрыл соединение, то callback-функция будет вызвана с NULL pbuf для индикации этого факта. void tcp_recved(struct tcp_pcb * pcb, u16_t len); Должна быть вызвана, когда приложение обработало данные, и приготовилось к получению большего количества данных. Цель этого - объявление окна большего размера, когда данные были обработаны. Аргумент len показывает длину обработанных данных. [Опрос приложения (polling)] Когда соединение в состоянии ожидания (то есть данные либо не передаются, либо не принимаются), lwIP будет постоянно опрашивать приложение путем вызова указанной callback-функции. Это может использоваться либо как сторожевой таймер, чтобы прибить соединения, которые слишком долго простаивают, или как метод ожидания освобождения памяти. Например, если вызов tcp_write() был неудачен из-за недоступности свободной памяти, приложение может использовать функционал опроса для вызова tcp_write() снова, когда соединение некоторое время бездействовало. void tcp_poll(struct tcp_pcb * pcb, err_t (* poll)(void * arg, struct tcp_pcb * tpcb), u8_t interval); Задает интервал опроса и callback-функцию, которая должна вызваться для опроса приложения. Интервал задается в количестве грубых интервалов срабатывания таймера TCP, которые обычно происходят дважды в секунду. Интервал 10 означает, что приложение будет опрашиваться каждые 5 секунд. [Закрытие и обрыв соединения] err_t tcp_close(struct tcp_pcb * pcb); Закрывает соединение. Функция может возвратить ERR_MEM, если не было было доступной памяти для закрытия соединения. Если это произошло, то приложение должно подождать и попробовать снова, либо использовать acknowledgment callback или функционал опроса. Если закрытие было успешно, то функция вернет ERR_OK. После вызова tcp_close() кодом TCP освобождается PCB. Обратите внимание, что данные все еще могут приниматься на закрытом соединении, пока другой хост не подтвердит закрытие. void tcp_abort(struct tcp_pcb * pcb); Обрывает соединение отправкой сегмента RST (reset) другому хосту. Блок PCB освобождается. Эта функция никогда не заканчивается неудачей. ВНИМАНИЕ: когда эта функция вызывается из одной из callback-функций TCP, убедитесь, что всегда возвращаете ERR_ABRT, иначе есть риск доступа к освобожденной памяти или утечки памяти! [Обработчик ошибки] Если соединение было оборвано из-за ошибки, или попытка соединения потерпела неудачу (либо таймаут, либо reset), то приложение оповещается об этом событии через err callback. Ошибки, которые могут оборвать соединение, включают нехватку памяти. Вызываемая callback-функция устанавливается функцией tcp_err(). void tcp_err(struct tcp_pcb * pcb, void (* err)(void * arg, err_t err)); Функция error callback не получает pcb, переданный как параметр, потому что PCB может быть уже освобожден. Если Вам нужно узнать что-нибудь о своем приложении внутри этой функции, то используйте параметр arg, как в любой другой функции. Алгоритм Найгла. Краткий обзор см. в Википедии [3]. // Разрешает алгоритм nagle: tcp_nagle_enable ( struct tcp_pcb * aPcb ); // Запрещает алгоритм nagle: tcp_nagle_disable ( struct tcp_pcb * aPcb ); // Возвратит true, если алгоритм не разрешен: tcp_nagle_disabled ( struct tcp_pcb * aPcb ); Пример (для xNetCann, created with NETCONN_TCP): tcp_nagle_enable ( xNetConn->pcb.tcp );
Если эти макросы не определены в Вашей версии lwIP, то нужно обновится до самой новой версии (2.1.2 на момент перевода документации [1]). Если Вы не можете сделать обновление, то используйте (как аварийную замену): xNewConn->pcb.tcp->flags |= TF_NODELAY для запрета TCP Keepalive. В файл lwipopts.h нужно добавить: #define LWIP_TCP_KEEPALIVE 1
Тогда на каждом сокете TCP, для которого Вам нужна поддержка keepalive, это нужно разрешить: /* Включение TCP Keepalive для этого pcb: */ pcb->so_options |= SOF_KEEPALIVE; /* Если Вам нужно поменять время между сообщениями keep alive.
Время устанавливается в миллисекундах: */ pcb->keep_intvl = 75000; /* 75 секунд */ [Диаграмма последовательности действий примера Raw TCP] Из-за того, что raw-реализация TCP предназначена для выполнения главным образом через callback-функции, его работа как правило тесно связана с приемом и обработкой отдельных сообщений. Следовательно, полезно иметь по крайней мере некоторое знакомство с протоколом TCP низкого уровня. Для тех, у кого нет предыдущего опыта работы с lwIP, иногда не ясно, какие делать вызовы функций, и когда. Следующая таблица показывает диаграмму взаимодействия между другим компьютером в сети (remote client) и локальным сервером lwIP. Взаимодействие показано для типичного (успешного) соединения по протоколу запрос-ответ (такому как например HTTP).
Примечания: tcp_write() просто ставит данные TCP в очередь для последующей передачи; этот вызова не начинает реальную передачу. Тем не менее, когда tcp_write() вызывается из callback-функции приема (recv), как в этом примере, нет необходимости вызывать tcp_output() для начала передачи отправляемых данных (действительно, tcp_output() специально отказывается делать что-либо, если она вызывается из recv callback). После возврата из recv callback стек автоматически начнет передавать любые данные - и ACK для предыдущего пакета сетевого клиента объединяется с первым сегментом исходящих данных. Если tcp_write() вызывается из какого-то другого места (возможно как результат некоего события вне обработки lwIP), то может быть необходимо вызвать tcp_output, чтобы инициировать передачу данных.
Примечания: (†) Callback-функции приема (recv) и передачи (sent) могут быть установлены после установки соединения (т. е. в callback-функции connected), если это необходимо. (*) Обратите внимание, что вызов tcp_output в действительности не нужен, если данные записаны клиентом в callback-функции connected, поскольку lwIP будет автоматически генерировать ACK после возврата из callback-функции. В других случаях это может потребоваться, однако см. также примечания к предыдущей таблице. В случае неудачного соединения соединяющийся клиент будет оповещен об этом через error callback-функцию, которая была установлена вызовом tcp_err(). Завершение сессии (сценарий 1 - shutdown со стороны удаленного сетевого клиента):
Завершение сессии (сценарий 2 - shutdown со стороны локального сервера):
Примечание (*): обратите внимание, что lwIP может запустить callback-функцию приема сервера recv после вызова tcp_close(). Если Вы не хотите принять данные, то обязательно обнуляйте callback-функцию приема recv (т. е. вызовите tcp_recv с NULL-указателем). [Ссылки] 1. LwIP Application Developers Manual Raw/TCP site:lwip.fandom.com. |