Перед вызовом любой TCP-функции обязательно должна быть вызвана функция lwip_init().
voidtcp_tmr(void);
После этого вызова Вы должны вызывать tcp_tmr() через каждый интервал TCP_TMR_INTERVAL в миллисекундах (по умолчанию 250 мс).
Однако с некоторого времени (версии 1.4.0), нужно вызывать одну функцию для обработки всех таймеров для всех протоколов в стеке. Добавьте вызов этой функции в главный цикл main или что-то аналогичное:
sys_check_timeouts();
[Идентификация соединения]
Функция tcp_arg() задает аргумент, который будет передан во все callback-функции для соединения.
voidtcp_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. 2. Вызовите tcp_bind, чтобы указать локальный IP-адрес и порт TCP. 3. Вызовите tcp_listen или tcp_listen_with_backlog. Замечание: эти функции освободят PCB, предоставленный как аргумент, и возвратят прослушивающий pcb меньшего размера (listener pcb, например tpcb = tcp_listen(tpcb)). 4. Вызовите tcp_accept, чтобы указать функцию, которая должна быть вызвана при поступлении нового соединения. Обратите внимание, что нет возможности принять/подтвердить сокет до указания callback, потому что это все работает на tcpip_thread.
Активное соединение. Запускает клиента TCP, который соединяется с сервером. Процесс по шагам:
1. Вызовите tcp_new, чтобы создать PCB. Опционально вызовите tcp_arg, чтобы связать специфическое для приложения значение с этим pcb. 2. Опционально вызовите tcp_bind, чтобы указать локальный IP-адрес и TCP-порт. 3. Вызовите tcp_connect.
[Функции соединения TCP]
struct tcp_pcb *tcp_new(void);
Создает новый PCB соединения. Изначально соединение находится в состоянии "закрыто" (closed). Если не хватает памяти для создания нового PCB, то будет возвращен NULL.
Привязывает 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().
То же самое, что и tcp_listen(), но ограничивает количество ожидающих соединений в очереди прослушивания значением, указанным аргументом ожидания backlog. Чтобы использовать это, нужно установить TCP_LISTEN_BACKLOG=1 в файле настроек lwipopts.h.
Дает команду для PCB запустить прослушивание входящих соединений. Перед этим должна быть вызвана tcp_listen(). Когда новое соединение поступило на локальный порт, будет вызвана указанная в аргументе функция с PCB для нового соединения.
Backlog: до 2.0 был необходим callback для подтверждения соединения.
voidtcp_accepted(struct tcp_pcb * pcb);
Информирует lwIP, что входящее соединение было принято. Обычно это вызывается из called-функции принятия соединения (accept callback). Это позволило lwIP выполнять задачи обслуживания, например, разрешить поставить дальнейшие входящие соединения в очередь ожидания прослушивания (backlog). Параметр pcb это прослушивающий PCB, не новое соединение.
Это устарело в пользу реализации новой схемы соединения.
Настраивает PCB для соединения с сетевым хостом, и посылает начальный сегмент SYN, открывающий соединение. Если соединение не было уже привязано к локальному порту, то для этого будет назначен локальный порт.
Функция tcp_connect() вернет управление немедленно, она не ждет, пока соединение будет должным образом установлено. Вместо этого она вызовет функцию, указанную как четвертый аргумент (аргумент connected), когда соединение установится. Если соединение не установилось должным образом (либо из-за отказа другого хоста, либо из-за того, что другой хост не ответил), то будет вызвана функция обработки ошибки с соответствующим образом установленным аргументом err.
Функция tcp_connect() может возвратить ERR_MEM, если не было доступной памяти для постановки в очередь сегмента SYN. Если SYN был действительно успешно поставлен в очередь, то функция tcp_connect() вернет ERR_OK.
[Отправка данных TCP]
Чтобы отправить данные на соединении TCP:
1. Вызовите tcp_sent(), чтобы указать callback-функцию для подтверждений. 2. Вызовите tcp_sndbuf(), чтобы найти максимальное количество данных, которое можно отправить. 3. Вызовите tcp_write(), чтобы поставить данные в очередь. 4. Вызовите tcp_output(). чтобы принудительно отправить данные.
u16_ttcp_sndbuf(struct tcp_pcb * pcb);
Вернет количество места для байт, доступного в очереди вывода (output queue).
Ставит в очередь данные, на которые указывает аргумент 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_ttcp_output(struct tcp_pcb * pcb);
Принуждает немедленно отправить все поставленные в очередь данные.
Указывает callback-функцию, которая должна быть вызвана, когда данные были подтверждены другим хостом. Аргумент len, переданный в callback-функцию, дает количество байт, которое было подтверждено в последнем подтверждении.
[Прием данных TCP]
Получение данных TCP основано на вызове callback-функции. Эта функция специфична для приложения, она будет вызвана, когда поступят новые данные.
Протокол TCP задает окно, которое говорит хосту отправки, сколько данных он может послать через соединение. Размер окна для всех соединений определено константой TCP_WND, которая может быть переназначена в файле опций lwipopts.h. Когда приложение обработало поступившие данные, оно должно вызвать функцию tcp_recved() чтобы показать, что TCP может увеличить окно приема.
Установит callback-функцию, которая будет вызвана при поступлении новых данных. Если не было ошибок, и callback-функция вернула ERR_OK, то она отвечает за освобождение pbuf. Иначе она не должна освобождать pbuf, чтобы код ядра lwIP мог хранить его.
Если другой хост закрыл соединение, то callback-функция будет вызвана с NULL pbuf для индикации этого факта.
voidtcp_recved(struct tcp_pcb * pcb, u16_t len);
Должна быть вызвана, когда приложение обработало данные, и приготовилось к получению большего количества данных. Цель этого - объявление окна большего размера, когда данные были обработаны. Аргумент len показывает длину обработанных данных.
[Опрос приложения (polling)]
Когда соединение в состоянии ожидания (то есть данные либо не передаются, либо не принимаются), lwIP будет постоянно опрашивать приложение путем вызова указанной callback-функции. Это может использоваться либо как сторожевой таймер, чтобы прибить соединения, которые слишком долго простаивают, или как метод ожидания освобождения памяти. Например, если вызов tcp_write() был неудачен из-за недоступности свободной памяти, приложение может использовать функционал опроса для вызова tcp_write() снова, когда соединение некоторое время бездействовало.
Задает интервал опроса и callback-функцию, которая должна вызваться для опроса приложения. Интервал задается в количестве грубых интервалов срабатывания таймера TCP, которые обычно происходят дважды в секунду. Интервал 10 означает, что приложение будет опрашиваться каждые 5 секунд.
[Закрытие и обрыв соединения]
err_ttcp_close(struct tcp_pcb * pcb);
Закрывает соединение. Функция может возвратить ERR_MEM, если не было было доступной памяти для закрытия соединения. Если это произошло, то приложение должно подождать и попробовать снова, либо использовать acknowledgment callback или функционал опроса. Если закрытие было успешно, то функция вернет ERR_OK.
После вызова tcp_close() кодом TCP освобождается PCB.
Обратите внимание, что данные все еще могут приниматься на закрытом соединении, пока другой хост не подтвердит закрытие.
voidtcp_abort(struct tcp_pcb * pcb);
Обрывает соединение отправкой сегмента RST (reset) другому хосту. Блок PCB освобождается. Эта функция никогда не заканчивается неудачей.
ВНИМАНИЕ: когда эта функция вызывается из одной из callback-функций TCP, убедитесь, что всегда возвращаете ERR_ABRT, иначе есть риск доступа к освобожденной памяти или утечки памяти!
[Обработчик ошибки]
Если соединение было оборвано из-за ошибки, или попытка соединения потерпела неудачу (либо таймаут, либо reset), то приложение оповещается об этом событии через err callback. Ошибки, которые могут оборвать соединение, включают нехватку памяти. Вызываемая callback-функция устанавливается функцией tcp_err().
Функция error callback не получает pcb, переданный как параметр, потому что PCB может быть уже освобожден. Если Вам нужно узнать что-нибудь о своем приложении внутри этой функции, то используйте параметр arg, как в любой другой функции.
// Возвратит 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 для запрета xNewConn->pcb.tcp->flags &= ~TF_NODELAY для разрешения ( xNewConn->pcb.tcp->flags & TF_NODELAY ) != 0 для опроса, разрешено или нет
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).
Сервер устанавливает новую callback-функцию передачи (sent).
← Сервер возвращается из callback-функции accept со статусом OK.
(пометка блока PCB активным)
Соединение установлено (теперь данные могут быть отправлены в любую сторону)
send →
Данные TCP →
Клиент отправил запрос данных.
lwIP запускает callback-функцию приема сервера recv
← tcp_write(response_data, len)
Сервер записал данные ответа для клиента.
lwIP ставит в очередь сегмент TCP
← tcp_write(response_data2, len2)
Сервер записал еще данные для клиента.
lwIP ставит в очередь сегмент TCP
Сегмент может комбинироваться с предыдущим сегментом.
← tcp_recved()
Сервер уведомляет lwIP об объявлении окна большего размера.
← Сервер возвращает управление из callback-функции приема recv со статусом OK.
← Данные TCP
lwIP ищет поставленные в очередь сегменты для отправки
lwIP отправляет сегмент (сегменты) данных клиенту, включая ACK для ранее принятых данных клиента.
Примечания: tcp_write() просто ставит данные TCP в очередь для последующей передачи; этот вызова не начинает реальную передачу. Тем не менее, когда tcp_write() вызывается из callback-функции приема (recv), как в этом примере, нет необходимости вызывать tcp_output() для начала передачи отправляемых данных (действительно, tcp_output() специально отказывается делать что-либо, если она вызывается из recv callback). После возврата из recv callback стек автоматически начнет передавать любые данные - и ACK для предыдущего пакета сетевого клиента объединяется с первым сегментом исходящих данных. Если tcp_write() вызывается из какого-то другого места (возможно как результат некоего события вне обработки lwIP), то может быть необходимо вызвать tcp_output, чтобы инициировать передачу данных.
Установка сессии lwIP (локальный клиент / сетевой удаленный сервер lwIP)
← tcp_new()
Создается TCP PCB.
← [tcp_bind()]
Опционально: привязка к определенному номеру порта и/или IP-адресу.
← tcp_arg()
Выделяет специфичную для клиента структуру сессии, устанавливая её как аргумент callback-функции.
← tcp_err()
Устанавливает callback-функцию error/abort (используется для сигнализации о неудачном соединении).
← tcp_arg()
Устанавливает аргумент callback-функции (указатель на структуру данных сервера).
← tcp_recv()
Устанавливает callback-функцию приема recv(†).
← tcp_sent()
Сервер устанавливает callback-функцию передачи sent(†).
← tcp_connect()
Соединение, с предоставлением callback-фунции connected оповещения о завершении соединения.
← SYN
← lwIP генерирует SYN
lwIP генерирует SYN-пакет для удаленного сетевого сервера.
SYN/ACK →
Удаленный сетевой сервер отправляет SYN/ACK.
lwIP запускает callback-функцию connected →
С точки зрения lwIP сессия теперь установлена, будет сгенерирован завершающий ACK трехстороннего рукопожатия TCP при возврате из callback-функции.
Соединение установлено (теперь данные могут быть отправлены в любую сторону)
← tcp_write(request_data, len)
Клиент записал данные запроса для сервера.
lwIP ставит в очередь сегмент TCP
← tcp_write(request_data2, len2)
Клиент записал еще данные для сервера.
lwIP ставит в очередь сегмент TCP
Сегмент может комбинироваться с предыдущим сегментом.
← tcp_output()
Клиент уведомляет lwIP о реальной генерации отправляемых пакетов(*).
← Клиент возвращает управление из callback-фукнции connected.
← Данные TCP
lwIP генерирует один или большее количество пакетов данных.
send →
Данные TCP →
lwIP запускает callback-функцию приема клиента recv
См. предыдущую таблицу.
Примечания:
(†) Callback-функции приема (recv) и передачи (sent) могут быть установлены после установки соединения (т. е. в callback-функции connected), если это необходимо.
(*) Обратите внимание, что вызов tcp_output в действительности не нужен, если данные записаны клиентом в callback-функции connected, поскольку lwIP будет автоматически генерировать ACK после возврата из callback-функции. В других случаях это может потребоваться, однако см. также примечания к предыдущей таблице.
В случае неудачного соединения соединяющийся клиент будет оповещен об этом через error callback-функцию, которая была установлена вызовом tcp_err().
Завершение сессии (сценарий 1 - shutdown со стороны удаленного сетевого клиента):
Remote client
Сообщение TCP
Действие стека lwIP
Действие сервера lwIP
Описание
close или shutdown (SHUT_WR) →
Клиент завершает запись конца сокета.
FIN →
Удаленный клиент посылает пакет с установленным битом FIN.
← ACK
lwIP распознает FIN, немедленно генерируя ACK
lwIP PCB входит в состояние CLOSE_WAIT.
lwIP запускает callback-функцию приема сервера recv с аргументом pbuf, равным NULL
lwIP сигнализирует серверу об окончании файла (end-of-file)
[← tcp_write()]
Сервер отправил последние данные данные ответа для клиента (если они есть).
...
← tcp_close()
Сервер освобождает приватные структуры данных, завершая соединение.
← FIN
lwIP генерирует FIN
lwIP оповещает удаленного сетевого клиента, что сервер закрыл соединение (PCB входит в состояние LAST_ACK).
ACK →
Клиент подтверждает последний FIN.
lwIP обрабатывает последний ACK
PCB входит в состояние CLOSED (и затем освобождается).
Завершение сессии (сценарий 2 - shutdown со стороны локального сервера):
Remote client
Сообщение TCP
Действие стека lwIP
Действие сервера lwIP
Описание
← tcp_close()
Сервер завершает соединение.
← FIN
lwIP генерирует пакет FIN
PCB входит в состояние FIN_WAIT_1
ACK →
lwIP обновляет PCB
PCB входит в состояние FIN_WAIT_2.
[send] →
[Данные TCP] →
Сервер (опционально) отправил последние данные(*).
...
close →
Клиент закрывает сокет.
FIN →
Удаленный сетевой клиент отправляет пакет FIN.
→ ACK
← lwIP распознает FIN, генерируя ACK
lwIP PCB входит в состояние TIME_WAIT.
lwIP запускает callback-функцию приема сервера recv с аргументом pbuf, равным NULL →
Клиент сигнализирует серверу lwIP об окончании файла (end-of-file)
Сервер освобождает приватные структуры данных.
...
Истекло время таймера TIME_WAIT
lwIP освобождает PCB.
Примечание (*): обратите внимание, что lwIP может запустить callback-функцию приема сервера recv после вызова tcp_close(). Если Вы не хотите принять данные, то обязательно обнуляйте callback-функцию приема recv (т. е. вызовите tcp_recv с NULL-указателем).