В предыдущей части нашего занятия мы познакомились с технологией AJAX, настроили проект, а также создали красивый документ для отображения в браузере, но пока без функционала.
Теперь добавим немного функционала в наш клиент-сервер.
Давайте сначала научимся передавать данные из браузера на сервер.
Для этого в блоке sidebar добавим несколько разноцветных кнопок
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
</ul> <p> </p> <div class = "butcenter"> <input class = "butcolor" type="button" style="color: #00ffff; background-color: #ff0000;" onclick="red()" value="RED"/> <br><br> <input class = "butcolor" type="button" style="color: #ff00ff; background-color: #00ff00;" onclick="green()" value="GREEN"/> <br><br> <input class = "butcolor" type="button" style="color: #ffff00; background-color: #0000ff;" onclick="blue()" value="BLUE"/> <br><br> <input class = "butcolor" type="button" style="color: #ffffff; background-color: #000000;" onclick="black()" value="BLACK"/> <br><br> <input class = "butcolor" type="button" style="color: #ff0000; background-color: #00ffff;" onclick="cyan()" value="CYAN"/> <br><br> <input class = "butcolor" type="button" style="color: #00ff00; background-color: #ff00ff;" onclick="magenta()" value="MAGENTA"/> <br><br> <input class = "butcolor" type="button" style="color: #0000ff; background-color: #ffff00;" onclick="yellow()" value="YELLOW"/> <br><br> <input class = "butcolor" type="button" style="color: #000000; background-color: #ffffff;" onclick="white()" value="WHITE"/> </div> </div> |
Мы присвоили класс блоку с кнопками, также самим кнопкам, также определили их цвет, цвет их текста, задали сам текст и ещё назначили имена функций для обработки событий нажатия на кнопки.
Сохраним страницу и посмотрим, как она выглядит до стилизации
Думаю, что это несерьёзно.
Придадим некоторые стили блоку с кнопками и самим кнопкам в файле style.css
1 2 3 4 5 6 7 8 9 |
.butcenter { text-align: center; } .butcolor { width: 150px; height: 48px; font-size: 17px; } |
Сохраним файл со стилями и обновим страницу
Вот теперь другое дело. Это мы ещё не сильно стилизовали кнопки, можно было бы ещё повыпендриваться, но цель урока не в этом. По крайней мере на такие кнопки будет намного приятнее нажимать.
Цель данных кнопок — по нажатию на них будет появляться на дисплее платы квадратик с тем же цветом, как и у кнопки.
Ну теперь, наконец-то долгожданный AJAX.
Чтобы нам не пришлось наш скрипт выносить в самый низ страницы, в тег тела документа добавим вот такой обработчик с именем функции, который будет вызываться после загрузки всего документа
1 |
<body onload="onload()"> |
Это нужно для того, чтобы запускать наши скрипты после того, как загрузится весь документ, чтобы загрузка документа не «тормозила».
Мы не будем наш скрипт выносить в отдельный файл, хотя можно было бы, но надо сначала научиться работать в файле документа.
Поэтому также в тегах head создадим вот такой блок для скрипта
1 2 3 |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script> </script> |
Теперь между открывающим и закрывающим тегом мы и будем писать текст нашего JavaScript.
Начнём с функции-обработчика, который запустится после загрузки документа.
1 2 3 4 5 6 |
<script> var xhr; function onload(){ xhr = new(XMLHttpRequest); } </script> |
Пока мы только создали экземпляр объекта HTTP-запроса к серверу. Как раз такой объект и позволяет производить запросы без перезагрузки всей страницы.
Перед функцией мы создали глобальную переменную для запроса, чтобы к ней мы потом могли обращаться в других функциях.
Поэтому у нас в функции-обработчике загрузки страницы будет только создание объекта запроса.
Соответственно, вдаваться в тонкости обработки данных запросов мы сельно не будем, у нас же не по JavaScript урок.
А вот формировать запрос мы будем по нажатию на одну из кнопок, а затем ещё и в других функциях.
Начнём с красной
1 2 3 4 5 6 7 |
xhr = new(XMLHttpRequest); } function red(){ xhr.open("GET", "color.html?c=1", true); xhr.responseType = "text"; xhr.send(null); } |
Мы пошлём запрос типа GET серверу с соответствующей строкой, затем присвоим тип запроса «текст», и затем вызовем непосредственно функцию отправки нашего запроса серверу.
Аналогичные функции добавим и для остальных кнопок
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 |
function green(){ xhr.open("GET", "color.html?c=2", true); xhr.responseType = "text"; xhr.send(null); } function blue(){ xhr.open("GET", "color.html?c=3", true); xhr.responseType = "text"; xhr.send(null); } function black(){ xhr.open("GET", "color.html?c=4", true); xhr.responseType = "text"; xhr.send(null); } function cyan(){ xhr.open("GET", "color.html?c=5", true); xhr.responseType = "text"; xhr.send(null); } function magenta(){ xhr.open("GET", "color.html?c=6", true); xhr.responseType = "text"; xhr.send(null); } function yellow(){ xhr.open("GET", "color.html?c=7", true); xhr.responseType = "text"; xhr.send(null); } function white(){ xhr.open("GET", "color.html?c=8", true); xhr.responseType = "text"; xhr.send(null); } </script> |
Сгенерируем файл fsdata.c и обновим дерево нашего проекта, только прежде чем собирать код, нам надо будет обработать наши запросы.
Опять же в функции задачи обработки запросов добавим данные запросы. Сделать это можно с помощью оператора вариантов, так как в наших запросах разный будет только последний символ
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 |
else if (strncmp((char const *)buf,"GET /color.html?c=",18)==0) { switch (buf[18]) { case '1': TFT_FillRectangle(100,120,200,220,LCD_COLOR_RED); break; case '2': TFT_FillRectangle(100,120,200,220,LCD_COLOR_GREEN); break; case '3': TFT_FillRectangle(100,120,200,220,LCD_COLOR_BLUE); break; case '4': TFT_FillRectangle(100,120,200,220,LCD_COLOR_BLACK); break; case '5': TFT_FillRectangle(100,120,200,220,LCD_COLOR_CYAN); break; case '6': TFT_FillRectangle(100,120,200,220,LCD_COLOR_MAGENTA); break; case '7': TFT_FillRectangle(100,120,200,220,LCD_COLOR_YELLOW); break; case '8': TFT_FillRectangle(100,120,200,220,LCD_COLOR_WHITE); break; } } else |
Соберём код, прошьём контроллер, обновим нашу страницу в браузере, используя тот же IP, и понажимаем на наши кнопки. Мы должны увидеть в качестве реакции появляющийся квадрат определённого цвета на дисплее отладочной платы
Посмотрим также в WireShark, что никакая страница не перезагружается в ответ на запрос клиента
Да мы это и без анализа знаем, мы же ничего не передавали серверу в ответ на его запрос.
Следующая задача, что-нибудь принять от сервера. Для этого мы также должны сделать запрос. Без запроса сервер клиенту может передавать данные только при использовании технологии WebSocket, но мы скоро и до неё доберёмся.
Добавим в коде нашей страницы index.html в любом месте (например, между двумя первыми рисунками вот такой вот код
1 2 3 4 5 6 7 8 9 10 11 12 |
<img src="IMG/img01.jpg"> </p> <p></p> <p></p> <pre><br>Name State Priority Stack Num <br>---------------------------------------------<br> </pre> <pre id="information"></pre> <pre><br><br>--------------------------------------------- <br>B : Blocked, R : Ready, D : Deleted, S : Suspended<br> </pre> <p></p> |
Я думаю, вы знаете, что тег <pre> применяется для точности передачи текста, чтобы пробелы также были пробелами, а не игнорировались и т.д.
Пустой блок pre с идентификатором information будет служить для того, чтобы данные с сервера поступали именно сюда.
Также после всего этого давайте добавим ещё одну кнопку для запуска запросов информации с сервера, она же будет служить и для остановки передачи
1 2 3 4 5 6 |
<p></p> <div id="butstring"> <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startstring()" value="START"/> </div> <p></p> <p></p> |
Кнопку мы поместили в блок, чтобы удобнее было туда обращаться.
Теперь перейдём выше в наш скрипт и добавим ещё одну переменную, мы позже увидим, для чего она нужна
1 2 |
var xhr; var idTimer1; |
Добавим функцию для запроса.
Так как запросы будут периодическими, то мы и дадим ей соответствующее имя
1 2 3 4 5 6 7 8 9 10 |
function Timer1(){ xhr.open("GET", "content.html?r=" + Math.random(), true); //Math.random() - защита от кеширования xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('information').innerHTML = xhr.response; } xhr.send(null); idTimer1 = setTimeout("Timer1()", 1000); } function red(){ |
Мы послали аналогичный запрос серверу, но так как мы будем принимать данные от него, то есть опасность того, что они закешируются и браузер начнёт подсовывать информацию из кеша, поэтому мы ещё к запросу пристроим случайное число, чтобы браузеру не казалось, что приходить будут одни и те же данные. Далее мы также назначаем тип данных «текст», а затем добавляем функцию обработчик, в JavaScript мы можем тело данного обработчика писать прямо здесь. В теле мы отправляем полученную текстовую информацию с сервера в наш блок, затем мы вызываем метод объекта для отправки запроса на сервер, а по окончании мы запускаем одноразовый таймер, в котором мы добавляем в качестве первого аргумента имя функции, которую мы будем вызывать по окончании интервала в милисекуднах, добавленного во втором аргументе. Но, так как имя функции у нас то, в какой мы и находимся, то таймер у нас получится уже не одноразовый, а вполне себе цикличный. Также мы воспользуемся возвращаемым аргументом функции и присвоим нашему таймеру идентификатор, чтобы мы его могли в любой момент остановить.
Только сама по себе первый раз данная функция не вызовется, её мы вызовем в обработчике нашей кнопки, которую мы только что добавили. Добавим данный обработчик в самом низу скрипта
1 2 3 4 5 |
function startstring(){ document.getElementById('butstring').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #666666;" onclick="stopstring()" value="STOP"/>'; Timer1(); } </script> |
Прежде чем запустить таймер по нажатию кнопки, мы сначала изменим блок с кнопкой, в котором мы данную кнопку превратим уже в кнопку остановки процесса запросов на сервер, а также назначим ей другой обработчик. У уж затем мы запустим наш таймер.
Поэтому давайте теперь также добавим обработчик также и для кнопки остановки
1 2 3 4 5 6 |
function stopstring(){ document.getElementById('butstring').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startstring()" value="START"/>'; clearTimeout(idTimer1); document.getElementById('information').innerHTML = ""; } function startstring(){ |
Здесь мы опять превращаем кнопку в стартовую, останавливаем таймер и очищаем окно с информацией.
Сохраним нашу страницу, сгенерируем файл fsdata.c, обновим дерево нашего проекта и после функции задачи вывода строк на дисплей TaskStringOut добавим функцию для формирования динамического веб-документа для ответа клиенту на запрос
1 2 3 4 5 6 7 8 |
//--------------------------------------------------------------- void DynWebPageStr(struct netconn *conn) { portCHAR PAGE_BODY[768]; uint16_t len = 0; } //--------------------------------------------------------------- |
В данную функцию мы только передаём указатель на структуру соединения.
Теперь отследим запрос клиента в функции задачи обработки запросов, в теле условия мы и вызовем нашу функцию формирования ответа
1 2 3 4 5 |
else if (strncmp((char const *)buf,"GET /content.html",17)==0) { DynWebPageStr(newconn); } else |
Прежде чем писать тело функции формирования ответа клиенту, добавим несколько глобальных массивов с фрагментами заголовка пакета HTTP. Разместим их во FLASH-памяти контроллера, используя специальные директивы
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 |
#define MAIL_SIZE (uint32_t) 5 static const unsigned char PAGE_HEADER_200_OK[] = { //"HTTP/1.1 200 OK" 0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d, 0x0a, //zero 0x00 }; static const unsigned char PAGE_HEADER_SERVER[] = { //"Server: lwIP/1.3.1 (http://savannah.nongnu.org/projects/lwip)" 0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x31,0x2e,0x33, 0x2e,0x31,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e, 0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70, 0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a, //zero 0x00 }; static const unsigned char PAGE_HEADER_CONTENT_TEXT[] = { //"Content-type: text/html" 0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x74,0x79,0x70,0x65,0x3a,0x20,0x74,0x65, 0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a, //zero 0x00 }; //* static const unsigned char PAGE_HEADER_CONTENT_STREAM[] = { //"Content-Type: application/octet-stream" 0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x61,0x70, 0x70,0x6c,0x69,0x63,0x61,0x74,0x69,0x6f,0x6e,0x2f,0x6f,0x63,0x74,0x65,0x74,0x2d, 0x73,0x74,0x72,0x65,0x61,0x6d,0x0d,0x0a, //zero 0x00 }; static const unsigned char PAGE_HEADER_LEN[] = { //"Content-Length: " 0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20, //zero 0x00 }; static const unsigned char PAGE_HEADER_BYTES[] = { //"Accept-Ranges: bytes" 0x41,0x63,0x63,0x65,0x70,0x74,0x2d,0x52,0x61,0x6e,0x67,0x65,0x73,0x3a,0x20,0x62, 0x79,0x74,0x65,0x73,0x0d,0x0a,0x0d,0x0a, //zero 0x00 }; /* USER CODE END PV */ |
В данных массивах у нас находятся именно коды символов.
Теперь продолжим писать тело функции DynWebPageStr.
Начнём готовить в нем строку для отправки клиенту
1 2 |
uint16_t len = 0; sprintf(PAGE_BODY,"%s%s%s",PAGE_HEADER_200_OK,PAGE_HEADER_SERVER,PAGE_HEADER_CONTENT_TEXT); |
Мы подготовили пока только заголовок.
Передавать клиенту мы будем информацию о процессах. И, так как запросы будут происходить регулярно, то данная информация на странице будет обновляться динамически.
Запросим информацию о процессах и занесём её сразу в нужное место
1 2 3 |
sprintf(PAGE_BODY,"%s%s%s",PAGE_HEADER_200_OK,PAGE_HEADER_SERVER,PAGE_HEADER_CONTENT_TEXT); len = strlen(PAGE_BODY); osThreadList((uint8_t *)(PAGE_BODY + len)); |
Ну и напоследок передадим наш документ клиенту
1 2 3 |
osThreadList((uint8_t *)(PAGE_BODY + len)); netconn_write(conn, PAGE_BODY, strlen((char*)PAGE_BODY), NETCONN_COPY); } |
Соберём код, прошьём контроллер и посмотрим результат в браузере.
При запуске страницы мы видим такую картину
Нажмём на кнопку START и увидем, что у нас появится информация и затем она будет потихоньку обновляться (раз в секунду). Правда использование стека здесь показано максимальное, поэтому информация будет меняться не сильно
Хотя за счёт того, что информация никак не сортируется, то строки с задачами будут прыгать с места на место, создавая иллюзию того, что информация по строкам меняется. На самом деле, если отсортировать её, то мы заметим, что меняется она очень редко. Но, самое главное, что мы видим то, что информация, отправленная с сервера клиенту обновляется регулярно. А строки мы ещё отсортируем немного позже.
Хоть мы уже прекрасно видим, что страница вся у нас каждый раз не перезагружается, тем не менее убедимся в этом в WireShark (нажмите на картинку для увеличения изображения)
Конечно, если бы нам и правда пришлось бы следить за динамически изменяемыми данными, то нам было бы очень тяжело это делать в таком режиме, когда строчки прыгают то и дело вверх-вниз, поэтому попробуем наши данные отсортировать по алфавиту.
Только делать мы это будем уже в следующей части нашего занятия, в которой мы также поработаем с передачей данных бинарного типа и отобразим их в браузере в виде графика.
Предыдущая часть Программирование МК STM32 Следующая часть
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий