Продолжим работу с нашим протоколом HTTP, а также с сетью LAN, библиотекой стека протоколов LWIP, её интерфейсом SOCKET, а также с интересующею всех возможностью передачи данных в браузер клиенту, а также обратно от него на сервер без перезагрузки всей страницы. Мы уже на прошлом занятии занимались таким делом и нам, как вы, надеюсь, помните, это удалось. Применяли для этого мы известную технологию AJAX.
Но столкнулись мы с некоторым вопросом при таком способе обмена данными. Для того, чтобы сервер передал какой-то определённый пакет данных, необходимо было, чтобы клиент его обязательно запросил. Также может существовать ситуация, когда серверу нужно передать данные в любой момент. Особенно такая ситуация возникает в таком случае, когда мы, например организуем чат между двумя клиентами. Один клиент хочет передать другому какое-то сообщение. Между клиентами не существует прямого соединения. Они связаны, например, в какой-то соцсети только через сервер, на котором находится сайт соцсети. Поэтому клиент вместе с определённым идентификатором того клиента, которому адресовано данное сообщение, передаёт всё это серверу, а сервер обязан максимально быстро передать сообщение тому клиенту, которому оно адресовано. Но как же быть, ведь по определению технологии AJAX так невозможно, потому что клиент как бы должен его попросить. Поэтому до технологии WebSocket клиент обязан был раз в какое-то время периодично слать на сервер запрос типа: «А нет ли там для меня какого-то сообщения?». Это было не совсем удобно, ни о какой мгновенности обмена сообщениями не было и речи. Поэтому нам на помощь пришла другая технология — WebSocket.
Также и в нашем случае может возникнуть такая ситуация, когда контроллеру нужно будет в браузер клиента передать какой-то пакет информации незамедлительно. Здесь также нам поможет WebSocket.
А ещё, с технологией WebSocket мы уже тоже работали, когда использовали API NETCONN, поэтому, думаю, нам будет гораздо легче, чем если бы мы знакомились с данной технологией с нуля.
Поэтому рассказывать про особенности протокола, который используется в случае применения технологии WebSocket, я не буду, всё это очень неплохо освещено в уроке 129, который вы, надеюсь, уже все посмотрели. Если кто-то не видел данный урок, то непременно посмотрите.
Теперь, когда уже все посмотрели урок 129, мы можем приступить к основной задаче нашего занятия — сделать возможность использования технологии WebSocket с применением уже интерфейса SOCKET библиотеки стека протоколов LWIP.
И поэтому мы теперь смело можем перейти сразу к нашему проекту, который мы сделаем из проекта урока 137 с именем LAN8742_HTTP_SERVER_AJAX_SOCKET, только имя мы, соответственно, присвоим ему новое — LAN8742_WEBSOCKET_SOCKET. Конечно, в имени прослеживается некоторая тавтология, но так уж получилось.
Откроем наш проект в Cube MX и произведём некоторые поправки.
Во-первых включим MBEDTLS
В Configuration в настройках MBEDTLS, так как там очень много всякого разнообразия, для того, чтобы не включить ничего лишнего отключим сначала всё.
Начнём с самого первого раздела
Идём в следующий раздел и аналогичные настройки заносим во все пункты, двигаясь плавно сверху вниз по всему списку. А сверху вниз удобнее потому, что когда мы отключаем некоторые пункты сверху, то зависимые от них пункты ниже могут отключаться сами
Один раздел пропускаем, там уже всё и так отключено. В следующем разделе также всё отключим
В следующем разделе уже всё отключено.
Сохраним такие настройки и теперь начнём включать нужные опции.
Начнём сразу с раздела Modules. Включим Base64
Здесь же включим и SHA1
Остальные нужные опции включились сами.
Сохраним настройки, сгенерируем проект для System Workbench и откроем его там. Установим уровень оптимизации в 0 (именно в 0, так как с единичкой были некоторые проблемы), а также уберём при наличии отладочные настройки.
Проект пока не собираем, так как у нас пока нет файла fsdata.c.
Весь состав документального контента мы возьмём тот же, что и в уроке 129. Скопируем папку fs, сохранённую для данного урока, вместе с содержимым, а также файлы makefsdata.exe, makefsdata.cmd и msvcr100d.dll, чтобы мы могли свободно собирать недостающий файл с контентом, в папку «Папка с проектом//Middlewares/Third_Party/LwIP/src/apps/httpd/». Также в папку fs помести файл favicon.ico, который вы сами себе сгенерируйте под себя. Это иконка сайта, которая отображается в закладке с браузером вместе с именем документа. Если этот файл поместить, то серверу не придется постоянно отвечать браузерам об его отсутствии. Для генерации данного файла существует ряд онлайн-сервисов.
Соберём с помощью файла makefsdata.cmd файл fsdata.c, вернёмся в проект, освежим проект в дереве (Refresh) и отключим от компиляции наш файл fsdata.c.
Попробуем теперь собрать проект, проект должен будет нормально собраться.
Откроем файл main.c и подключим данные библиотеки
1 2 3 |
#include "lwip/apps/fs.h" #include "mbedtls/sha1.h" #include "mbedtls/base64.h" |
В функции main() изменим немного строку, выводящуюся на дисплей, для красоты
TFT_DisplayString(0, 10, (uint8_t *)"WebSocket Server", CENTER_MODE);
Удалим все инициализированные глобальные массивы строк: PAGE_HEADER_200_OK, PAGE_HEADER_SERVER, PAGE_HEADER_CONTENT_TEXT, PAGE_HEADER_CONTENT_STREAM, PAGE_HEADER_LEN, PAGE_HEADER_BYTES.
Удалим также функции DynWebPageStr и DynWebPage вместе с телами.
В функции задачи по умолчанию StartDefaultTask добавим вызов функции инициализации стека протоколов LWIP, иначе наша плата не будет даже пинговаться
1 2 |
/* USER CODE BEGIN 5 */ MX_LWIP_Init(); |
Функцию tcp_thread переименуем в http_thread
static void http_thread(void *arg)
В функции задачи по умолчанию StartDefaultTask переименуем аргументы также в создании этой задачи
sys_thread_new("http_thread", http_thread, (void*)&sock01, DEFAULT_THREAD_STACKSIZE * 4, osPriorityNormal);
В функции http_thread вот здесь немного убавим очередь
listen(sock, 5);
Уберём также в этой функции условия запросов файлов style.css, img01.jpg, img02.jpg, img03.jpg, img03.jpg, bg01.png, bg02.png, bg03.png, color.html, content.html и content.bin, так как таких файлов у нас теперь нет.
Добавим ещё одну глобальную структуру, а также переменную её типа
1 2 3 4 5 6 |
} struct_sock; typedef struct struct_send_socket_t { int sock; uint16_t y_pos; } struct_send_socket; struct_send_socket send_socket01; |
Создадим идентификатор ещё одной очереди
1 2 |
osMailQId strout_Queue; osMessageQId send_type_Queue; |
Выше добавим макрос её размера
1 2 |
#define MAIL_SIZE (uint32_t) 5 #define QUEUE_SIZE (uint32_t) 1 |
Создадим данную очередь в функции main()
1 2 3 |
strout_Queue = osMailCreate(osMailQ(stroutqueue), NULL); osMessageQDef(send_type_Queue, QUEUE_SIZE, uint16_t); send_type_Queue = osMessageCreate(osMessageQ(send_type_Queue), NULL); |
Объявим глобальный буфер для пакета WebSocket, а также выше ещё один буфер который нам тоже потребуется
1 2 3 |
char str_buf1[30] = {}; char str_buf2[256] = {}; uint8_t send_ws_buf[1312] = {0}; |
Выше функции http_thread добавим функцию ещё одной задачи, в которой мы будем отправлять пакеты WebSocket клиенту
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
//--------------------------------------------------------------- static void tcp_send(void *arg) { osEvent event; struct_send_socket *arg_send_socket; arg_send_socket = (struct_send_socket*) arg; volatile uint16_t len = 0, type = 0; int i, val=0; char str1[60]; for(;;) { event = osMessageGet(send_type_Queue, 100); if (event.status == osEventMessage) { type = event.value.v; } if((type==1)||(type==2)) { if(type==1) { osThreadList(send_ws_buf); len = strlen((char*)send_ws_buf); send_ws_buf[0]=0x81; } else { send_ws_buf[0]=0x82; len = 1024; for(i=0;i<512;i++) { val = HAL_RNG_GetRandomNumber(&hrng)%4096; send_ws_buf[i * 2 + 4] = (uint8_t)val; send_ws_buf[i * 2 + 5] = (uint8_t)(val>>8); } } if(len<126) { osThreadList((uint8_t *)(send_ws_buf + 2)); StrSort((char *)(send_ws_buf + 2)); sprintf(str1, "%u",xPortGetMinimumEverFreeHeapSize()); strcat((char *)(send_ws_buf + 2), str1); len = strlen((char*)(send_ws_buf + 2)); send_ws_buf[1] = len; write(arg_send_socket->sock, send_ws_buf, len + 2); } else if((len<65536)) { send_ws_buf[1] = 126; send_ws_buf[2] = (uint8_t)(len>>8); send_ws_buf[3] = (uint8_t)len; if(type==1) { osThreadList((uint8_t *)(send_ws_buf + 4)); StrSort((char *)(send_ws_buf + 4)); } write(arg_send_socket->sock, send_ws_buf, len + 4); } } osDelay(1000); } } //--------------------------------------------------------------- |
Код функции подобен коду из урока 129.
Добавим глобальный идентификатор следующей задачи
1 2 |
#define QUEUE_SIZE (uint32_t) 1 osThreadId ThreadTCPHandle; |
Также добавим инициализированный массив уникального идентификатора и массив для строки с хэшем
1 2 3 |
osThreadId ThreadTCPHandle; char guid_str[] = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"}; unsigned char hash[20] = {0}; |
Выше функции http_thread добавим функцию ещё одной задачи, где мы будем обрабатывать трафик протокола WebSocket
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
//--------------------------------------------------------------- static void tcp_thread(void *arg) { struct_out *qstruct; int sock, accept_sock, ret; struct sockaddr_in address, remotehost; socklen_t sockaddrsize; int buflen = 1024; char buf[1024] = {}; char *istr; size_t len; char str_msg = 0; if ((sock = socket(AF_INET,SOCK_STREAM, 0)) >= 0) { address.sin_family = AF_INET; address.sin_port = htons(8080); address.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr *)&address, sizeof (address)) == 0) { listen(sock, 5); for(;;) { accept_sock = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&sockaddrsize); if(accept_sock >= 0) { send_socket01.y_pos = 200; send_socket01.sock = accept_sock; ThreadTCPHandle = sys_thread_new("tcp_send", tcp_send, (void*)&send_socket01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal ); for(;;) { ret = recvfrom(accept_sock, buf, buflen, 0, (struct sockaddr *)&remotehost, &sockaddrsize); if(ret > 0) { if ((ret >=5) && (strncmp(buf, "GET /", 5) == 0)) { //parse buf qstruct = osMailAlloc(strout_Queue, osWaitForever); qstruct->y_pos = 110; istr = strstr(buf,"Sec-WebSocket-Key:"); istr[43] = 0; strcpy(str_buf1,istr+19); sprintf(qstruct->str,"%s", str_buf1); osMailPut(strout_Queue, qstruct); osMailFree(strout_Queue, qstruct); strcpy(str_buf,"HTTP/1.1 101 Switching Protocols\r\n"); strcat(str_buf,"Upgrade: websocket\r\n"); strcat(str_buf,"Connection: Upgrade\r\n"); strcat(str_buf,"Sec-WebSocket-Accept: "); sprintf(str_usart,"%s%s", istr+19,guid_str); mbedtls_sha1((unsigned char *)str_usart, 60, hash); mbedtls_base64_encode( (unsigned char *)str_buf2, 100, &len, hash, 20); str_buf2[len] = 0; strcat(str_buf,(char*)str_buf2); strcat(str_buf,"\r\n\r\n"); write(accept_sock, (const unsigned char*)(str_buf), strlen(str_buf)); } else if((ret >1) && ( (buf[0]&0x0F) == 0x08)) { buf[0] = 0x88; buf[1] = 0x00; write(accept_sock, (const unsigned char*)buf, 2); close(accept_sock); break; } else if((ret >1) && (buf[0] == 0x81)) { //mask if(buf[1]>>7) str_msg=buf[6]^buf[2]; else str_msg=buf[2]; if(str_msg=='1') { osMessagePut(send_type_Queue, 1, 100); } else if(str_msg=='2') { osMessagePut(send_type_Queue, 2, 100); } else if(str_msg=='3') { osMessagePut(send_type_Queue, 3, 100); } else if(str_msg=='4') { osMessagePut(send_type_Queue, 4, 100); } } } else { close(accept_sock); break; } } } osThreadTerminate(ThreadTCPHandle); } } else { close(sock); return; } } } //--------------------------------------------------------------- |
Код функции также подобен применённому в уроке 129 только ориентирован уже на использование функционала API SOCKET.
Осталось нам лишь в функции http_thread своевременно создать нашу задачу
1 2 3 |
if (bind(sock, (struct sockaddr *)&address, sizeof (address)) == 0) { sys_thread_new("tcp_thread", tcp_thread, NULL, DEFAULT_THREAD_STACKSIZE*2, osPriorityNormal ); |
Соберём проект, прошьём контроллер и запросим главную страницу нашего сервера в браузере. В ответ мы должны будем получить вот такую страницу
Соединимся с сервером для обмена по протоколу WebSocket, нажав кнопку Connect
Кнопка превратилась в Disconnect. Нажмём её, чтобы проверить, что разъединяться мы тоже умеем
Кнопка опять превратилась в Connect и мы видим надпись, что соединение наше закрылось корректно.
Ещё раз нажмём Connect, чтобы снова соединиться с сервером, а затем нажмём верхнюю кнопку START, чтобы дать команду серверу, чтобы тот начал регулярную передачу текстовых данных в таблицу
Мы видим, что передача текстовой информации происходит отлично, а кнопка превратилась в STOP и нижняя кнопка START исчезла
Нажмём кнопку STOP и передача прекратится, а кнопка превратится опять в START и также появится внизу кнопка START, нажав на которую, мы теперь дадим серверу команду на непрерывную передачу графической информации
И вот мы видим, как раз в секунду обновляется графическая информация на странице, а кнопка START превратилась в STOP и теперь исчезла верхняя кнопка START
Остановим с помощью кнопки STOP наш график, обе кнопки START, вернутся на своё место.
Разъединимся с сервером с помощью кнопки Disconnect и попробуем немного прибавить скорость вывода графической информации.
Для этого в функции tcp_send убавим задержку в 10 раз
osDelay(100);
Соберём код, прошьём контроллер, откроем вновь нашу страницу, соединимся с сервером с помощью кнопки Connect и запустим наш график.
Всё отлично работает.
Таким образом, в данном уроке мы освоили передачу данных с сервер в браузер клиента посредством технологии WebSocket, что позволило нам также обойтись без перезагрузки всей страницы, а также мы теперь можем передавать потоки данных без запроса клиента.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
А Вы будете рассматривать пример с MQTT? Спасибо.
mainscs
А разве такой есть в LWIP?
hii i want to create web socket connection from my web socket server. Client is STM32 and server is on my raspbarry pi. When i send http websocket connetion request, is connected but when send data ,it's automatically disconnect. I find out this because of frame format problem. Can you please help me to how can i create frame format to transmit data to server.
У кого на борту нет SDRAM, следует увеличить кучу во FreeRTOSе — в проекте куба или напрямую найти #define configTOTAL_HEAP_SIZE ((size_t)32768) и заменить на 65536. А то поток tcp_thread не создаётся. Спасибо видео-уроку, точнее автору. И в 129 уроке тоже увеличить кучу