Разберём процесс установки TCP-соединения в стеке lwIP по шагам, на примере кода из статьи «JSON-транспорт между микроконтроллером и сервером». Я буду сначала писать код, а потом давать к нему комментарии.
Инициализация
void InitJSON()
{
struct ip_addr server_ip;
IP4_ADDR(&server_ip, 192,168,1,152);
struct tcp_pcb *TCPSockJSON;
TCPSockJSON = tcp_new();
tcp_recv(TCPSockJSON, json_receive);
tcp_sent(TCPSockJSON, json_client_sent);
tcp_connect(TCPSockJSON, &server_ip, 9090, json_connected);
}
Стек lwIP устроен по принципу конечного автомата. При наступлении определённых событий (подключение, приём данных, ошибки) он вызывает функции-обработчики этих событий, и на старте вы должны передать ему адреса этих функций.
Структура TCPSockJSON — ядро соединения, именно в ней будут лежать все настройки и состояние подключения. Она будет передана во все функции-обработчики событий. Первое что нужно сделать — проинициализировать её функцией lwIP tcp_new.
Далее расставляем обработчики интересующих нас событий. Это будут:
- Успешное подключение — обработчик json_connected
- Приём данных — обработчик json_receive
- Передача данных — обработчик json_client_sent
Функция tcp_connect подключается к серверу (IP = &server_ip, порт = 9090) и сразу вызывает обработчик json_connected.
Обработчик подключения
static err_t json_connected(void *arg, struct tcp_pcb *pcb, err_t err)
{
char *string = "{\"hop\": 0}";
LWIP_UNUSED_ARG(arg);
if(err == ERR_OK)
{
tcp_write(pcb, string, strlen(string), 0);
tcp_output(pcb);
}
return err;
}
Сразу после подключения проверяем ошибки, и если всё ОК — передаём серверу строку «{«hop»: 0}». Это — приветственная строка, сообщение «я включился и со мной всё нормально». Здесь можно просто передать какой-нибудь статус устройства, к примеру.
Для записи данных в буфер передачи используем функцию tcp_write, ей требуется сокет, строка и её длина. Для реальной передачи данных — функция tcp_output. Вы можете накопить в буфере много данных после нескольких вызовов функции tcp_write, и потом отправить всё одним пакетом с помощью tcp_output.
lwIP вызывает обработчики по цепочке, поэтому в конце обязательно передаём ошибку дальше: return err.
Приём данных
static err_t json_receive(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
LWIP_UNUSED_ARG(arg);
if(err == ERR_OK && p != NULL)
{
char *string = p->payload;
string[p->len] = 0;
processServerJSON(pcb, cJSON_Parse(string));
tcp_recved(pcb, p->tot_len);
pbuf_free(p);
}
else
json_client_close(pcb);
return ERR_OK;
}
Снова проверяем ошибки, если их нет — обрабатываем принятые данные.
Сами данные лежат в поле p->payload, а их длина — в p->len. Мы знаем что принимаем от сервера не абстрактные данные, а именно строку — и чтобы сделать из этого нормальную строку, просто добавим в её конце (по адресу p->len) терминирующий ноль. Это не очень правильно, потому что так мы лезем прямо в структуру p, и можем испортить данные, лежащие сразу за строкой. Лучше скопировать строку через memcpy.
Далее парсим принятый текст в JSON функцией библиотеки cJSON (cJSON_Parse), и обработаем его нашей функцией processServerJSON.
Забрав все данные, скажем lwIP что данные получены и их можно стирать и принимать новые. Для этого вызовем функцию tcp_recved. Потом просто освободим память, занимаемую буфером p.
В случае ошибок при приёме — вызовем нашу функцию json_client_close.
В конце — передадим код ошибки дальше.
Закрытие соединения
static void json_client_close(struct tcp_pcb *pcb)
{
tcp_arg(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_close(pcb);
}
Сбросим обработчики состояний (передав NULL в качестве адресов обработчиков) и закроем соединение функцией tcp_close. Всё просто.
Обработка принятых данных
static void processServerJSON(struct tcp_pcb *pcb, cJSON *json)
{
cJSON *json_out = cJSON_CreateObject();
cJSON_AddNumberToObject(json_out, "data", 500);
char *json_string = cJSON_Print(json_out);
tcp_write(pcb, json_string, strlen(json_string), 0);
tcp_output(pcb);
free(json_string);
cJSON_Delete(json_out);
cJSON_Delete(json);
}
Принимаем готовый объект cJSON, создаём новый объект cJSON, добавляем в него поле data и превращаем его в строку.
Для передачи — снова вызываем tcp_write/tcp_output.
В конце — ни в коем случае не забывайте удалять весь мусор! К примеру, код с неудалёнными cJSON-объектами проработает всего лишь в течение 500 циклов приёма/передачи и память закончится.
Забыв об освобождении строки json_string вы обеспечите падение после 2-3 тысяч циклов приёма-передачи.
Обработчик передачи
static err_t json_client_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
LWIP_UNUSED_ARG(arg);
return ERR_OK;
}
Просто передаём код ошибки дальше, никаких действий здесь не требуется.
Свежие комментарии