В предыдущей части урока мы познакомились с технологией WebSocket, настроили проект и подготовили страницу для клиента.
Теперь нам надо добавить функционал в автоматически созданную задачу для вывода данных на дисплей TaskStringOut.
Вот это, соответственно, мы вставим до бесконечного цикла
1 2 3 4 |
/* USER CODE BEGIN TaskStringOut */ osEvent event; struct_out *qstruct; /* Infinite loop */ |
А вот это в бесконечный цикл
1 2 3 4 5 6 7 8 9 10 11 12 |
/* Infinite loop */ for(;;) { event = osMailGet(strout_Queue, osWaitForever); if (event.status == osEventMail) { qstruct = event.value.p; sprintf(str_out,"%s", qstruct->str); TFT_DisplayString(50, qstruct->y_pos, (uint8_t *)str_out, LEFT_MODE); } } /* USER CODE END TaskStringOut */ |
Также мы с вами видим, что у нас исчезла инициализация стека протоколов, вместо неё теперь здесь вот это
MX_MBEDTLS_Init();
если мы зайдём вовнутрь данной функции, то заметим, что она практически пустая, там есть пользовательские заготовки, то есть можно инициализировать LWIP и в ней, но мы это сделаем в тексте задачи по умолчанию StartDefaultTask в пользовательской части
1 2 |
/* USER CODE BEGIN 5 */ MX_LWIP_Init(); |
Надеюсь, все мы понимаем, что без этого наша плата не будет осуществлять обмен данными по сети.
Функции DynWebPageStr и DynWebPage удалим вместе с телами, теперь мы данные передавать будем без таких огромных заголовков.
В функции сортировки StrSort изменим имя массива, применяя здесь уже массив в памяти SDRAM
strarray_ptr *strarray1 = (void*) str_buf3;
Функцию tcp_thread мы переименуем в http_thread, так как здесь у нас в основном происходит обмен по такому протоколу.
Причём тело из данной функции мы также полностью удалим, здесь будет сильная реструктуризация кода.
Также внесём изменения и в функции задачи по умолчанию, где также весь пользовательский код до бесконечного цикла, кроме инициализации LWIP, мы сначала также удалим.
Добавим сюда только строку создания задачи по обработке пакетов HTTP
1 2 |
MX_LWIP_Init(); sys_thread_new("http_thread", http_thread, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityNormal ); |
Начнём писать код функции созданной задачи http_thread.
Сначала добавим все переменные, массивы и структуры, которые нам потребуются в данной функции
1 2 3 4 5 6 7 8 9 10 |
static void http_thread(void *arg) { struct_out *qstruct; err_t err, recv_err; struct netconn *conn; struct netbuf *inbuf; struct netconn *newconn; u16_t buflen; char* buf; struct fs_file file; |
Создание соединения мы сейчас будем полностью осуществлять в данной задаче, не разделяя по двум задачам. Раньше мы основную структуру инициализировали в задаче по умолчанию.
Далее применим другой цвет вывода информации на дисплей и создадим основную структуру соединения
1 2 3 |
struct fs_file file; TFT_SetTextColor(LCD_COLOR_BLUE); conn = netconn_new(NETCONN_TCP); |
Если со структурой всё нормально, то свяжем её с локальным портом
1 2 3 4 5 |
conn = netconn_new(NETCONN_TCP); if(conn!=NULL) { err = netconn_bind(conn, NULL, 80); } |
А теперь выше функции задачи http_thread добавим функцию с предыдущим именем — это у нас будет функция задачи по обработке соединения для WebSocket. Сразу добавим в данную функцию все локальные переменные, структуры и всякие указатели, которые нам будут нужны в последствии и также аналогичным образом создадим соединение и свяжем её с другим локальным портом, который у нас и будет служить для обмена по соединению WebSocket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//--------------------------------------------------------------- static void tcp_thread(void *arg) { struct_out *qstruct; err_t err, recv_err; struct netconn *conn; struct netbuf *inbuf; struct netconn *newconn; u16_t buflen; char* buf; char *istr; size_t len; char str_msg = 0; conn = netconn_new(NETCONN_TCP); if(conn!=NULL) { err = netconn_bind(conn, NULL, 8080); } } //--------------------------------------------------------------- |
Вернёмся в функцию http_thread, где при успешной связи соединения с портом создадим задачу для WebSocket и начнём слушать наш порт
1 2 3 4 5 6 |
err = netconn_bind(conn, NULL, 80); if (err == ERR_OK) { sys_thread_new("tcp_thread", tcp_thread, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityNormal ); netconn_listen(conn); } |
В противном случае мы удалим структуру соединения
1 2 3 4 5 6 |
netconn_listen(conn); } else { netconn_delete(conn); } |
Вернёмся в тело положительного исхода ситуации и добавим туда бесконечный цикл, в котором свяжем основную структуру со структурой соединения с конкретным клиентом
1 2 3 4 5 |
netconn_listen(conn); for(;;) { err = netconn_accept(conn, &newconn); } |
Если всё нормально, то начинаем ждать от клиента пакет TCP
1 2 3 4 5 |
err = netconn_accept(conn, &newconn); if (err == ERR_OK) { recv_err = netconn_recv(newconn, &inbuf); } |
А если что-то не так, то пока уйдём в бесконечный цикл и там уже будем разбираться в ситуации, если что
1 2 3 4 5 6 |
recv_err = netconn_recv(newconn, &inbuf); } else { osDelay(1); } |
Проверим, что наш пакет пришёл нормально, и, пока не начиная писать код тела условия, по окончанию данного тела, освободим память буфера, разъединимся с клиентом и уничтожим структуру, чтобы потом не забыть
1 2 3 4 5 6 7 |
recv_err = netconn_recv(newconn, &inbuf); if (recv_err == ERR_OK) { } netconn_close(newconn); netbuf_delete(inbuf); netconn_delete(newconn); |
Теперь заходим в тело условия и, произведя ещё одну проверку, считаем буфер пришедшего пакета, а вернее свяжем его с нашим указателем, откуда потом и будем данный буфер читать
1 2 3 4 5 6 |
if (recv_err == ERR_OK) { if (netconn_err(newconn) == ERR_OK) { netbuf_data(inbuf, (void**)&buf, &buflen); } |
А дальше мы, проверяя то, что нам пришло от клиента, возвращаем ему пакеты с нашими документами и также библиотекой для графика
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
netbuf_data(inbuf, (void**)&buf, &buflen); if ((buflen >=5) && (strncmp(buf, "GET /", 5) == 0)) { qstruct = osMailAlloc(strout_Queue, osWaitForever); qstruct->y_pos = 250; sprintf(qstruct->str,"%-20s", "Connect"); osMailPut(strout_Queue, qstruct); osDelay(1); qstruct->y_pos = 90; sprintf(qstruct->str,"%-20s", " "); osMailPut(strout_Queue, qstruct); osDelay(1); if ((strncmp((char const *)buf,"GET / ",6)==0)||(strncmp((char const *)buf,"GET /index.html",15)==0)) { fs_open(&file, "/index.html"); netconn_write(newconn, (const unsigned char*)(file.data), (size_t)file.len, NETCONN_NOCOPY); fs_close(&file); } else if (strncmp((char const *)buf,"GET /js/Chart.min.js",20)==0) { fs_open(&file, "/js/Chart.min.js"); netconn_write(newconn, (const unsigned char*)(file.data), (size_t)file.len, NETCONN_NOCOPY); fs_close(&file); } else { //Load Error page fs_open(&file, "/404.html"); netconn_write(newconn, (const unsigned char*)(file.data), (size_t)file.len, NETCONN_NOCOPY); fs_close(&file); qstruct->y_pos = 90; sprintf(qstruct->str,"%-20s", "file not found"); osMailPut(strout_Queue, qstruct); } if ((buflen >=20)) buf[20] = 0; else buf[buflen] = 0; qstruct->y_pos = 45; sprintf(qstruct->str,"%-20s", buf); osMailPut(strout_Queue, qstruct); osMailFree(strout_Queue, qstruct); } |
Это практически код прошлого занятия, только из него мы убрали передачу рисунков, а также динамических страниц.
Теперь начнём потихоньку уже заниматься вебсокетом.
В задаче tcp_thread мы также начнём слушать наш порт
1 2 3 4 5 6 7 8 9 |
err = netconn_bind(conn, NULL, 8080); if (err == ERR_OK) { netconn_listen(conn); } else { netconn_delete(conn); } |
Затем также добавим бесконечный цикл, в котором свяжем структуры
1 2 3 4 5 |
netconn_listen(conn); for(;;) { err = netconn_accept(conn, &newconn); } |
Добавим глобальную структуру соединения и создадим переменную типа данной структуры
1 2 3 4 5 6 |
} struct_sock; typedef struct struct_conn_t { uint32_t conn; uint8_t type; } struct_conn; struct_conn conn01; |
Эта структура нам понадобится для параметра, который мы будем передавать ещё одной задаче.
Добавим задачу в которой, мы будем передавать пакеты клиенту.
Сначала создадим глобальный идентификатор данной задачи
1 2 |
#define LCD_FRAME_BUFFER SDRAM_DEVICE_ADDR osThreadId ThreadTCPHandle; |
Теперь добавим функцию для этой задачи выше функции tcp_thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//--------------------------------------------------------------- static void tcp_send(void *arg) { struct_conn *arg_conn; struct netconn *conn; osEvent event; volatile uint16_t len = 0, type = 0; int i, val=0; arg_conn = (struct_conn*) arg; conn = (void*)arg_conn->conn; for(;;) { osDelay(1000); } } //--------------------------------------------------------------- |
Вернёмся в функцию tcp_thread, где при удачной связи соединений, мы присвоим полю нашей структуры параметра указатель на структуру соединения, а также создадим нашу новую задачу, уходя после её создания в бесконечный цикл. После тела условия мы удалим структуру соединения и удалим задачу. В данное места мы попадаем, если вдруг мы выйдем из бесконечного цикла, а такой случай у нас предвидится
1 2 3 4 5 6 7 8 9 10 11 12 |
err = netconn_accept(conn, &newconn); if (err == ERR_OK) { conn01.conn = (uint32_t)newconn; ThreadTCPHandle = sys_thread_new("tcp_send", tcp_send, (void*)&conn01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal ); for(;;) { } } netconn_delete(newconn); osThreadTerminate(ThreadTCPHandle); |
В бесконечном цикле попытаемся принять пакет от клиента, затем добавим условие успешного приёма, и, пока не добавляя код в тело успешного варианта, добавим код в тело противного случая, где очистим буфер, разъединимся с клиентом, а также выйдем из бесконечного цикла. Также после обоих тел условия мы также очистим структуру буфера
1 2 3 4 5 6 7 8 9 10 11 12 13 |
for(;;) { recv_err = netconn_recv(newconn, &inbuf); if (recv_err == ERR_OK) { } else { netbuf_delete(inbuf); netconn_close(newconn); break; } netbuf_delete(inbuf); |
Теперь переходим к положительному варианту условия, где установим указатель с нашим указателем, откуда потом и будем читать содержимое буфера
1 2 3 |
if (recv_err == ERR_OK) { netbuf_data(inbuf, (void**)&buf, &buflen); |
Проверим пакет, пришедший от клиента, на наличие HTTP-запроса типа GET, так как сначала такой и должен прийти
1 2 3 4 |
netbuf_data(inbuf, (void**)&buf, &buflen); if ((buflen >=5) && (strncmp(buf, "GET /", 5) == 0)) { } |
Теперь мы также можем собрать код и проверить его работоспособность, лишний раз не помешает убедиться, что запуск нового процесса не остановил нашу программу.
Пока, конечно, никакой запрос нам никто не пошлёт, для этого нужна функция в документе index.html.
Давайте займёмся этим.
Для начала мы добавим ещё одну кнопку для соединения с портом для WebSocket нашего сервера, которая будет потом превращаться в кнопку для разъединения
1 2 3 4 5 |
<div id="page"> <p></p> <div id="butconnect"> <input class = "butcolor" type="button" style="color: #FF00FF; background-color: #00FF00;" onclick="startconnect()" value="Connect"/> </div> |
Создадим глобальные переменные для соединения WebSocket, для графика, а также для обращения к полям по идентификаторам, чтобы несколько раз такие длинные строки не повторять в скрипте. Инициализацию данных переменных мы проводим уже в функции, которая вызовется, когда до конца прогрузится страница
1 2 3 4 5 6 7 8 9 10 11 |
<script> var WebSocket_connection; var myChart; var butstring_elem, butchart_elem, status_elem, information_elem, butconnect_elem; window.onload = function() { butstring_elem = document.getElementById('butstring'); butchart_elem = document.getElementById('butchart'); status_elem = document.getElementById('status'); information_elem = document.getElementById('information'); butconnect_elem = document.getElementById('butconnect'); } |
Вы, наверно, обратили внимание, что данную функцию мы применяем несколько по-другому. Этот способ удобен, если нам не хочется писать наименование функции в тегах начала содержимого страницы.
Добавим обработчик кнопки Connect, в котором попытаемся соединиться с сокетом
1 2 3 4 5 |
butconnect_elem = document.getElementById('butconnect'); } function startconnect(){ WebSocket_connection = new WebSocket("ws://192.168.1.191:8080"); } |
Здесь мы применили функцию для создания соединения типа WebSocket. Мы создали объект типа WebSocket, который в качестве параметра берёт строку, в которой сначала идёт аббревиатура протокола ws, затем двоеточие и два слэша, как и в случае с http, а затем уже IP-адрес или имя сервера, а также порт, который мы назначили соединению на сервере.
У данного класса (конечно, в JS не всегда это называется классом, хотя поддержка уже включена), объект которого мы создали, существует несколько видов функций обратного вызова, которыми мы будем пользоваться несколько позже.
Пока проверим, то. что клиент наш будет запрашивать соединение у сервера по кнопке. Поэтому соберём сначала, как всегда, файл fsdata.c, обновим дерево проекта, соберём код, прошьём его в контроллер, запустим WireShark, отфильтровав там весь наш поток по IP нашего сервера (кто конечно его уже не запустил заранее, а я думаю, что уже давно его все запустили), обновим страницу в браузере и нажмём на верхнюю кнопку. У нас вроде бы ничего не происходит в браузере, а на самом деле творится уже вот что
Браузер незаментненько отправил уже некий запрос серверу, причём сервер его подтвердил, то есть он его принял и вполне уже готов дать ответ, но за это мы скоро уже тоже возьмёмся.
Посмотрим теперь состав запроса нашего клиента
Состав запроса здесь немалый, здесь даётся много информации разного плана, но больше всего нас интересует вот эта строка, с которой мы сейчас и начнём работать
Sec-WebSocket-Key: ZbAt28e5ASwHIgSM/IGtZA==
Это и есть ключ, который даёт нам на обработку клиент.
Поэтому вернёмся в наш проект и попробуем с ним поработать.
В функции tcp_thread мы сначала запросим память под очередь для отправки данных в функцию вывода на дисплей, перешлём в структуру вертикальную координату и попытаемся найти то место в памяти скопированной из буфера строке, в которой присутствует вхождение строки, по которой мы и найдём наш заветный ключ
1 2 3 4 5 6 |
if ((buflen >=5) && (strncmp(buf, "GET /", 5) == 0)) { //parse buf qstruct = osMailAlloc(strout_Queue, osWaitForever); qstruct->y_pos = 120; istr = strstr(buf,"Sec-WebSocket-Key:"); |
Если к длине ключа и прибавить к ней данную строку, то мы получим число 43. Вот по такому адресу мы и запишем окончание строки, так как мы уверены, что строку данную наша функция strstr уже нашла и указатель istr уже туда поставила
1 2 |
istr = strstr(buf,"Sec-WebSocket-Key:"); istr[43] = 0; |
Затем мы наш ключ отобразим на дисплее, не забывая после этого очистить память очереди
1 2 3 4 5 6 |
istr[43] = 0; strcpy(str_buf1,istr+19); sprintf(qstruct->str,"%s", str_buf1); osMailPut(strout_Queue, qstruct); osMailFree(strout_Queue, qstruct); osDelay(3); |
Теперь начнём готовить клиенту ответ. Подготовим начало запроса, состоящего из стандартных строк
1 2 3 4 5 |
osDelay(3); strcpy(str_buf1,"HTTP/1.1 101 Switching Protocolsrn"); strcat(str_buf1,"Upgrade: websocketrn"); strcat(str_buf1,"Connection: Upgradern"); strcat(str_buf1,"Sec-WebSocket-Accept: "); |
Теперь добавим глобальный строковый массив в виде константы для GUID, а также строковый массив для HASH
1 2 3 |
#define LCD_FRAME_BUFFER SDRAM_DEVICE_ADDR char guid_str[] = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"}; unsigned char hash[20] = {0}; |
Вернёмся в нашу функцию и в отдельном буфере соединим ключ, который пришёл нам от клиента с уникальным идентификатором
1 2 |
strcat(str_buf1,"Sec-WebSocket-Accept: "); sprintf((char*)out_buffer,"%s%s", istr+19,guid_str); |
Затем сформируем из этого всего хэш по алгоритму SHA1 с помощью специальной функции библиотеки mbedtls
1 2 |
sprintf((char*)out_buffer,"%s%s", istr+19,guid_str); mbedtls_sha1(out_buffer, 60, hash); |
Закодируем наш хэш по алгоритму Base64
1 2 |
mbedtls_sha1(out_buffer, 60, hash); mbedtls_base64_encode( (unsigned char *)str_buf2, 100, &len, hash, 20); |
Завершим нулём нашу выходную строку нулём в нужном месте используя для этого длину строки, которую возвратила функция
1 2 |
mbedtls_base64_encode( (unsigned char *)str_buf2, 100, &len, hash, 20); str_buf2[len] = 0; |
Присоединим наш закодированный хэш, который и будет ответом на ключ, к нашей частично подготовленной строке ответа клиенту и завершим данный ответ дважды возвратом каретки и переводом строки
1 2 3 |
str_buf2[len] = 0; strcat(str_buf1,(char*)str_buf2); strcat(str_buf1,"rnrn"); |
Отправим строку с ответом клиенту
1 2 |
strcat(str_buf1,"rnrn"); netconn_write(newconn, (const unsigned char*)(str_buf1), strlen(str_buf1), NETCONN_NOCOPY); |
Соберём код, прошьём контроллер, обновим страницу в браузере и ещё раз попробуем нажать на кнопку.
Теперь мы уже видим наш ответ.
Значит соединение у нас успешно создано.
В следующей части нашего занятия мы научимся закрывать WebSocket, а также отправим с клиента и начнём обработку на сервере команды начала и окончания передачи данных.
Предыдущая часть Программирование МК STM32 Следующая часть
Отладочную плату можно приобрести здесь 32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий