В предыдущей части нашего занятия мы создали ряд задач и научились отвечать клиенту на запрос соединения WebSocket.
Давайте теперь похвалимся о том, что у нас создано соединение, в браузере, для этого зайдём в наш index.html и добавим ещё немного кода в обработчик нажатия нашей кнопки
1 2 3 4 5 6 7 8 9 10 11 12 |
WebSocket_connection = new WebSocket("ws://192.168.1.191:8080"); WebSocket_connection.binaryType = 'arraybuffer'; var output = ''; for (var property in WebSocket_connection) { output += property + ': ' + WebSocket_connection[property]+'; '; } WebSocket_connection.onopen = function() { status_elem.innerHTML = "cоединение установлено<br>' "; butconnect_elem.innerHTML = '<input class = "butcolor" type="button" style="color: #00FF00; background-color: #FF00FF;" onclick="stopconnect()" value="Disonnect"/>'; butstring_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startstring()" value="START"/>'; butchart_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/>'; }; |
Мы превратили кнопку Connect в Disconnect, отобразили кнопки START возле графика и возле текста, а также добавили функцию-обработчик открытия сокета, в которой написали в соответствующем месте, что у нас соединение установлено. Также мы назначили тип бинарных данных — arraybuffer, что позволяет получать данные в виде массива.
Давайте это проверим, для этого опять сохраним нашу страницу, перегенерируем fsdata.c, обновим дерево проекта, пересоберём код и прошьём контроллер. Обновим страницу в браузере и нажмём на кнопку
У нас появились кнопки, а также кнопка Connect превратилась в Disconnect. Так же мы видим сообщение об успешном соединении.
Мы научились с вами соединение открывать и, прежде чем что-то куда-то по нём слать, мы должны научиться его и закрывать.
А для этого нам придётся немного окунуться в специфику протокола WebSocket.
Вот так вот показан фрейм в официальной редакции протокола
Сначала передаётся первый байт, который состоит из четырёх флаговых битов и опкода.
Фреймы существуют разных типов: для передачи текста, для передачи бинарных данных, фрейм закрытия соединения, фрейм PING, фрейм PONG, также фреймы бывают фрагментированными, также поэтому есть фреймы, сообщающие, что это фрагмент сообщения.
Вообщем, первый байт.
Флаг FIN — флаг говорящий о том, что либо отдельный фрейм (не фрагмент), либо это последний фрагмент сообщения.
Флаги RSV1-RSV3 нужны для того, чтобы сообщить, что это какой-то расширенный протокол. мы этого касаться не будем.
Затем идёт опкод. Вот виды опкодов
Как мы видим, опкод нам и показывает тип нашего фрейма. То есть если это фрейм, который сообщает серверу о желании разорвать соединение, то это значение 8.
Поэтому мы будем проверять это при парсинге пакета. То есть к нам должно прийти значение 0x88, в котором будет включены старшие биты в обоих тетрадах. Первый — FIN, а второй — наш опкод.
Второй байт — это байт длины, о котором мы поговорим также позже. В случае фрейма разрыва соединения он равен нулю.
Поэтому вернёмся в наш проект в функцию tcp_thread и отреагируем на такое желание клиента
1 2 3 4 5 6 7 8 9 10 11 |
netconn_write(newconn, (const unsigned char*)(str_buf1), strlen(str_buf1), NETCONN_NOCOPY); } else if((buflen >1) && ( (buf[0]&0x0F) == 0x08)) { buf[0] = 0x88; buf[1] = 0x00; netconn_write(newconn, (const unsigned char*)buf, 2, NETCONN_NOCOPY); netconn_close(newconn); netbuf_delete(inbuf); break; } |
Мы проверили, что длина фрейма не совсем маленькая, также проверили только опкод — этого вполне достаточно, и если это так, то мы заполнили наши байты и отправили их назад нашему клиенту, что означает наше согласие на разрыв соединения с ним.
Теперь также надо нам где-то заставить браузер такой фрейм послать. Для этого в документе добавим обработчик кнопки Disconnect следующего содержания
1 2 3 4 5 6 |
butchart_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/>'; }; } function stopconnect(){ WebSocket_connection.close(); } |
Далее в функции startconnect добавим обработчик события закрытия сокета
1 2 3 4 5 6 7 8 9 10 11 12 13 |
butchart_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/>'; }; WebSocket_connection.onclose = function(event) { if (event.wasClean) { status_elem.innerHTML = 'cоединение закрыто корректно'; } else { status_elem.innerHTML = 'соединение закрыто некорректно'; } status_elem.innerHTML += '<br>код: ' + event.code; butconnect_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #FF00FF; background-color: #00FF00;" onclick="startconnect()" value="Connect"/>'; butstring_elem.innerHTML = '<br><br><br><br>'; butchart_elem.innerHTML = '<br><br><br><br>'; }; |
Сохраним страницу, перегенерируем fsdata.c, обновим дерево проекта, пересоберём код и прошьём контроллер. Обновим страницу в браузере и нажмём на кнопку Disconnect
Мы видим, что соединение у нас закрылось корректно, а также кнопка опять превратилась в Connect и кнопки START исчезли. Так и должно быть.
Посмотрим также, что происходит в этом случае в анализаторе трафика
А здесь всё просто прекрасно!
У нас произошло двустороннее закрытие сокета, а затем ещё и физическое также обоюдное закрытие соединения TCP.
Отлично! Идём дальше. Теперь нам надо будет научиться что-то наконец-то по нашему сокету передать.
Сначала попробуем передать от браузера команду на начало передачи данных сервером браузеру. Подчёркиваю — именно на начало, а не передавать такие команды на каждую передачу. Затем сервер будет передавать клиенту всё, что угодно уже без спроса последнего.
Для этого вернёмся в index.html и добавим обработчик кнопки START для пока для текстового поля
1 2 3 4 5 6 7 8 9 |
WebSocket_connection.close(); } function startstring(){ WebSocket_connection.binaryType = 'blob'; butstring_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #666666;" onclick="stopstring()" value="STOP"/>'; butchart_elem.innerHTML = ''; WebSocket_connection.send("1"); } </script> |
Вот такую вот символическую единичку мы передадим серверу с помощью команды send нашего объекта.
Также мы изменили значение типа в blob, как и по умолчанию. Ещё мы Нашу кнопку START превратили в STOP, а также удалили кнопку START для графика.
Также добавим обработчик кнопки STOP, в которой мы передадим троечку. Мы её, возможно и не будем принимать, но так лучше закрывается соединение
1 2 3 4 5 6 7 |
WebSocket_connection.send("1"); } function stopstring(){ WebSocket_connection.send("3"); butstring_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startstring()" value="START"/>'; butchart_elem.innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/>'; } |
Теперь идём в наш проект получать нашу единичку.
В функции tcp_thread поймаем наши запросы с клиента — и единичку и двойку, которая будет приходить по нажатию другой кнопки START для запуска процесса передачи данных уже бинарного типа. Для начала вытащим нашу строку из сообщения
1 2 3 4 5 6 7 8 |
break; } else if((buflen >1) && (buf[0] == 0x81)) { //mask if(buf[1]>>7) str_msg=buf[6]^buf[2]; else str_msg=buf[2]; } |
Мы определили, что это именно сообщение нефрагментированное первого типа (текстового). От клиента всегда будет идти сообщение именно текстового типа. Если это так, то мы попадаем внутрь цикла.
Тут уже может быть маска. Что же такое маска?
А маска — это такая вещь, которая состоит из 4-х байтов и применяется в сообщениях WebSocket, чтобы хоть как-то зашифровать поток, и затем данные 4 байта постепенно по очереди циклично ложатся по операции XOR на весь поток байтов сообщения. Сделано это для того, чтобы было хоть как-то невозможно перехватывать и расшифровывать поток на ходу. Маску эту мы можем применять или не применять. Поэтому вернёмся ко второму байту фрейма, в котором первым битом и является флаг наличия данной маски. Если он в 1 — то маска будет, а если в 0, то маски не будет. Остальные 7 бит занимает длина сообщения, о которой тоже будет отдельный разговор. Если у нас нет маски (флаг в нуле), а также длина меньше 126, то тогда с 3 байта уже пойдут полезные данные. А вот если маска есть, а длина сообщения также меньше 126, то с третьего по шестой байт, а если считать с нуля, то со второго байта по пятый и будет располагаться маска, а уже с шестого будет располагаться полезная нагрузка.
Поэтому если у нас маска есть, то мы исследуем шестой байт, накладывая на него по XOR второй, а если маски нет, то просто берём второй байт.
В задачу передачи данных клиенту мы будем значение сообщения передавать в виде очереди.
Добавим сначала идентификатор очереди
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); |
Вернёмся в функцию tcp_thread, в которой теперь исследуем пришедший байт и передадим число в функцию передачи данных клиенту
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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); } |
Мы обработали все типы возможных значений сообщения.
Теперь займёмся нашей задачей передачи данных клиенту.
В бесконечном цикле функции tcp_send попытаемся прочитать байт из очереди
1 2 3 |
for(;;) { event = osMessageGet(send_type_Queue, 100); |
Присвоим его переменной
1 2 3 4 5 |
event = osMessageGet(send_type_Queue, 100); if (event.status == osEventMessage) { type = event.value.v; } |
Далее мы с помощью условия отфильтруемся от всех значений принятого от клиента байта, отличных от 1 и 2. То есть если будет какое-то другое значение, то мы тогда попадаем только в задержку, так что наш цикл будет только следить за очередью
1 2 3 4 5 |
type = event.value.v; } if((type==1)||(type==2)) { } |
Если у нас тип сообщения 1, то есть тогда мы будем передавать клиенту строковые данные, то тогда мы положим в наш буфер список наших задач, измерим длину списка и сразу подготовим первый байт, объявив наше отправляемое сообщение финальным (не путать с запросом на закрытие соединения, наш бит означает то, что сообщение больше не будет иметь фрагментов), а также присвоив ему тип 1 опкода — текстовые данные. По идее мы затёрли самый первый байт нашего списка, но это ничего, мы его потом ещё раз положим куда надо, это было просто использование нашего буфера для того, чтобы измерить длину будущего фрейма.
1 2 3 4 5 6 7 8 |
if((type==1)||(type==2)) { if(type==1) { osThreadList(send_ws_buf); len = strlen((char*)send_ws_buf); send_ws_buf[0]=0x81; } |
А в противном случае (отправка бинарных данных) мы уже объявим тип данных 2 (binary frame), затем зададим длину, мы же её заранее знаем, и аналогично алгоритму прошлого урока мы положим в буфер 512 случайных величин (а всего там получится 1024 байта). Данные мы укладываем в наш массив, начиная с байта 4, так как размер у нас будет точно от 125 до 65536
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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); } } |
А вот теперь, прежде чем сочинять дальнейший код, мы должны знать, каким образом можно посредством протокола RFC 6455 передавать данные длиной более 125 байт, не разбивая их на фрагменты. Этим мы займёмся в следующей части занятия, в которой мы также обработаем события приёма данных на клиенте и отобразим их в соответствующем виде в браузере.
Предыдущая часть Программирование МК STM32 Следующая часть
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий