ESP32 TCP/IP — различия между версиями

Материал из razgovorov.ru
Перейти к: навигация, поиск
(Handling errors)
(TCP/IP Sockets)
 
(не показано 10 промежуточных версий этого же участника)
Строка 70: Строка 70:
 
Когда мы создаем серверный сокет, мы хотим, чтобы он прослушивал входящие запросы на соединение. Для этого нам нужно указать сокет, номер порта TCP/IP которого он должен прослушивать. Обратите внимание, что мы не предоставляем номер порта a непосредственно из значения int / short. Вместо этого мы возвращаем значение, возвращаемое функцией htons(). Эта функция выполняет преобразование числа в так называемый «порядок сетевого байта». Это порядок байтов, который был выбран по соглашению, который используется для передачи двоичных двоичных данных без знака через Интернет. Фактический формат - это «большой конец», что означает, что если мы возьмем число, такое как 9876 (десятичное), то оно представлено в двоичном виде как 00100110 10010100 или 0x26D4 в шестнадцатеричном формате. Для порядка сетевых байтов мы сначала передаем 00100110 (0x26), а затем 10010100 (0xD4). Важно понимать, что ESP32 представляет собой небольшую внутреннюю архитектуру, которая означает, что мы должны обязательно преобразовать 2 байта и 4 байта в сетевой порядок байтов (большой конец).
 
Когда мы создаем серверный сокет, мы хотим, чтобы он прослушивал входящие запросы на соединение. Для этого нам нужно указать сокет, номер порта TCP/IP которого он должен прослушивать. Обратите внимание, что мы не предоставляем номер порта a непосредственно из значения int / short. Вместо этого мы возвращаем значение, возвращаемое функцией htons(). Эта функция выполняет преобразование числа в так называемый «порядок сетевого байта». Это порядок байтов, который был выбран по соглашению, который используется для передачи двоичных двоичных данных без знака через Интернет. Фактический формат - это «большой конец», что означает, что если мы возьмем число, такое как 9876 (десятичное), то оно представлено в двоичном виде как 00100110 10010100 или 0x26D4 в шестнадцатеричном формате. Для порядка сетевых байтов мы сначала передаем 00100110 (0x26), а затем 10010100 (0xD4). Важно понимать, что ESP32 представляет собой небольшую внутреннюю архитектуру, которая означает, что мы должны обязательно преобразовать 2 байта и 4 байта в сетевой порядок байтов (большой конец).
  
На данном устройстве может использоваться только одно приложение за любой данный локальный номер порта. Если мы хотим связать номер порта с приложением, например, наше серверное приложение, в этом случае мы выполняем задачу с именем «привязка», которая связывает (или присваивает) номер порта сокету, который, в свою очередь, принадлежит приложению.
+
Приложение на ESP32 в один момент может использовать только один порт. Если мы хотим связать номер порта с приложением, например, наше серверное приложение, в этом случае мы выполняем задачу с именем «привязка», которая связывает (или присваивает) номер порта сокету, который, в свою очередь, принадлежит приложению.
  
 
<source lang=c>
 
<source lang=c>
Строка 88: Строка 88:
  
 
Залогом является количество запросов на соединение, которые будут выполняться во время выполнения и принимаются до их передачи в приложение для обработки. Способ думать об этом - это представить, что вы являетесь приложением, и вы можете делать только одно за раз. Например, вы можете разговаривать только с одним человеком за раз по телефону. Теперь представьте, что у вас есть секретарь, который обрабатывает ваши входящие звонки. Когда звонок прибывает, и вы не заняты, секретарь передает вам звонок. Теперь представьте, что вы заняты. В это время секретарь отвечает на звонок и просит абонента подождать. Когда вы освобождаетесь, она передает вам ожидающий звонок. Теперь предположим, что вы все еще заняты, когда звонит еще один клиент. Она также призывает этого звонящего подождать. Мы начинаем строить очередь абонентов. И именно здесь начинается игра с отставанием. Заготовка указывает время выполнения, сколько звонков может быть получено, и попросили подождать. Если поступит больше вызовов, чем позволяет наш backlog, время выполнения немедленно отклонит вызов. Это не только предотвращает потребление ресурсов при запуске на сервера, а также может использоваться в качестве индикатора для вызывающего абонента, что его лучше обслуживать в другом месте.
 
Залогом является количество запросов на соединение, которые будут выполняться во время выполнения и принимаются до их передачи в приложение для обработки. Способ думать об этом - это представить, что вы являетесь приложением, и вы можете делать только одно за раз. Например, вы можете разговаривать только с одним человеком за раз по телефону. Теперь представьте, что у вас есть секретарь, который обрабатывает ваши входящие звонки. Когда звонок прибывает, и вы не заняты, секретарь передает вам звонок. Теперь представьте, что вы заняты. В это время секретарь отвечает на звонок и просит абонента подождать. Когда вы освобождаетесь, она передает вам ожидающий звонок. Теперь предположим, что вы все еще заняты, когда звонит еще один клиент. Она также призывает этого звонящего подождать. Мы начинаем строить очередь абонентов. И именно здесь начинается игра с отставанием. Заготовка указывает время выполнения, сколько звонков может быть получено, и попросили подождать. Если поступит больше вызовов, чем позволяет наш backlog, время выполнения немедленно отклонит вызов. Это не только предотвращает потребление ресурсов при запуске на сервера, а также может использоваться в качестве индикатора для вызывающего абонента, что его лучше обслуживать в другом месте.
 
ТЗалогом является количество запросов на соединение, которые будут выполняться во время выполнения и принимаются до их передачи в приложение для обработки. Способ думать об этом - это представить, что вы являетесь приложением, и вы можете делать только одно за раз. Например, вы можете разговаривать только с одним человеком за раз по телефону. Теперь представьте, что у вас есть секретарь, который обрабатывает ваши входящие звонки. Когда звонок прибывает, и вы не заняты, секретарь передает вам звонок. Теперь представьте, что вы заняты. В это время секретарь отвечает на звонок и просит абонента подождать. Когда вы освобождаетесь, она передает вам ожидающий звонок. Теперь предположим, что вы все еще заняты, когда звонит еще один клиент. Она также призывает этого звонящего подождать. Мы начинаем строить очередь абонентов. И именно здесь начинается игра с отставанием. Заготовка указывает время выполнения, сколько звонков может быть получено, и попросили подождать. Если поступит больше вызовов, чем позволяет наш backlog, время выполнения немедленно отклонит вызов. Не только
 
Это предотвращает потребление ресурсов при запуске на сервере, а также может использоваться в качестве индикатора для вызывающего абонента, что его лучше обслуживать в другом месте.
 
  
 
Теперь с точки зрения сервера мы готовы сделать некоторые работы. Серверное приложение теперь может блокировать ожидания входящих клиентских подключений. Мысль о том, что цель сервера в жизни - обрабатывать клиентские запросы, а когда нет активного запроса клиента, нет ничего, что можно было бы сделать, кроме как ждать запроса. Хотя это, безусловно, одна модель, это не обязательно единственная модель или даже лучшая модель (во всех случаях). Обычно нам нравятся наши процессоры для «использования». Используемый означает, что, хотя он имеет продуктивную работу, он может сделать, тогда он должен это сделать. Если единственное, что может сделать наша программа, это вызов клиентских услуг, то исходная модель имеет смысл. Тем не менее, есть определенные программы, которые, если у них нет клиентского запроса на немедленную службу, могут потратить время на то, что полезно. Мы вернемся к этому понятию позже. На данный момент мы рассмотрим вызов функции accept(). Когда вызывается accept(), произойдет одна из двух вещей. Если соединение с клиентом не будет немедленно ждать нас, мы будем блокировать до такого времени в будущем, когда произойдет соединение с клиентом. В это время мы проснемся и получим связь с недавно прибывшим клиентом. Если, с другой стороны, мы вызываем accept(), и нас ждет клиентское соединение, мы немедленно получим это соединение, и мы продолжим. В обоих случаях мы вызываем accept() и возвращаем соединение с клиентом. Различие между случаями заключается в том, нужно ли нам ждать прибытия соединения.
 
Теперь с точки зрения сервера мы готовы сделать некоторые работы. Серверное приложение теперь может блокировать ожидания входящих клиентских подключений. Мысль о том, что цель сервера в жизни - обрабатывать клиентские запросы, а когда нет активного запроса клиента, нет ничего, что можно было бы сделать, кроме как ждать запроса. Хотя это, безусловно, одна модель, это не обязательно единственная модель или даже лучшая модель (во всех случаях). Обычно нам нравятся наши процессоры для «использования». Используемый означает, что, хотя он имеет продуктивную работу, он может сделать, тогда он должен это сделать. Если единственное, что может сделать наша программа, это вызов клиентских услуг, то исходная модель имеет смысл. Тем не менее, есть определенные программы, которые, если у них нет клиентского запроса на немедленную службу, могут потратить время на то, что полезно. Мы вернемся к этому понятию позже. На данный момент мы рассмотрим вызов функции accept(). Когда вызывается accept(), произойдет одна из двух вещей. Если соединение с клиентом не будет немедленно ждать нас, мы будем блокировать до такого времени в будущем, когда произойдет соединение с клиентом. В это время мы проснемся и получим связь с недавно прибывшим клиентом. Если, с другой стороны, мы вызываем accept(), и нас ждет клиентское соединение, мы немедленно получим это соединение, и мы продолжим. В обоих случаях мы вызываем accept() и возвращаем соединение с клиентом. Различие между случаями заключается в том, нужно ли нам ждать прибытия соединения.
Строка 268: Строка 265:
  
 
====Configuration settings====
 
====Configuration settings====
Within the "menuconfig" there are some settings that relate to TCP/IP and can be found
+
Внутри «menuconfig» есть некоторые параметры, относящиеся к TCP / IP, и их можно найти в настройках lwIP. Настройки:
within the lwIP settings. The settings are:
+
* Максимальное количество открытых сокетов - целое число - CONFIG_LWIP_MAX_SOCKETS - Это количество одновременно открытых сокетов. Значение по умолчанию - 4, а максимальное значение - 16.
• Max number of open sockets – integer – CONFIG_LWIP_MAX_SOCKETS – This is the
+
* Включить SO_REUSEADDR - boolean - LWIP_SO_REUSE -
number of concurrently open sockets. The default is 4 and the maximum
+
 
appears to be 16.
 
• Enable SO_REUSEADDR boolean LWIP_SO_REUSE
 
 
====Using select()====
 
====Using select()====
Imagine that we have multiple sockets each of which may be the source of incoming
+
Представьте, что у нас есть несколько сокетов, каждый из которых может быть источником входящих данных. Если мы попытаемся и читаем () данные из сокета, мы обычно блокируем, пока данные не будут готовы. Если мы это сделаем, то, если данные станут доступными в другом сокете, мы не узнаем. Альтернативой является попытка считывать данные неблокируемым способом. Это тоже было бы полезно, но потребовало бы, чтобы мы каждый раз тестировали каждый сокет в режиме занятости или опроса. Это оо не является оптимальным. В идеале, что мы хотели бы сделать, это блокировать, одновременно наблюдая за несколькими сокетами и просыпаясь, когда у первого есть что-то полезное для нас.
data. If we try and read() data from a socket, we normally block until data is ready. If
+
 
we did this, then if data becomes available on another socket, we wouldn't know. An
 
alternative is to try and read data in a non-blocking fashion. This too would be useful
 
but would require that we test each socket in turn in a busy or polling fashion. This too
 
is not optimal. Ideally what we would like to do is block while watching multiple sockets
 
simultaneously and wake up when the first one has something useful for us to do.
 
 
See also:
 
See also:
select
+
* select
The world of select()
+
* The world of select()
====Differences from "standard" sockets====
+
 
Two header files that are commonly found in other sockets implementations are not part
+
====Отличия от «стандартных» сокетов====
of the ESP-IDF definition. They are:
+
Два файла заголовка, которые обычно встречаются в других реализациях сокетов, не являются частью определения ESP-IDF. Они есть:
• netinet/in.h
+
* Netinet / in.h
• arpa/inet.h
+
* Arpa / inet.h
Despite not being present, no obvious issues have been found and it is assumed that
+
 
the content normally contained within has been distributed across other headers.
+
Несмотря на отсутствие присутствия, никаких очевидных проблем не обнаружено, и предполагается, что содержание, обычно содержащееся внутри, было распределено по другим заголовкам.
  
 
===UDP/IP Sockets===
 
===UDP/IP Sockets===
If we think of TCP as forming a connection between two parties similar to a telephone
+
Если мы думаем о TCP как о формировании связи между двумя сторонами, подобными телефонному звонку, то UDP походит на отправку письма через почтовую систему. Если бы я отправил вам письмо, мне нужно было узнать ваше имя и адрес. Ваш адрес необходим, чтобы письмо могло быть доставлено в правильный дом, пока ваше имя гарантирует, что оно окажется в ваших руках, в отличие от кого-то, кто может жить с вами. В терминах TCP / IP адрес - это IP-адрес, а имя - номер порта.
call, then UDP is like sending a letter through the postal system. If I were to send you a
+
 
letter, I would need to know your name and address. Your address is needed so that
+
С помощью телефонного разговора мы можем обмениваться такой же информацией, как нам нравится. Иногда я говорю, иногда вы говорите ... но нет максимального ограничения на то, сколько информации мы можем обменять в одном разговоре. Вместе с письмом есть только так много страниц бумаги, которые будут вписываться в конверты, которые у меня есть.
the letter can be delivered to the correct house while your name ensure that it ends up
+
 
in your hands as opposed to someone else who may live with you. In TCP/IP terms, the
+
Понятие почтовой аналогии - это то, как мы можем думать о UDP. Аббревиатура обозначает протокол User Datagram Protocol, и это понятие дейтаграммы, которое сродни букве. Дейтаграмма представляет собой массив байтов, которые передаются от отправителя в приемник как единое целое. Максимальный размер дейтаграммы с использованием UDP - 64 Кбайт. Не нужно устанавливать соединение между двумя сторонами, прежде чем данные начнут течь. Однако есть нижняя сторона. Отправитель данных не будет уведомлен о невозможности получения данных получателем. С TCP у нас есть установление связи между двумя сторонами, что позволяет отправителю знать, что данные были получены, а если нет, может автоматически повторно передавать до тех пор, пока он не будет получен или мы не откажемся. С UDP и точно так же, как письмо, когда мы отправляем дейтаграмму, мы теряем из виду, действительно ли он прибывает благополучно в пункт назначения.
address is the IP address and the name is the port number.
+
 
With a telephone conversation, we can exchange as much or as little information as we
+
Сейчас самое время вернуться к IP-адресам и номерам портов. Мы должны начать понимать, что на ПК только одно приложение может прослушивать любой порт. Например, если мое приложение прослушивает порт 12345, тогда никакое другое приложение также не может прослушивать тот же порт ... не ваше приложение, а другое копия / экземпляр моего экземпляра.
like. Sometimes I talk, sometimes you talk … but there is no maximum limit on how
+
 
much information we can exchange in one conversation. With a letter however, there
+
Когда входящее соединение или датаграмма поступает на машину, оно прибыло, потому что IP-адрес отправленных данных соответствует IP-адресу устройства, на котором он был отправлен.
are only so many pages of paper that will fit in the envelopes I have at my disposal.
+
 
The notion of the mail analogy is how we might choose to think about UDP. The
+
Затем мы маршрутизируем внутри устройства на основе номеров портов. И здесь я хочу уточнить детали. Мы прокладываем маршрут внутри машины на основе пары как протокола, так и номера порта.
acronym stands for User Datagram Protocol and it is the notion of the datagram that is
+
 
akin to the letter. A datagram is an array of bytes that are transmitted from the sender to
+
Так, например, если запрос поступает на машину для порта 12345 по TCP-соединению, он маршрутизируется в порт наблюдения 123 приложения TCP. Если запрос поступает на тот же компьютер для порта 12345 через UDP, он направляется в UDP Порт наблюдения приложений 12345. Это означает, что мы можем иметь два приложения, прослушивающих один и тот же порт, но по разным протоколам. Если сделать это более формально, пространство распределения для номеров портов является функцией протокола, и двум приложениям не разрешается одновременно резервировать один и тот же порт в одном и том же пространстве распределения протокола.
the receiver as a unit. The maximum size of a datagram using UDP is 64KBytes. No
+
 
connection need be setup between the two parties before data starts to flow. However,
+
Хотя я использовал историю ПК с несколькими приложениями, в нашем ESP32 история похожа, хотя мы просто запускаем одно приложение на устройстве. Если вашему одиночному приложению необходимо прослушивать несколько портов, не пытайтесь использовать один и тот же порт с тем же протоколом, что и второй вызов функции, первый из которых уже назначил порт. Это деталь, которую я рад за вас забыть, потому что вы редко сталкиваетесь с ней, но я хотел поймать ее здесь для полноты.
there is a down side. The sender of the data will not be made aware of a receiver's
+
 
failure to retrieve the data. With TCP, we have handshaking between the two parties
+
Чтобы запрограммировать UDP, мы снова используем сокеты. Чтобы снова настроить сервер сокетов с помощью UDP, мы вызываем socket () для создания сокета, и снова мы вызываем bind (), чтобы указать номер порта, который мы хотим прослушать. Нет необходимости в вызове listen (). Когда сервер готов принять входящий запрос, мы вызываем recvfrom (), который блокируется до получения дейтаграммы. Как только человек прибывает, мы просыпаемся и можем обрабатывать запрос.
that lets the sender know that the data was received and, if not, can automatically retransmit
+
 
until it has been received or we decide to give up. With UDP, and just like a
+
Запрос содержит обратный адрес, и мы можем отправить ответ, используя sendto (), если мы пожелаем.
letter, when we send a datagram, we lose sight of whether or not it actually arrives
+
 
safely at the destination.
+
На стороне клиента мы создаем сокет (), а затем можем вызывать sendto (). Вызов sendto () принимает IP-адрес и порт цели как параметры, так и данные полезной нагрузки.
Now is a good time to come back to IP addresses and port numbers. We should start to
+
 
be aware that on a PC, only one application can be listening upon any given port. For
+
Например:
example, if my application is listening on port 12345, then no other application can also
+
<source lang="c">
be listening on that same port … not your application nor another copy/instance of mine.
 
When an incoming connection or datagram arrives at a machine, it has arrived because
 
the IP address of the sent data matches the IP address of the device at which it arrived.
 
We then route within the device based on port numbers. And here is where I want to
 
clarify a detail. We route within the machine based on the pair of both protocol and port
 
number.
 
So for example, if a request arrives at a machine for port 12345 over a TCP connection,
 
it is routed to the TCP application watching port 12345. If a request arrives at the same
 
machine for port 12345 over UDP, it is routed to the UDP application watching port
 
12345. What this means is that we can have two applications listening on the same
 
port but on different protocols. Putting this more formally, the allocation space for port
 
numbers is a function of the protocol and it is not allowed for two applications to
 
simultaneously reserve the same port within the same protocol allocation space.
 
Although I used the story of a PC running multiple applications, in our ESP32 the story
 
is similar even though we just run one application on the device. If your single
 
application should need to listen on multiple ports, don't try and use the same port with
 
the same protocol as the second function call will find the first one has already allocated
 
the port. This is a detail that I am happy for you to forget as you will rarely come across
 
it but I wanted to catch it here for completeness.
 
To program with UDP, once again we use sockets. To set up a socket server using UDP
 
again we call socket() to create a socket and again we call bind() to specify the port
 
number we wish to listen upon. There is no need for a call to listen(). When the
 
server is ready to receive an incoming request, we call recvfrom() which blocks until a
 
datagram is received. Once one arrives, we wake up and can process the request.
 
The request contains a return address and we can send a response using sendto()
 
should we wish.
 
On the client side, we create a socket() and then can invoke sendto(). The call to
 
sendto() takes the IP address and port of the target as parameters as well as the
 
payload data.
 
For example:
 
 
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
 
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
 
sendto(socket_fd, data, size, 0, destAddr, destAddrLen);
 
sendto(socket_fd, data, size, 0, destAddr, destAddrLen);
socket
+
</source>
sendto
+
 
recvfrom
+
* socket
 +
* sendto
 +
* recvfrom
  
 
===TLS, SSL and security===
 
===TLS, SSL and security===
So far we have been thinking about making sockets calls that form a network
+
До сих пор мы думали о создании сокетов, которые формируют сетевое соединение, а затем отправляют и получают данные по этому соединению. Однако у нас есть проблема с безопасностью. Данные, которые протекают по проводу, не зашифрованы. Это означает, что если кто-то должен «обнюхать» или иным образом исследовать сетевые данные, мы увидим содержимое передаваемых данных. Например, если я отправляю пароль, используемый для аутентификации, если мы будем изучать содержимое данных, мы сможем определить пароль, который я использую.
connection and then sending and receiving data over that connection. However we
+
 
have a security problem. The data that flows over the wire is not encrypted. This
+
На самом деле не так сложно распознать отправленные и полученные данные. Отличные инструменты, такие как wirehark, используются для отладки и могут быть легко использованы для проверки содержимого сетевых пакетов или потоков. Очевидно, что мы уже обмениваемся данными кредитной карты, электронной почтой и другой конфиденциальной информацией через Интернет, как это делается?
means that if one were to "sniff" or otherwise examine the network data, we would see
+
 
the content of the data being transmitted. For example, if I send a password used for
+
Ответ - это концепция под названием «Secure Socket Layer» или SSL. SSL обеспечивает возможность шифрования данных перед передачей, так что только предполагаемый получатель может ее расшифровать. И наоборот, любые ответы, отправленные получателем, также зашифровываются таким образом, что только мы можем расшифровать данные. Если кто-то должен был захватывать или иным образом проверять данные, отправляемые по проводам, им не удастся вернуться к исходному содержанию данных.
authentication, if we were to examine the content of the data, we would be able to
+
 
determine the password I am using.
+
То, как это работает, - это концепция секретных ключей. Представьте себе, что я думаю о очень большом случайном числе (и большим я имею в виду ОЧЕНЬ большой). Мы называем этот частный номер своим личным ключом. Теперь представьте, что связанный с закрытым ключом соответствующий номер (открытый ключ), который можно использовать для дешифрования сообщения, которое было закодировано с помощью закрытого ключа. Теперь представьте себе, что я хочу, чтобы наравне с партнером. Я отправляю запрос (незашифрованный) партнеру и запрос его открытого ключа. Он отправляет это обратно, и я посылаю ему копию своего открытого ключа зашифрованного его открытым ключом. Поскольку для дешифрования данных можно использовать только подходящую пару открытых / закрытых ключей, только желаемый получатель может расшифровать сообщение, после чего у него будет копия моего открытого ключа.
It actually isn't that difficult to sense the data being sent and received. Excellent tools
+
 
such as wireshark are used for debugging and can easily be used to examine the
+
Теперь в будущем я могу отправить ему сообщения, зашифрованные моим личным ключом и зашифрованные его открытым ключом, и он сможет их расшифровать с помощью своей копии своего закрытого ключа и моего открытого ключа, пока он может отправлять мне зашифрованные сообщения, закодированные его Закрытый ключ, который я могу расшифровать с помощью моей копии открытого ключа. Поменяв открытые ключи, мы теперь можем продолжать обмен данными, не опасаясь, что это увидит кто-то еще.
content of the network packets or stream. Obviously we are already exchanging credit
+
 
card data, email and other sensitive information over the Internet so how is that done?
+
Все это шифрование данных происходит вне и выше знаний о сетях TCP/IP. TCP/IP обеспечивает доставку данных, но ничего не заботится о его содержании.
The answer is a concept called the "Secure Socket Layer" or SSL. SSL provides the
+
 
capability to encrypt the data before transmission such that only the intended recipient
+
Таким образом, и на высоком уровне, если мы хотим обмениваться защищенными данными, мы должны выполнить шифрование и дешифрование с использованием алгоритмов и библиотек, которые живут за пределами API сокетов, и использовать сокеты в качестве транспорта для передачи и получения зашифрованных данных, которые Поданных и полученных от алгоритмов шифрования.
can decrypt it. Conversely, any responses sent by the recipient are also encrypted such
+
 
that only we can decrypt the data. If someone were to capture or otherwise examine
+
При использовании mbed TLS нам нужен большой размер стека. Я еще не знаю, как мало мы можем уйти, но я использовал 8000 байт.
the data being sent over the wire, there is no way for them to get back to the original
+
 
data content.
 
The way this works is through the concept of private keys. Imagine I think of a very
 
large random number (and by large I mean VERY large). We call this private number
 
my private key. Now imagine that associated with the private key is a corresponding
 
number (the public key) that can be used to decrypt a message that was encoded using
 
the private key. Now imagine I want to correspond with a partner securely. I send a
 
request (unencrypted) to the partner and ask for his public key. He sends that back and
 
I send to him a copy of MY public key encrypted with his public key. Since only a
 
matching pair of public/private keys can be used to decrypt data, only the desired
 
recipient can decrypt the message at which point he will have a copy of my public key.
 
Now in the future I can send him messages encrypted with my private key and further
 
encrypted with his public key and he will be able to decode them with his copy of his
 
private key and my public key while he can send me encrypted messages encoded with
 
his private key which I can decode with my copy of his public key. By having
 
exchanged public keys, we are now good to continue exchanging data without fear that
 
it will be seen by anyone else.
 
All of this encryption of data happens outside and above the knowledge of TCP/IP
 
networking. TCP/IP provides the delivery of data but cares nothing about its content.
 
As such, and at a high level, if we wish to exchange secure data, we must perform the
 
encryption and decryption using algorithms and libraries that live outside of the sockets
 
API and use sockets as the transport for transmitting and receiving the encrypted data
 
that is fed into and received from the encryption algorithms.
 
When using mbed TLS, we need a large stack size. I don't yet know how small we can
 
get away with but I have been using 8000 bytes.
 
 
See also:
 
See also:
mbed TLS
+
* mbed TLS
mbed TLS home page
+
* mbed TLS home page
mbed TLS tutoria l
+
* mbed TLS tutoria l
mbed TLS API reference
+
* mbed TLS API reference
 +
 
 
====mbedTLS app structure====
 
====mbedTLS app structure====
Let us start to break down the structure of a TLS application that uses the mbedTLS
+
Давайте начнем разбирать структуру приложения TLS, которое использует API-интерфейсы mbedTLS.
APIs.
+
 
First there is the notion of a network context that is initialized by a call to
+
Во-первых, существует понятие сетевого контекста, который инициализируется вызовом mbedtls_net_init ().
mbedtls_net_init().
+
 
 +
<source lang=c>
 
mbedtls_net_context server_fd;
 
mbedtls_net_context server_fd;
 
mbedtls_net_init(&server_fd);
 
mbedtls_net_init(&server_fd);
There is nothing more to explain here. The data that is initialized is "opaque" to us and
+
</source>
the invocation of this function is part of the rules.
+
 
next comes the initialization of the SSL context with a call to mbedtls_ssl_init().
+
Здесь больше нечего объяснять. Инициализированные данные являются «непрозрачными» для нас, и вызов этой функции является частью правил.
 +
 
 +
Далее идет инициализация контекста SSL с вызовом:
 +
 
 +
<source lang=c>
 +
mbedtls_ssl_init().
 
mbedtls_ssl_context ssl;
 
mbedtls_ssl_context ssl;
 
mbedtls_ssl_init(&ssl);
 
mbedtls_ssl_init(&ssl);
Again, there is nothing more to explain here. The data is again opaque and this
+
</source>
function merely initializes it for us. Calling this function is also part of the rules.
+
 
Now we call mebtls_ssl_config_init().
+
Опять же, здесь больше нечего объяснять. Данные снова непрозрачны, и эта функция просто инициализирует его для нас. Вызов этой функции также является частью правил.
 +
 
 +
Теперь мы вызываем mebtls_ssl_config_init ().
 +
<source lang=c>
 
mbedtls_ssl_config config;
 
mbedtls_ssl_config config;
 
mbedtls_ssl_config_init(&config);
 
mbedtls_ssl_config_init(&config);
These initializations repeat for other data types including:
+
</source>
 +
 
 +
Эти инициализации повторяются для других типов данных, включая:
 +
<source lang=c>
 
mbedtls_ctr_drbg_context ctr_drbg;
 
mbedtls_ctr_drbg_context ctr_drbg;
 
mbedtls_ctr_drbg_init(&ctr_drbg);
 
mbedtls_ctr_drbg_init(&ctr_drbg);
Строка 424: Строка 375:
 
mbedtls_x509_crt cacert;
 
mbedtls_x509_crt cacert;
 
mbedtls_x509_crt_init(&cacert);
 
mbedtls_x509_crt_init(&cacert);
SSL utilizes good random number generators. What is a "good" random number?
+
</source>
Since computers are deterministic devices, the generation of a random number is
+
 
performed through the execution of an algorithm and since algorithms are deterministic,
+
SSL использует хорошие генераторы случайных чисел. Что такое «хорошее» случайное число?
then a sequence of numbers generated by these functions might, in principle, be
+
 
predictable. A good random number generator is one where the sequence of numbers
+
Поскольку компьютеры являются детерминированными устройствами, генерация случайного числа выполняется посредством выполнения алгоритма, и поскольку алгоритмы детерминированы, то последовательность чисел, порожденных этими функциями, может в принципе быть предсказуемой. Хорошим генератором случайных чисел является тот, где последовательность произведённых чисел вовсе не легко предсказуема и генерирует значения без смещения к их значениям с равной вероятностью любого числа в пределах выбранного диапазона.
produced is not at all easily predictable and generates values with no biases towards
+
 
their values with an equal probability of any number within a range being chosen.
+
Мы инициализируем генератор случайных чисел с вызовом bedtls_ctr_drbg_seed ().
We initialize the random number generator with a call to mbedtls_ctr_drbg_seed().
+
Примечание. Мы видим фразу «ctr_drbg» ..., которая является аббревиатурой «Генератор случайных байтов детерминированного режима». Это отраслевая стандартная спецификация / алгоритм для генерации случайных чисел.
Note: We see the phrase "ctr_drbg" … that is an acronym for "Counter mode Deterministic Random Byte
+
 
Generator". It is an industry standard specification/algorithm for generating random numbers.
+
[http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf]
http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf
+
 
With the setup under our belts, it is now time to start considering SSL based
+
С установкой под нашими ремнями пришло время начать рассмотрение связи на основе SSL. Поскольку мы рассматриваем SSL через сокеты, если мы не использовали сокеты TLS, мы бы выполнили вызов socket () для создания сокета, а затем connect () для подключения к нашему партнеру. В мире mbedtls мы называем mbedtls_net_connect (). Это имеет вид:
communication. Since we are considering SSL over sockets, if we were not using TLS
+
 
sockets, we would perform a call to socket() to create a socket and then connect() to
+
<source lang=c>
connect to our partner. In the world of mbedtls, we call mbedtls_net_connect(). This
 
has the form:
 
 
mbedtls_net_connect(&server_fd, <hostname>, <port>, MBEDTLS_NET_PROTO_TCP);
 
mbedtls_net_connect(&server_fd, <hostname>, <port>, MBEDTLS_NET_PROTO_TCP);
The hostname and port define where we are connecting to. Notice the first parameter.
+
</source>
This is the mbedtls_net_context structure that we initialized with a call to
+
 
mbedtls_net_init() previously. We should always check the return code to ensure that
+
Имя хоста и порт определяют, к чему мы подключаемся. Обратите внимание на первый параметр.
the connection was successful.
+
 
Now we get to configure our SSL defaults with a call to
+
Это структура mbedtls_net_context, которую мы инициализировали с помощью вызова mbedtls_net_init () ранее. Мы всегда должны проверять код возврата, чтобы убедиться, что соединение было успешным.
mbedtls_ssl_config_defaults(). For example:
+
Теперь мы настроим настройки SSL по умолчанию с вызовом mbedtls_ssl_config_defaults (). Например:
 +
 
 +
<source lang=c>
 
mbedtls_ssl_config_defaults(
 
mbedtls_ssl_config_defaults(
&conf,
+
  &conf, MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_IS_CLIENT,
+
  MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_TRANSPORT_STREAM,
+
  MBED_SSL_PRESET_DEFAULT)
MBED_SSL_PRESET_DEFAULT)
+
</source>
When we are communicating via SSL, we commonly wish to validate that the
+
 
credentials provided by our partner indicate that they are who they claim to be. This
+
Когда мы общаемся через SSL, мы обычно хотим подтвердить, что учетные данные, предоставленные нашим партнером, указывают на то, что они являются теми, кого они утверждают. Этот процесс называется аутентификацией. Мы можем определить, какую аутентификацию мы хотим выполнить, вызывая mbedtls_ssl_conf_authmode ().
process is called authentication. We can define what kind of authentication we wish to
+
 
perform by calling mbedtls_ssl_conf_authmode().
+
<source lang=c>
 
mbedtls_ssl_conf_authmode(&ssl, MBEDTLS_SSL_VERIFY_NONE)
 
mbedtls_ssl_conf_authmode(&ssl, MBEDTLS_SSL_VERIFY_NONE)
Earlier we said that SSL is heavily dependent on a good random number generator.
+
</source>
Now we tell the environment which random number generator we wish to use:
+
 
 +
Ранее мы говорили, что SSL сильно зависит от хорошего генератора случайных чисел.
 +
 
 +
Теперь мы расскажем об окружающей среде, какой генератор случайных чисел мы хотим использовать:
 +
<source lang=c>
 
mbedtls_ssl_conf_rng(&ssl, mbedtls_ctr_drbg_random, &ctr_drbg)
 
mbedtls_ssl_conf_rng(&ssl, mbedtls_ctr_drbg_random, &ctr_drbg)
Next we do some more SSL context setup by calling mbedtls_ssl_set_hostname().
+
</source>
 +
 
 +
Затем мы создадим еще несколько настроек контекста SSL, вызывая mbedtls_ssl_set_hostname().
 +
 
 +
<source lang=c>
 
mbedtls_ssl_set_hostname(&ssl, "name")
 
mbedtls_ssl_set_hostname(&ssl, "name")
Now we instruct the SSL environment which functions to use to send and receive data
+
</source>
by calling mbedtls_ssl_set_bio().
+
Теперь мы инструктируем среду SSL, которая функционирует для отправки и получения данных, вызывая mbedtls_ssl_set_bio ().
 +
 
 +
<source lang=c>
 
mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL)
 
mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL)
At this point, we have formed a connection to our partner and configured the SSL
+
</source>
environment. What remains is to actually read and write data. To write data we call
+
На этом этапе мы создали связь с нашим партнером и настроили среду SSL. Остается на самом деле читать и писать данные. Для записи данных мы называем mbed_ssl_write ().
mbed_ssl_write().
+
 
 +
<source lang=c>
 
mbedtls_ssl_write(&ssl, buf, len)
 
mbedtls_ssl_write(&ssl, buf, len)
 +
</source>
 +
 
and to read data we call mbedtls_ssl_read().
 
and to read data we call mbedtls_ssl_read().
 +
<source lang=c>
 
mbedtls_ssl_read(&ssl, buf, len)
 
mbedtls_ssl_read(&ssl, buf, len)
 +
</source>
 +
 
See also:
 
See also:
mbedtls_net_init
+
* mbedtls_net_init
mbedtls_ssl_init
+
* mbedtls_ssl_init
mbedtls_ssl_config_init
+
* mbedtls_ssl_config_init
mbedtls_net_connect
+
* mbedtls_net_connect
mbedtls_ssl_config_defaults
+
* mbedtls_ssl_config_defaults
mbedtls_ssl_conf_authmode
+
* mbedtls_ssl_conf_authmode
mbedtls_ssl_conf_rng
+
* mbedtls_ssl_conf_rng
mbedtls_ssl_set_hostname
+
* mbedtls_ssl_set_hostname
mbedtls_ssl_set_bio
+
* mbedtls_ssl_set_bio
mbedtls_ssl_write
+
* mbedtls_ssl_write
mbedtls_ssl_read
+
* mbedtls_ssl_read
 +
 
 
====mbedTLS Example====
 
====mbedTLS Example====
Here is a sample function that has been tested on an ESP32 to make an HTTPS call to
+
Вот примерная функция, которая была протестирована на ESP32, чтобы вызвать HTTPS-вызов на сервер HTTPS для получения некоторых результатов.
an HTTPS server to retrieve some results.
+
 
 +
<source lang=c>
 
#include "mbedtls/platform.h"
 
#include "mbedtls/platform.h"
 
#include "mbedtls/ctr_drbg.h"
 
#include "mbedtls/ctr_drbg.h"
Строка 500: Строка 469:
 
static char tag[] = "callhttps";
 
static char tag[] = "callhttps";
 
static char errortext[256];
 
static char errortext[256];
 +
 
static void my_debug(void *ctx, int level, const char *file, int line, const char *str) {
 
static void my_debug(void *ctx, int level, const char *file, int line, const char *str) {
((void) level);
+
  ((void) level);
((void) ctx);
+
  ((void) ctx);
printf("%s:%04d: %s", file, line, str);
+
  printf("%s:%04d: %s", file, line, str);
 
}
 
}
 
void callhttps() {
 
void callhttps() {
ESP_LOGD(tag, "--> callhttps\n");
+
  ESP_LOGD(tag, "--> callhttps\n");
mbedtls_net_context server_fd;
+
  mbedtls_net_context server_fd;
mbedtls_entropy_context entropy;
+
  mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
+
  mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl;
+
  mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
+
  mbedtls_ssl_config conf;
mbedtls_x509_crt cacert;
+
  mbedtls_x509_crt cacert;
int ret;
+
  int ret;
int len;
+
  int len;
char *pers = "ssl_client1";
+
  char *pers = "ssl_client1";
unsigned char buf[1024];
+
  unsigned char buf[1024];
mbedtls_net_init(&server_fd);
+
  mbedtls_net_init(&server_fd);
mbedtls_ssl_init(&ssl);
+
  mbedtls_ssl_init(&ssl);
mbedtls_ssl_config_init(&conf);
+
  mbedtls_ssl_config_init(&conf);
mbedtls_x509_crt_init(&cacert);
+
  mbedtls_x509_crt_init(&cacert);
mbedtls_ctr_drbg_init(&ctr_drbg);
+
  mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
+
  mbedtls_entropy_init(&entropy);
mbedtls_ssl_conf_dbg(&conf, my_debug, stdout);
+
  mbedtls_ssl_conf_dbg(&conf, my_debug, stdout);
mbedtls_debug_set_threshold(2); // Log at error only
+
  mbedtls_debug_set_threshold(2); // Log at error only
ret = mbedtls_ctr_drbg_seed(
+
  ret = mbedtls_ctr_drbg_seed(
&ctr_drbg,
+
  &ctr_drbg,
mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen(pers));
+
  mbedtls_entropy_func, &entropy, (const unsigned char *) pers,             strlen(pers));
if (ret != 0) {
+
  if (ret != 0) {
ESP_LOGE(tag, " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret);
+
      ESP_LOGE(tag, " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret);
return;
+
      return;
 +
  }
 +
  ret = mbedtls_net_connect(&server_fd, SERVER_NAME, SERVER_PORT, MBEDTLS_NET_PROTO_TCP);
 +
  if (ret != 0) {
 +
      ESP_LOGE(tag, " failed\n ! mbedtls_net_connect returned %d\n\n", ret);
 +
      return;
 +
  }
 +
  ret = mbedtls_ssl_config_defaults(
 +
      &conf,
 +
      MBEDTLS_SSL_IS_CLIENT,
 +
      MBEDTLS_SSL_TRANSPORT_STREAM,
 +
      MBEDTLS_SSL_PRESET_DEFAULT);
 +
  if (ret != 0) {
 +
      ESP_LOGE(tag, " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret);
 +
  return;
 +
  }
 +
  mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
 +
  mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
 +
  ret = mbedtls_ssl_setup(&ssl, &conf);
 +
  if (ret != 0) {
 +
      mbedtls_strerror(ret, errortext, sizeof(errortext));
 +
      ESP_LOGE(tag, "error from mbedtls_ssl_setup: %d - %x - %s\n", ret, ret, errortext);
 +
      return;
 +
  }
 +
  ret = mbedtls_ssl_set_hostname(&ssl, "httpbin.org");
 +
  if (ret != 0) {
 +
        mbedtls_strerror(ret, errortext, sizeof(errortext));
 +
        ESP_LOGE(tag, "error from mbedtls_ssl_set_hostname: %d - %x -      %s\n", ret, ret, errortext);
 +
      return;
 +
  }
 +
  mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send,      mbedtls_net_recv, NULL);
 +
  char *requestMessage = \
 +
      "GET /ip HTTP/1.1\r\n" \
 +
      "User-Agent: kolban\r\n" \
 +
      "Host: httpbin.org\r\n" \
 +
      "Accept-Language: en-us\r\n" \
 +
      "Accept-Encoding: gzip, deflate\r\n" \
 +
      "\r\n";
 +
  sprintf((char *)buf, requestMessage);
 +
  len = strlen((char *)buf);
 +
  ret = mbedtls_ssl_write(&ssl, buf, len);
 +
  if (ret < 0) {
 +
      mbedtls_strerror(ret, errortext, sizeof(errortext));
 +
      ESP_LOGE(tag, "error from write: %d -%x - %s\n", ret, ret,      errortext);
 +
      return;
 +
  }
 +
  len = sizeof(buf);
 +
  ret = mbedtls_ssl_read(&ssl, buf, len);
 +
  if (ret < 0) {
 +
      ESP_LOGE(tag, "error from read: %d\n", len);
 +
      return;
 +
  }
 +
  printf("Result:\n%.*s\n", len, buf);
 +
  mbedtls_net_free(&server_fd);
 +
  mbedtls_ssl_free(&ssl);
 +
  mbedtls_ssl_config_free(&conf);
 +
  mbedtls_ctr_drbg_free(&ctr_drbg);
 +
  mbedtls_entropy_free(&entropy);
 +
  ESP_LOGV(tag, "All done");
 
}
 
}
ret = mbedtls_net_connect(&server_fd, SERVER_NAME, SERVER_PORT, MBEDTLS_NET_PROTO_TCP);
+
</source>
if (ret != 0) {
+
 
ESP_LOGE(tag, " failed\n ! mbedtls_net_connect returned %d\n\n", ret);
+
Заметки:
return;
+
 
}
+
При отладке MBED в Curl установите значение MBEDTLS_DEBUG на 1 в curl_config.h
ret = mbedtls_ssl_config_defaults(
 
&conf,
 
MBEDTLS_SSL_IS_CLIENT,
 
MBEDTLS_SSL_TRANSPORT_STREAM,
 
MBEDTLS_SSL_PRESET_DEFAULT);
 
if (ret != 0) {
 
ESP_LOGE(tag, " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret);
 
return;
 
}
 
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
 
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
 
ret = mbedtls_ssl_setup(&ssl, &conf);
 
if (ret != 0) {
 
mbedtls_strerror(ret, errortext, sizeof(errortext));
 
ESP_LOGE(tag, "error from mbedtls_ssl_setup: %d - %x - %s\n", ret, ret, errortext);
 
return;
 
}
 
ret = mbedtls_ssl_set_hostname(&ssl, "httpbin.org");
 
if (ret != 0) {
 
mbedtls_strerror(ret, errortext, sizeof(errortext));
 
ESP_LOGE(tag, "error from mbedtls_ssl_set_hostname: %d - %x - %s\n", ret, ret, errortext);
 
return;
 
}
 
mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
 
char *requestMessage = \
 
"GET /ip HTTP/1.1\r\n" \
 
"User-Agent: kolban\r\n" \
 
"Host: httpbin.org\r\n" \
 
"Accept-Language: en-us\r\n" \
 
"Accept-Encoding: gzip, deflate\r\n" \
 
"\r\n";
 
sprintf((char *)buf, requestMessage);
 
len = strlen((char *)buf);
 
ret = mbedtls_ssl_write(&ssl, buf, len);
 
if (ret < 0) {
 
mbedtls_strerror(ret, errortext, sizeof(errortext));
 
ESP_LOGE(tag, "error from write: %d -%x - %s\n", ret, ret, errortext);
 
return;
 
}
 
len = sizeof(buf);
 
ret = mbedtls_ssl_read(&ssl, buf, len);
 
if (ret < 0) {
 
ESP_LOGE(tag, "error from read: %d\n", len);
 
return;
 
}
 
printf("Result:\n%.*s\n", len, buf);
 
mbedtls_net_free(&server_fd);
 
mbedtls_ssl_free(&ssl);
 
mbedtls_ssl_config_free(&conf);
 
mbedtls_ctr_drbg_free(&ctr_drbg);
 
mbedtls_entropy_free(&entropy);
 
ESP_LOGV(tag, "All done");
 
}
 
Notes:
 
When debugging MBED in Curl set MBEDTLS_DEBUG to 1 in curl_config.h
 
  
 
====OpenSSL====
 
====OpenSSL====
OpenSSL is a popular implementation of an SSL stack. In the ESP32 environment, the
+
OpenSSL - популярная реализация стека SSL. В среде ESP32 выбранный стек для SSL / TLS является mbedTLS, который не совпадает с OpenSSL. В рамках ESP-IDF был предоставлен слой отображения, который предоставляет API OpenSSL поверх реализации mbedTLS.
selected stack for SSL/TLS is mbedTLS which is not the same as OpenSSL. As part of
 
the ESP-IDF, a mapping layer has been provided that exposes the OpenSSL API on top
 
of an mbedTLS implementation.
 
  
 
===Name Service===
 
===Name Service===
 
В Интернете серверные машины можно найти по их службе имен доменов (DNS). Это сервис, который переводит читаемое человеком представление машины, например «google.com», в необходимое значение IP-адреса (например, 216.58.217.206).  
 
В Интернете серверные машины можно найти по их службе имен доменов (DNS). Это сервис, который переводит читаемое человеком представление машины, например «google.com», в необходимое значение IP-адреса (например, 216.58.217.206).  
 +
 
Чтобы это преобразование произошло, ESP32 должен знать IP-адрес одного или более DNS-серверов, которые затем будут использоваться для выполнения преобразования имени в IP-адрес.  
 
Чтобы это преобразование произошло, ESP32 должен знать IP-адрес одного или более DNS-серверов, которые затем будут использоваться для выполнения преобразования имени в IP-адрес.  
 +
 
Если мы используем DHCP, тогда больше ничего не нужно делать - DHCP-сервер автоматически предоставляет адреса DNS-серверов.  
 
Если мы используем DHCP, тогда больше ничего не нужно делать - DHCP-сервер автоматически предоставляет адреса DNS-серверов.  
Однако, если мы не используем DHCP (например усли мы используем статические IP-адреса), тогда нам нужно проинструктировать ESP32
+
 
 +
Однако, если мы не используем DHCP (например если мы используем статические IP-адреса), тогда нам нужно проинструктировать ESP32
 
о местоположения DNS-серверов вручную. Мы можем сделать это с помощью функции dns_setserver().
 
о местоположения DNS-серверов вручную. Мы можем сделать это с помощью функции dns_setserver().
Это принимает IP-адрес как входной вместе с каким из двух возможных DNS-серверов для
+
 
использовать. ESP32 настроен на то, чтобы знать личность до двух внешних серверов имен.
+
Это принимает IP-адрес как входной вместе с каким из двух возможных DNS-серверов для использования. ESP32 настроен на то, чтобы знать личность до двух внешних серверов имен.
Причина для двоих заключается в том, что если попытка достичь первого не удастся, мы будем использовать
+
 
второй. Мы можем получить наши текущие идентификаторы DNS-сервера, используя dns_getserver ().
+
Причина для двоих заключается в том, что если попытка достичь первого не удастся, мы будем использовать второй. Мы можем получить наши текущие идентификаторы DNS-сервера, используя dns_getserver ().
 
Google публично предоставляет два сервера имен с адресами 8.8.8.8 и 8.8.4.4.
 
Google публично предоставляет два сервера имен с адресами 8.8.8.8 и 8.8.4.4.
 
Как только мы определим серверы имен, мы можем найти адрес имени хоста используя функцию gethostbyname().
 
Как только мы определим серверы имен, мы можем найти адрес имени хоста используя функцию gethostbyname().
Во время разработки мы можем протестировать конкретный DNS-сервер, чтобы проверить, что он может определить по имени хоста.  
+
 
 +
Во время разработки мы можем протестировать конкретный DNS-сервер, чтобы проверить, что он может определить по имени хоста.
 +
 
Отличным инструментом Linux для выполнения этой задачи является «nslookup». В нем есть многие варианты, но для наших целей мы можем предоставить им имя хоста для поиска и использовать DNS-сервер:
 
Отличным инструментом Linux для выполнения этой задачи является «nslookup». В нем есть многие варианты, но для наших целей мы можем предоставить им имя хоста для поиска и использовать DNS-сервер:
<source>
+
<source дфтп ифыр>
 
$ nslookup example.com 8.8.8.8
 
$ nslookup example.com 8.8.8.8
 
Server: 8.8.8.8
 
Server: 8.8.8.8
Строка 620: Строка 595:
 
Name: example.com
 
Name: example.com
 
Address: 93.184.216.34
 
Address: 93.184.216.34
 +
</source>
 
See also:
 
See also:
gethostbyname
+
gethostbyname
dns_getserver
+
* dns_getserver
dns_setserver
+
* dns_setserver
Wikipedia: Domain Name System
+
* Wikipedia: Domain Name System
Google: Public DNS
+
* Google: Public DNS
</source>
 
  
 
===Multicast Domain Name Systems===
 
===Multicast Domain Name Systems===
Строка 642: Строка 617:
  
 
====mDNS API programming====
 
====mDNS API programming====
The ESP-IDF provides a set of rich APIs for programming mDNS on the ESP32.
+
ESP-IDF предоставляет набор богатых API для программирования mDNS на ESP32.
Specifically, we can either advertise ourselves in mDNS or else query existing mDNS
+
 
information.
+
В частности, мы можем либо рекламировать себя в mDNS, либо запросить существующую информацию mDNS.
The attributes of an mDNS server entry appear to be:
+
 
hostname – mdns_set_hostname()
+
Атрибуты записи сервера mDNS выглядят следующим образом:
default instance – mdns_set_instance()
+
* hostname – mdns_set_hostname()
service – mdns_service_add()
+
* default instance – mdns_set_instance()
type – _http, _ftp etc etc
+
* service – mdns_service_add()
protocol – _tcp, _udp etc etc
+
** type – _http, _ftp etc etc
port – port number
+
** protocol – _tcp, _udp etc etc
instance name for service – mdns_service_instance_set()
+
** port – port number
TXT data for service – mdns_service_txt_set()
+
* instance name for service – mdns_service_instance_set()
 +
* TXT data for service – mdns_service_txt_set()
 
See also:
 
See also:
mdns_set_hostname
+
* mdns_set_hostname
mdns_set_instance
+
* mdns_set_instance
mdns_service_add
+
* mdns_service_add
mdns_service_instance_set
+
* mdns_service_instance_set
mdns_service_port_set
+
* mdns_service_port_set
 +
 
 
====Installing Bonjour====
 
====Installing Bonjour====
 
Launch the Bonjour installer:
 
Launch the Bonjour installer:
If all has gone well, we will find a new Windows service running called "Bonjour
+
 
Service":
+
Если все пошло хорошо, мы найдем новую службу Windows, называемую "Bonjour Service":
There is also a Bonjour browser available here …
+
[http://hobbyistsoftware.com/bonjourbrowser There is also a Bonjour browser available here …]
http://hobbyistsoftware.com/bonjourbrowser
 
  
 
====Avahi====
 
====Avahi====
An implementation of Multicast DNS on Linux is called Avahi. Avahi runs as the systemd
+
Реализация многоадресной DNS в Linux называется Avahi. Avahi работает как демон systemd под названием «avahi-daemon». Мы можем определить, работает ли он с:
daemon called "avahi-daemon". We can determine whether or not it is running with:
+
 
 +
<source lang=bash>
 
$ systemctl status avahi-daemon
 
$ systemctl status avahi-daemon
 
● avahi-daemon.service - Avahi mDNS/DNS-SD Stack
 
● avahi-daemon.service - Avahi mDNS/DNS-SD Stack
 
Loaded: loaded (/lib/systemd/system/avahi-daemon.service; enabled)
 
Loaded: loaded (/lib/systemd/system/avahi-daemon.service; enabled)
Active: active (running) since Wed 2016-01-20 22:13:35 CST; 1 day 13h ago
+
Active: active (running) since Wed 2016-01-20 22:13:35 CST; 1 day 13h ago Main PID: 384 (avahi-daemon)
Main PID: 384 (avahi-daemon)
 
 
Status: "avahi-daemon 0.6.31 starting up."
 
Status: "avahi-daemon 0.6.31 starting up."
 
CGroup: /system.slice/avahi-daemon.service
 
CGroup: /system.slice/avahi-daemon.service
Строка 680: Строка 656:
 
└─426 avahi-daemon: chroot helper
 
└─426 avahi-daemon: chroot helper
 
The avahi-daemon utilizes a configuration file found at /etc/avahi/avahi-daemon.conf.
 
The avahi-daemon utilizes a configuration file found at /etc/avahi/avahi-daemon.conf.
The default name that avahi advertizes itself as is the local hostname.
+
</source>
When host-name resolution is performed, the system file called /etc/nsswitch.conf is
+
 
used to determine the order of resolution. Specifically the hosts entry contains the
+
Имя по умолчанию, которое avahi рекламирует как локальное имя хоста.
name resolution. An example would be:
+
Когда выполняется разрешение имени хоста, системный файл с именем /etc/nsswitch.conf используется для определения порядка разрешения. В частности, запись хостов содержит разрешение имен. Примером может служить:
 +
<source lang=bash>
 
hosts: files mdns4_minimal [NOTFOUND=return] dns
 
hosts: files mdns4_minimal [NOTFOUND=return] dns
Which says "first look in /etc/hosts, then consult mDNS and then use full DNS". What
+
</source>
this means is that a device which advertizes itself with mDNS can be found via a lookup
+
 
of "<hostname>.local". For example, if I boot up a Linux machine which gets a dynamic
+
Что говорит «сначала посмотрите в / etc / hosts, затем обратитесь к mDNS, а затем используйте полный DNS». Это означает, что устройство, которое рекламирует себя с помощью mDNS, может быть найдено с помощью поиска «<hostname> .local». Например, если я загружаю машину Linux, которая получает динамический IP-адрес через DHCP, а имя хоста этого компьютера - «chip1», то я могу связаться с ним с адресом домена «chip1.local». Если IP-адрес устройства изменяется, последующие разрешения имени домена будут продолжать корректно разрешаться.
IP address through DHCP and the hostname of that machine is "chip1", then I can
+
 
reach it with a domain name address of "chip1.local". If the IP address of the device
+
Инструменты Avahi не устанавливаются по умолчанию, но могут быть установлены с использованием пакета «avahi-utils»:
changes, subsequent resolutions of the domain name will continue to correctly resolve.
+
 
Avahi tools are not installed by default but can be installed using the "avahi-utils"
+
<source lang=bash>
package:
 
 
$ sudo apt-get install avahi-utils
 
$ sudo apt-get install avahi-utils
To see the list of mDNS devices in your network, we can use the avahi-browse
+
</source>
command. For example:
+
 
 +
Чтобы просмотреть список устройств mDNS в вашей сети, мы можем использовать команду avahi-browse. Например:
 +
 
 +
<source lang=bash>
 
$ avahi-browse -at
 
$ avahi-browse -at
 
+ wlan1 IPv6 chip1 [ce:79:cf:21:db:95] Workstation local
 
+ wlan1 IPv6 chip1 [ce:79:cf:21:db:95] Workstation local
Строка 718: Строка 697:
 
+ wlan0 IPv4 Living Room _googlecast._tcp local
 
+ wlan0 IPv4 Living Room _googlecast._tcp local
 
+ wlan0 IPv4 123456789 _teamviewer._tcp local
 
+ wlan0 IPv4 123456789 _teamviewer._tcp local
To access an mDNS advertized server from Microsoft Windows, you will need a service
+
</source>
similar to Apple's Bonjour installed. Bonjour is distributed as part of Apple's iTunes
+
Чтобы получить доступ к объявленному сервером mDNS из Microsoft Windows, вам понадобится служба, аналогичная установленному Bonjour от Apple. Bonjour распространяется как часть продукта iTunes от Apple. После установки мы должны иметь доступ к опубликованным серверам по адресу <name> .local. Доступен инструмент партнерских окон под названием «Bonjour Browser»
product. Once installed, we should be able to access the published servers at their
+
Который отображает список серверов mDNS на окнах.
<name>.local address. A partner windows tool called "Bonjour Browser" is available
+
 
which displays an mDNS listing of servers on windows.
 
 
See also:
 
See also:
avahi home page
+
* avahi home page
man(1) – avahi-browse
+
* man(1) – avahi-browse
man(5) – avahi-daemon.conf
+
* man(5) – avahi-daemon.conf
  
 
===Working with SNTP===
 
===Working with SNTP===
SNTP is the Simple Network Time Protocol and allows a device connected to the
+
SNTP is the Simple Network Time Protocol and allows a device connected to the Internet to learn the current time. In order to use this, you must know of at least one time server located on the Internet. The US National Institute for Science and Technology (NIST) maintains a number of these which can be found here:
Internet to learn the current time. In order to use this, you must know of at least one
+
 
time server located on the Internet. The US National Institute for Science and
 
Technology (NIST) maintains a number of these which can be found here:
 
 
• http://tf.nist.gov/tf-cgi/servers.cg i
 
• http://tf.nist.gov/tf-cgi/servers.cg i
Other time servers can be found all over the globe and I encourage you to Google
+
 
search for your nearest or country specific server.
+
Other time servers can be found all over the globe and I encourage you to Google search for your nearest or country specific server.
Once you know the identity of a server by its host name or IP address, you can call
+
 
either of the functions called sntp_setservername() or sntp_setserver() to declare that
+
Once you know the identity of a server by its host name or IP address, you can call either of the functions called sntp_setservername() or sntp_setserver() to declare that we wish to use that time server instance. The ESP32 can be configured with up to three different time servers so that if one or two are not available, we might still get a
we wish to use that time server instance. The ESP32 can be configured with up to
 
three different time servers so that if one or two are not available, we might still get a
 
 
result.
 
result.
The ESP32 must also be told the local timezone in which it is running. This is set with a
+
 
call to sntp_set_timezone() which takes the number of hours offset from UTC. For
+
The ESP32 must also be told the local timezone in which it is running. This is set with a call to sntp_set_timezone() which takes the number of hours offset from UTC. For example, I am in Texas and my timezone offset becomes "-5". Although this function is present, I would suggest using the POSIX tzset() function instead.
example, I am in Texas and my timezone offset becomes "-5". Although this function is
+
 
present, I would suggest using the POSIX tzset() function instead.
+
With these configured, we can start the SNTP service on the ESP32 by calling sntp_init(). This will cause the device to determine its current time by sending packets over the network to the time servers and examining their responses. It is important to note that immediately after calling sntp_init(), you will not yet know what the current time may be. This is because it may take a few seconds for the ESP32 to
With these configured, we can start the SNTP service on the ESP32 by calling
+
sends the time requests and get their responses and this will all happen asynchronously to your current commands and won't complete till sometime later.
sntp_init(). This will cause the device to determine its current time by sending
+
 
packets over the network to the time servers and examining their responses. It is
 
important to note that immediately after calling sntp_init(), you will not yet know what
 
the current time may be. This is because it may take a few seconds for the ESP32 to
 
sends the time requests and get their responses and this will all happen asynchronously
 
to your current commands and won't complete till sometime later.
 
 
When ready, we can retrieve the current time with a call to
 
When ready, we can retrieve the current time with a call to
sntp_get_current_timestamp() which will return the number of seconds since the 1st of
+
sntp_get_current_timestamp() which will return the number of seconds since the 1st of January 1970 UTC. We can also call the function called sntp_get_real_time() which will return a string representation of the time. While these functions obviously exist, I would not recommend using them. Instead look at the POSIX alternatives which are
January 1970 UTC. We can also call the function called sntp_get_real_time() which
 
will return a string representation of the time. While these functions obviously exist, I
 
would not recommend using them. Instead look at the POSIX alternatives which are
 
 
time() and asctime().
 
time() and asctime().
Here is an example of using SNTP to set the time:
+
 
 +
Here is an example of using SNTP to set the time:  
 +
 
 
ip_addr_t addr;
 
ip_addr_t addr;
 
sntp_setoperatingmode(SNTP_OPMODE_POLL);
 
sntp_setoperatingmode(SNTP_OPMODE_POLL);
Строка 764: Строка 732:
 
sntp_setserver(0, &addr);
 
sntp_setserver(0, &addr);
 
sntp_init();
 
sntp_init();
 +
 
The time can be accessed from a variety of POSIX compliant functions including:
 
The time can be accessed from a variety of POSIX compliant functions including:
asctime – Build a string representation of time.
+
* asctime – Build a string representation of time.
clock – Return processor time.
+
* clock – Return processor time.
ctime – Build a string representation of time.
+
* ctime – Build a string representation of time.
difftime – Calculate a time difference.
+
* difftime – Calculate a time difference.
gettimeofday – Retrieve the current time of day.
+
* gettimeofday – Retrieve the current time of day.
gmtime – Produce a struct tm from a time_t.
+
* gmtime – Produce a struct tm from a time_t.
localtime – Produce a struct tm from a time_t.
+
* localtime – Produce a struct tm from a time_t.
settimeofday – Set the current time.
+
* settimeofday – Set the current time.
strftime – Format a time_t to a string.
+
* strftime – Format a time_t to a string.
time – Get the current time as a time_t (seconds since epoch).
+
* time – Get the current time as a time_t (seconds since epoch).
 +
 
 
See also:
 
See also:
SNTP API
+
* SNTP API
Timers and time
+
* Timers and time
asctime
+
* asctime
ctime
+
* ctime
gmtime
+
* gmtime
localtime
+
* localtime
strftime
+
* strftime
IETF RFC5905: Network Time Protocol Version 4: Protocol and Algorithms Specification
+
* IETF RFC5905: Network Time Protocol Version 4: Protocol and  
 +
 
 +
Algorithms Specification
 
===Java Sockets===
 
===Java Sockets===
The sockets API is the defacto standard API for programming against TCP/IP. My
+
The sockets API is the defacto standard API for programming against TCP/IP. My programming language of choice is Java and it has full support for sockets. What this
programming language of choice is Java and it has full support for sockets. What this
+
means is that I can write a Java based application that leverages sockets to communicate with the ESP32. I can send and receive data through quite easily.
means is that I can write a Java based application that leverages sockets to
+
 
communicate with the ESP32. I can send and receive data through quite easily.
+
In Java, there are two primary classes that represents sockets, those are java.net.Socket which represents a client application which will form a connection and the second class is java.net.ServerSocket which represents a server that is listening on a socket awaiting a client connection. Since the ESP32 can be either a client or a server, both of these Java classes will come into play.
In Java, there are two primary classes that represents sockets, those are
+
 
java.net.Socket which represents a client application which will form a connection and
+
To connect to an ESP32 running as a server, we need to know the IP address of the device and the port number on which it is listening. Once we know those, we can create an instance of the Java client with:
the second class is java.net.ServerSocket which represents a server that is listening
 
on a socket awaiting a client connection. Since the ESP32 can be either a client or a
 
server, both of these Java classes will come into play.
 
To connect to an ESP32 running as a server, we need to know the IP address of the
 
device and the port number on which it is listening. Once we know those, we can
 
create an instance of the Java client with:
 
 
Socket clientSocket = new Socket(ipAddress, port);
 
Socket clientSocket = new Socket(ipAddress, port);
This will form a connection to the ESP32. Now we can ask for both an InputStream
+
 
from which to receive partner data and an OutputStream to which we can write data.
+
This will form a connection to the ESP32. Now we can ask for both an InputStream from which to receive partner data and an OutputStream to which we can write data.
 +
 
 
InputStream is = clientSocket.getInputStream();
 
InputStream is = clientSocket.getInputStream();
 
OutputStream os = clientSocket.getOutputStream();
 
OutputStream os = clientSocket.getOutputStream();
When we are finished with the connection, we should call close() to close the Java side
+
 
of the connection:
+
When we are finished with the connection, we should call close() to close the Java side of the connection:
 +
 
 
clientSocket.close();
 
clientSocket.close();
 +
 
It really is as simple as that. Here is an example application:
 
It really is as simple as that. Here is an example application:
 
package kolban;
 
package kolban;
 +
 +
<source lang=java>
 
import java.io.OutputStream;
 
import java.io.OutputStream;
 
import java.net.Socket;
 
import java.net.Socket;
Строка 849: Строка 820:
 
} // End of class
 
} // End of class
 
// End of file
 
// End of file
To configure a Java application as a socket server is just as easy. This time we create
+
</source>
an instance of the SocketServer class using:
+
 
 +
To configure a Java application as a socket server is just as easy.  
 +
This time we create an instance of the SocketServer class using:
 +
 
 
SocketServer serverSocket = new SocketServer(port)
 
SocketServer serverSocket = new SocketServer(port)
The port supplied is the port number on the machine on which the JVM is running that
+
The port supplied is the port number on the machine on which the JVM is running that will be the endpoint of remote client connection requests. Once we have a ServerSocket instance, we need to wait for an incoming client connection. We do this using the blocking API method called accept().
will be the endpoint of remote client connection requests. Once we have a
+
 
ServerSocket instance, we need to wait for an incoming client connection. We do this
 
using the blocking API method called accept().
 
 
Socket partnerSocket = serverSocket.accept();
 
Socket partnerSocket = serverSocket.accept();
This call blocks until a client connect arrives. The returned partnerSocket is the
+
This call blocks until a client connect arrives. The returned partnerSocket is the connected socket to the partner which can used in the same fashion as we previously discussed for client connections.  
connected socket to the partner which can used in the same fashion as we previously
+
 
discussed for client connections. This means that we can request the InputStream and
+
This means that we can request the InputStream and OutputStream objects to read and write to and from the partner. Since Java is a ultithreaded
OutputStream objects to read and write to and from the partner. Since Java is a multithreaded
 
 
language, once we wake up from accept() we can pass off the received
 
language, once we wake up from accept() we can pass off the received
partner socket to a new thread and repeat the accept() call for other parallel
+
partner socket to a new thread and repeat the accept() call for other parallel connections. Remember to close() any partner socket connections you receive when you are done with them.
connections. Remember to close() any partner socket connections you receive when
+
 
you are done with them.
+
So far, we have been talking about TCP oriented connections where once a connection is opened it stays open until closed during which time either end can send or receive independently from the other. Now we look at datagrams that use the UDP protocol.
So far, we have been talking about TCP oriented connections where once a connection
+
 
is opened it stays open until closed during which time either end can send or receive
+
The core class behind this is called DatagramSocket. Unlike TCP, the DatagramSocket class is used both for clients and servers.
independently from the other. Now we look at datagrams that use the UDP protocol.
+
First, let us look at a client. If we wish to write a Java UDP client, we will create an instance of a DatagramSocket using:
The core class behind this is called DatagramSocket. Unlike TCP, the DatagramSocket
+
 
class is used both for clients and servers.
 
First, let us look at a client. If we wish to write a Java UDP client, we will create an
 
instance of a DatagramSocket using:
 
 
DatagramSocket clientSocket = new DatagramSocket();
 
DatagramSocket clientSocket = new DatagramSocket();
Next we will "connect" to the remote UDP partner. We will need to know the IP address
+
 
and port that the partner is listening upon. Although the API is called "connect", we
+
Next we will "connect" to the remote UDP partner. We will need to know the IP address and port that the partner is listening upon. Although the API is called "connect", we need to realize that no connection is formed. Datagrams are connectionless so what we are actually doing is associating our client socket with the partner socket on the other end so that when we actually wish to send data, we will know where to send it to.
need to realize that no connection is formed. Datagrams are connectionless so what
+
 
we are actually doing is associating our client socket with the partner socket on the
 
other end so that when we actually wish to send data, we will know where to send it to.
 
 
clientSocket.connect(ipAddress, port);
 
clientSocket.connect(ipAddress, port);
 +
 
Now we are ready to send a datagram using the send() method:
 
Now we are ready to send a datagram using the send() method:
 +
 
DatagramPacket data = new DatagramPacket(new byte[100], 100);
 
DatagramPacket data = new DatagramPacket(new byte[100], 100);
 
clientSocket.send(data);
 
clientSocket.send(data);
 +
 
To write a UDP listener that listens for incoming datagrams, we can use the following:
 
To write a UDP listener that listens for incoming datagrams, we can use the following:
 +
 
DatagramSocket serverSocket = new DatagramSocket(port);
 
DatagramSocket serverSocket = new DatagramSocket(port);
The port here is the port number on the same machine as the JVM that will be used to
+
 
listen for incoming UDP connections.
+
The port here is the port number on the same machine as the JVM that will be used to listen for incoming UDP connections.
 
To wait for an incoming datagram, call receive().
 
To wait for an incoming datagram, call receive().
 +
 
DatagramPacket data = new DatagramPacket(new byte[100], 100);
 
DatagramPacket data = new DatagramPacket(new byte[100], 100);
 
clientSocket.receive(data);
 
clientSocket.receive(data);
If you are going to use the Java Socket APIs, read the JavaDoc thoroughly for these
+
 
classes are there are many features and options that were not listed here.
+
If you are going to use the Java Socket APIs, read the JavaDoc thoroughly for these classes are there are many features and options that were not listed here.
 +
 
 
See also:
 
See also:
Java tutorial: All About Sockets
+
* Java tutorial: All About Sockets
JDK 8 JavaDoc
+
* JDK 8 JavaDoc
 +
 
 +
[[Категория:ESP32]]

Текущая версия на 15:24, 8 июля 2017

TCP / IP - это сетевой протокол, который используется в Интернете. Это протокол, который ESP32 изначально понимает и использует с Wi-Fi в качестве транспорта. Книги по книгам уже были написаны о TCP / IP, и наша цель - не пытаться воспроизвести подробное обсуждение того, как это работает, однако есть некоторые концепции, которые мы будем пытаться и захватывать.

Во-первых, есть IP-адрес. Это 32-битное значение и должно быть уникальным для каждого устройства, подключенного к Интернету. 32-битное значение можно рассматривать как четыре различных 8-битных значения (4 x 8 = 32). Поскольку мы можем представить 8-битное число как десятичное значение от 0 до 255, мы обычно представляем IP-адреса с обозначением <число>. <Число>. <Число>. <Число> например, 173.194.64.102. Эти IP-адреса обычно не используются в приложениях. Вместо этого набирается текстовое имя такое как «google.com» ... но не вводите их в заблуждение, эти имена являются иллюзией на уровне TCP/IP. Все работы выполняются с 32-битными IP-адресами. Существует система отображения, которая берет имя (например, «google.com») и получает соответствующий IP-адрес. Технология, которая делает это, называется «системой доменных имен» или DNS.

Когда мы думаем о TCP/IP, на самом деле есть три разных протокола. Первый - IP (Интернет-протокол). Это базовый протокол передачи дейтаграмм транспортного уровня. Над уровнем IP находится TCP (протокол управления передачей), который обеспечивает иллюзию соединения по IP-протоколу без установления соединения. Наконец, есть UDP (User Datagram Protocol). Это также живет выше IP-протокола и обеспечивает передачу дейтаграмм (без установления соединения) между приложениями. Когда мы говорим о TCP/IP, мы говорим не только о TCP, работающем поверх IP, но и фактически используем это как сокращение для основных протоколов, которые являются IP, TCP и UDP, а также дополнительные связанные протоколы уровня приложений, такие как DNS, HTTP, FTP , Telnet и многое другое.

The Lightweight IP Stack – lwip

Если мы будем рассматривать TCP/IP в качестве протокола, мы можем разбить наше понимание сетей на два разных уровня. Один из них - это аппаратный уровень, который отвечает за получение потока 1 и 0 из одного места в другое. Общие реализации для этого включают Ethernet, Token Ring и (да ... Я сейчас встречаюсь с модемами). Они характеризуются физическими проводами от ваших устройств. Wi-Fi сам по себе является транспортным уровнем. Он имеет дело с использованием радиоволн в качестве среды связи 1 и 0 между двумя точками. Спецификация WiFI - IEEE 802.11.

Как только мы сможем передавать и принимать данные, следующий уровень организует данные по этой физической сети, и именно здесь вступает в действие TCP/IP. Он обеспечивает правила и управление передачей данных, адресацией, маршрутизацией, протокольными переговорами и т.д.

Как правило, TCP/IP реализуется в программном обеспечении по основному механизму физического транспорта. Подумайте об этом мгновение. Представьте, что я сказал вам, что у меня есть «волшебная коробка», и если вы поместите что-то в эту коробку, она будет волшебным образом перенесена в другую коробку. Это аналогия физического транспорта. Программное обеспечение, которое является TCP / IP, добавляет механизмы выше этого. Например, представьте, что коробка имеет ширину всего 6 дюймов. Если вы хотите отправить мне что-то через наши ящики, вы должны расколоть его и отправить его на куски. Ваш конец истории коробки обрабатывает это. Моя коробка получит детали и соберите их для меня. Части могут прибыть в порядок, а некоторые части могут даже потеряться на маршруте и должны быть отправлены повторно с оригиналов. Аппаратное обеспечение (коробки) не имеет понятия как добиться этого. Все, что они знают, это часть данных в одном конце, мы надеемся, прибудем к другому ... но не гарантируем.

TCP/IP - большой протокол. Он содержит множество частей. К счастью, это хорошо указано и было реализовано многими поставщиками за последние 45 лет. Некоторые из реализаций всего пакета частей TCP/IP были написаны как с открытым исходным кодом и распространяются и поддерживаются сообществом. Это означает, что если у вас есть новый аппаратный уровень, можно (в принципе) поднять уже написанную реализацию TCP/IP, сопоставить его с вашим оборудованием, скомпилировать его для своей среды, и все будет работать. На самом деле это гораздо легче сказать, чем сделать ... и, к счастью для нас, наши друзья в Espressif сделали для нас работу.

Одна такая реализация с открытым исходным кодом стека TCP/IP называется «The LightweightIPStack ", который обычно называют" lwIP ", который можно подробно прочитать на домашней странице (см. Ссылки). В рамках распространения ESP-IDF у нас есть библиотеки, которые предоставляют реализацию lwIP. Это lwIP, который предоставляет ESP32 следующие услуги:

  • IP
  • ICMP
  • IGMP
  • MLD
  • ND
  • UDP
  • TCP
  • sockets API
  • DNS

Опять же, хорошая новость заключается в том, что подавляющее большинство lwIP не имеет для нас значения, дизайнеров и разработчиков приложений ESP32. Это жизненно важно ... но важно для внутренней работы ESP32 и не подвергается нам как потребителям.

See also:

  • lwIP 2.0.0

TCP

TCP-соединение - это двунаправленная труба, через которую данные могут поступать в обоих направлениях. До того, как соединение установлено, одна сторона действует как сервер. Это пассивно прослушивание входящих запросов на соединение. Он будет просто сидеть там столько, сколько потребуется, пока не поступит запрос на соединение. Другая сторона соединения отвечает за инициирование соединения и активно просит создать соединение. Как только соединение будет построено, обе стороны могут отправлять и получать данные. Чтобы «клиент» запросил соединение, он должен знать адресную информацию, по которой сервер слушает. Этот адрес состоит из двух отдельных частей. Первая часть - это IP-адрес сервера, а вторая часть - «номер порта» для конкретного слушателя. Если мы подумаем о ПК, у вас может быть много приложений, работающих на нем, каждый из которых может получать входящее соединение. Просто знание IP-адреса вашего ПК недостаточно для обращения к правильному приложению. Комбинация IP-адреса плюс номер порта обеспечивает всю необходимую адресацию.

Как аналог этого, подумайте о своем мобильном телефоне. Он пассивно сидит там, пока кто-то не назовет его. В нашей истории ваш телефон - слушатель. Адрес, который кто-то использует для формирования соединения, - это номер вашего телефона, который состоит из кода области плюс остаток. Например, номер телефона (817) 555-1234 достигнет определенного телефона. Однако код города 817 предназначен для Форт-Уэрта в Техасе ... призывая, что сам по себе недостаточно для того, чтобы связаться с человеком ... требуется полный номер телефона.

Нет, мы не будем рассматривать, как ESP32 может настроить себя как слушателя для входящего соединения TCP / IP, и это требует, чтобы мы начали понимать важные «сокеты» API.

TCP/IP Sockets

API сокетов - это программный интерфейс для работы с сетями TCP / IP. Это, вероятно, самый известный API для сетевого программирования. Программирование сокетов знакомо программистам в Linux, Windows, Java и т.д.

Сетевые потоки TCP/IP поставляются в двух вариантах ... соединение, ориентированное на TCP и дейтаграмму, ориентированную на UDP. API сокетов предоставляет различные шаблоны вызовов для обоих стилей.

Для TCP сервер построен:

  1. . Создание сокета TCP
  2. . Связывание локального порта с сокетом
  3. . Установка режима сокета в режим прослушивания
  4. . Принятие нового соединения с клиентом
  5. . Получение и отправка данных
  6. . Закройте соединение клиент / сервер
  7. . Возвращаясь к шагу 4

Для клиента TCP мы создаем:

  1. . Создание сокета TCP
  2. . Подключение к серверу TCP
  3. . Отправка данных / прием данных
  4. . Закройте соединение

Теперь давайте разложим их на фрагменты кода, которые мы можем проанализировать более подробно. Определения заголовков для API сокетов можно найти в <lwip / sockets.h>.

Для клиентских и серверных приложений задача создания сокета одинакова.

Это вызов API функции socket ().

int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

Возврат из socket () - это целочисленный дескриптор, который используется для обозначения сокета. Сокеты имеют много состояний, связанных с ними, однако это состояние является внутренним для реализации TCP/IP и сокетов, и его не нужно подвергать сетевому программисту. Таким образом, нет необходимости раскрывать эти данные программисту. Мы можем думать о вызове socket (), запрашивая время выполнения для создания и инициализации всех данных необходимых для сетевого общения. Эти данные принадлежат времени выполнения, и нам передается «ссылочный номер» или дескриптор, который действует как прокси-сервер для данных. Когда мы захотим впоследствии выполнить работу над этим сетевым подключением, мы перейдем к тому дескриптору, который был ранее выпущен нам, и мы можем связать его с соединением. Это изолирует и изолирует программиста от кишок реализации TCP/IP и оставляет нам полезную абстракцию.

Когда мы создаем серверный сокет, мы хотим, чтобы он прослушивал входящие запросы на соединение. Для этого нам нужно указать сокет, номер порта TCP/IP которого он должен прослушивать. Обратите внимание, что мы не предоставляем номер порта a непосредственно из значения int / short. Вместо этого мы возвращаем значение, возвращаемое функцией htons(). Эта функция выполняет преобразование числа в так называемый «порядок сетевого байта». Это порядок байтов, который был выбран по соглашению, который используется для передачи двоичных двоичных данных без знака через Интернет. Фактический формат - это «большой конец», что означает, что если мы возьмем число, такое как 9876 (десятичное), то оно представлено в двоичном виде как 00100110 10010100 или 0x26D4 в шестнадцатеричном формате. Для порядка сетевых байтов мы сначала передаем 00100110 (0x26), а затем 10010100 (0xD4). Важно понимать, что ESP32 представляет собой небольшую внутреннюю архитектуру, которая означает, что мы должны обязательно преобразовать 2 байта и 4 байта в сетевой порядок байтов (большой конец).

Приложение на ESP32 в один момент может использовать только один порт. Если мы хотим связать номер порта с приложением, например, наше серверное приложение, в этом случае мы выполняем задачу с именем «привязка», которая связывает (или присваивает) номер порта сокету, который, в свою очередь, принадлежит приложению.

struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(portNumber);
bind(sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

Теперь, когда сокет связан с номером локального порта, мы можем запросить, чтобы среда выполнения начала прослушивать входящие соединения. Мы делаем это, вызывая API listen (). Перед вызовом listen() соединения с клиентами были бы отклонены с указанием клиенту, что на соответствующем целевом адресе ничего не было. Как только мы вызываем listen (), сервер начнет принимать входящие клиентские соединения. API выглядит так:

listen(sock, backlog)

Залогом является количество запросов на соединение, которые будут выполняться во время выполнения и принимаются до их передачи в приложение для обработки. Способ думать об этом - это представить, что вы являетесь приложением, и вы можете делать только одно за раз. Например, вы можете разговаривать только с одним человеком за раз по телефону. Теперь представьте, что у вас есть секретарь, который обрабатывает ваши входящие звонки. Когда звонок прибывает, и вы не заняты, секретарь передает вам звонок. Теперь представьте, что вы заняты. В это время секретарь отвечает на звонок и просит абонента подождать. Когда вы освобождаетесь, она передает вам ожидающий звонок. Теперь предположим, что вы все еще заняты, когда звонит еще один клиент. Она также призывает этого звонящего подождать. Мы начинаем строить очередь абонентов. И именно здесь начинается игра с отставанием. Заготовка указывает время выполнения, сколько звонков может быть получено, и попросили подождать. Если поступит больше вызовов, чем позволяет наш backlog, время выполнения немедленно отклонит вызов. Это не только предотвращает потребление ресурсов при запуске на сервера, а также может использоваться в качестве индикатора для вызывающего абонента, что его лучше обслуживать в другом месте.

Теперь с точки зрения сервера мы готовы сделать некоторые работы. Серверное приложение теперь может блокировать ожидания входящих клиентских подключений. Мысль о том, что цель сервера в жизни - обрабатывать клиентские запросы, а когда нет активного запроса клиента, нет ничего, что можно было бы сделать, кроме как ждать запроса. Хотя это, безусловно, одна модель, это не обязательно единственная модель или даже лучшая модель (во всех случаях). Обычно нам нравятся наши процессоры для «использования». Используемый означает, что, хотя он имеет продуктивную работу, он может сделать, тогда он должен это сделать. Если единственное, что может сделать наша программа, это вызов клиентских услуг, то исходная модель имеет смысл. Тем не менее, есть определенные программы, которые, если у них нет клиентского запроса на немедленную службу, могут потратить время на то, что полезно. Мы вернемся к этому понятию позже. На данный момент мы рассмотрим вызов функции accept(). Когда вызывается accept(), произойдет одна из двух вещей. Если соединение с клиентом не будет немедленно ждать нас, мы будем блокировать до такого времени в будущем, когда произойдет соединение с клиентом. В это время мы проснемся и получим связь с недавно прибывшим клиентом. Если, с другой стороны, мы вызываем accept(), и нас ждет клиентское соединение, мы немедленно получим это соединение, и мы продолжим. В обоих случаях мы вызываем accept() и возвращаем соединение с клиентом. Различие между случаями заключается в том, нужно ли нам ждать прибытия соединения. Вызов API выглядит так:

struct sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);
int clientSock = accept(sock, (struct sockaddr *)&clientAddress, &clientAddressLength);

Возврат из accept() - это новый сокет (целочисленный дескриптор), который представляет соединение между запрашивающим клиентом и сервером. Крайне важно понять, что это отличается от ранее созданного сокета сервера, который мы привязали к нашему серверному порту прослушивания. Этот сокет все еще жив и здоров и существует, чтобы продолжать обслуживать дальнейшие клиентские соединения. Новый возвращенный сокет - это соединение для разговора, инициированного этим единственным клиентом. Как и все TCP-соединения, разговор является симметричным и двунаправленным. Это означает, что теперь уже нет понятия клиента и сервера ... обе стороны могут отправлять и получать по своему усмотрению в любое время.

Если мы хотим создать сокет-клиент, история похожа. Снова мы создаем socket(), но на этот раз нет необходимости в истории bind() / listen() / accept(). Вместо этого мы используем API connect() для подключения к целевой конечной точке TCP/IP.

Например:

struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.1.200", &serverAddress.sin_addr.s_addr);
serverAddress.sin_port = htons(9999);
int rc = connect(sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in));

See also:

  • Native byte order, endian and network byte order
  • socket
  • bind
  • listen
  • accept
  • send
  • recv
  • connect
  • Wikipedia – Berkeley Sockets
  • Beej's Guide to Network Programming

Обработка ошибок

Большинство API-интерфейсов сокетов возвращают код возврата int. Если этот код <0, то произошла ошибка.

Характер ошибки можно найти, используя глобальный int, называемый «errno». Однако в среде многозадачности не рекомендуется работать с глобальными переменными. В области сокетов мы можем запросить сокет для последней ошибки, с которой он столкнулся, используя следующий фрагмент кода:

int espx_last_socket_errno(int socket) {
   int ret = 0;
   u32_t optlen = sizeof(ret);
   getsockopt(socket, SOL_SOCKET, SO_ERROR, &ret, &optlen);
   return ret;
}

Значения ошибок можно сравнить с константами. Вот таблица констант, используемых в текущей реализации FreeRTOS:

Символ Значение Описание
EPERM 1 Operation not permitted
ENOENT 2 No such file or directory
ESRCH 3 No such process
EINTR 4 Interrupted system call
EIO 5 I/O error
ENXIO 6 No such device or address
E2BIG 7 Arg list too long
ENOEXEC 8 Exec format error
EBADF 9 Bad file number
ECHILD 10 No child processes
EAGAIN 11 Try again
ENOMEM 12 Out of memory
EACCES 13 Permission denied
EFAULT 14 Bad address
ENOTBLK 15 Block device required
EBUSY 16 Device or resource busy
EEXIST 17 File exists
EXDEV 18 Cross-device link
ENODEV 19 No such device
ENOTDIR 20 Not a directory
EISDIR 21 Is a directory
EINVAL 22 Invalid argument
ENFILE 23 File table overflow
EMFILE 24 Too many open files
ENOTTY 25 Not a typewriter
ETXTBSY 26 Text file busy
EFBIG 27 File too large
ENOSPC 28 No space left on device
ESPIPE 29 Illegal seek
EROFS 30 Read-only file system
EMLINK 31 Too many links
EPIPE 32 Broken pipe
EDOM 33 Math argument out of domain of func
ERANGE 34 Math result not representable
EDEADLK 35 Resource deadlock would occur
ENAMETOOLONG 36 File name too long
ENOLCK 37 No record locks available
ENOSYS 38 Function not implemented
ENOTEMPTY 39 Directory not empty
ELOOP 40 Too many symbolic links encountered
EWOULDBLOCK EAGAIN 41 Operation would block
ENOMSG 42 No message of desired type
EIDRM 43 Identifier removed
ECHRNG 44 Channel number out of range
EL2NSYNC 45 Level 2 not synchronized
EL3HLT 46 Level 3 halted
EL3RST 47 Level 3 reset
ELNRNG 48 Link number out of range
EUNATCH 49 Protocol driver not attached
ENOCSI 50 No CSI structure available
EL2HLT 51 Level 2 halted
EBADE 52 Invalid exchange
EBADR 53 Invalid request descriptor
EXFULL 54 Exchange full
ENOANO 55 No anode
EBADRQC 56 Invalid request code
EBADSLT 57 Invalid slot
EBFONT 59 Bad font file format
ENOSTR 60 Device not a stream
ENODATA 61 No data available
ETIME 62 Timer expired
ENOSR 63 Out of streams resources
ENONET 64 Machine is not on the network
ENOPKG 65 Package not installed
EREMOTE 66 Object is remote
ENOLINK 67 Link has been severed
EADV 68 Advertise error
ESRMNT 69 Srmount error
ECOMM 70 Communication error on send
EPROTO 71 Protocol error
EMULTIHOP 72 Multihop attempted
EDOTDOT 73 RFS specific error
EBADMSG 74 Not a data message
EOVERFLOW 75 Value too large for defined data type
ENOTUNIQ 76 Name not unique on network
EBADFD 77 File descriptor in bad state
EREMCHG 78 Remote address changed
ELIBACC 79 Can not access a needed shared library
ELIBBAD 80 Accessing a corrupted shared library
ELIBSCN 81 .lib section in a.out corrupted
ELIBMAX 82 Attempting to link in too many shared libraries
ELIBEXEC 83 Cannot exec a shared library directly
EILSEQ 84 Illegal byte sequence
ERESTART 85 Interrupted system call should be restarted
ESTRPIPE 86 Streams pipe error
EUSERS 87 Too many users
ENOTSOCK 88 Socket operation on non-socket
EDESTADDRREQ 89 Destination address required
EMSGSIZE 90 Message too long
EPROTOTYPE 91 Protocol wrong type for socket
ENOPROTOOPT 92 Protocol not available
EPROTONOSUPPORT 93 Protocol not supported
ESOCKTNOSUPPORT 94 Socket type not supported
EOPNOTSUPP 95 Operation not supported on transport endpoint
EPFNOSUPPORT 96 Protocol family not supported
EAFNOSUPPORT 97 Address family not supported by protocol
EADDRINUSE 98 Address already in use
EADDRNOTAVAIL 99 Cannot assign requested address
ENETDOWN 100 Network is down
ENETUNREACH 101 Network is unreachable
ENETRESET 102 Network dropped connection because of reset
ECONNABORTED 103 Software caused connection abort
ECONNRESET 104 Connection reset by peer
ENOBUFS 105 No buffer space available
EISCONN 106 Transport endpoint is already connected
ENOTCONN 107 Transport endpoint is not connected
ESHUTDOWN 108 Cannot send after transport endpoint shutdown
ETOOMANYREFS 109 Too many references: cannot splice
ETIMEDOUT 110 Connection timed out
ECONNREFUSED 111 Connection refused
EHOSTDOWN 112 Host is down
EHOSTUNREACH 113 No route to host
EALREADY 114 Operation already in progress
EINPROGRESS 115 Operation now in progress
ESTALE 116 Stale NFS file handle
EUCLEAN 117 Structure needs cleaning
ENOTNAM 118 Not a XENIX named type file
ENAVAIL 119 No XENIX semaphores available
EISNAM 120 Is a named type file
EREMOTEIO 121 Remote I/O error
EDQUOT 122 Quota exceeded
ENOMEDIUM 123 No medium found
EMEDIUMTYPE 124 Wrong medium type

Configuration settings

Внутри «menuconfig» есть некоторые параметры, относящиеся к TCP / IP, и их можно найти в настройках lwIP. Настройки:

  • Максимальное количество открытых сокетов - целое число - CONFIG_LWIP_MAX_SOCKETS - Это количество одновременно открытых сокетов. Значение по умолчанию - 4, а максимальное значение - 16.
  • Включить SO_REUSEADDR - boolean - LWIP_SO_REUSE -

Using select()

Представьте, что у нас есть несколько сокетов, каждый из которых может быть источником входящих данных. Если мы попытаемся и читаем () данные из сокета, мы обычно блокируем, пока данные не будут готовы. Если мы это сделаем, то, если данные станут доступными в другом сокете, мы не узнаем. Альтернативой является попытка считывать данные неблокируемым способом. Это тоже было бы полезно, но потребовало бы, чтобы мы каждый раз тестировали каждый сокет в режиме занятости или опроса. Это оо не является оптимальным. В идеале, что мы хотели бы сделать, это блокировать, одновременно наблюдая за несколькими сокетами и просыпаясь, когда у первого есть что-то полезное для нас.

See also:

  • select
  • The world of select()

Отличия от «стандартных» сокетов

Два файла заголовка, которые обычно встречаются в других реализациях сокетов, не являются частью определения ESP-IDF. Они есть:

  • Netinet / in.h
  • Arpa / inet.h

Несмотря на отсутствие присутствия, никаких очевидных проблем не обнаружено, и предполагается, что содержание, обычно содержащееся внутри, было распределено по другим заголовкам.

UDP/IP Sockets

Если мы думаем о TCP как о формировании связи между двумя сторонами, подобными телефонному звонку, то UDP походит на отправку письма через почтовую систему. Если бы я отправил вам письмо, мне нужно было узнать ваше имя и адрес. Ваш адрес необходим, чтобы письмо могло быть доставлено в правильный дом, пока ваше имя гарантирует, что оно окажется в ваших руках, в отличие от кого-то, кто может жить с вами. В терминах TCP / IP адрес - это IP-адрес, а имя - номер порта.

С помощью телефонного разговора мы можем обмениваться такой же информацией, как нам нравится. Иногда я говорю, иногда вы говорите ... но нет максимального ограничения на то, сколько информации мы можем обменять в одном разговоре. Вместе с письмом есть только так много страниц бумаги, которые будут вписываться в конверты, которые у меня есть.

Понятие почтовой аналогии - это то, как мы можем думать о UDP. Аббревиатура обозначает протокол User Datagram Protocol, и это понятие дейтаграммы, которое сродни букве. Дейтаграмма представляет собой массив байтов, которые передаются от отправителя в приемник как единое целое. Максимальный размер дейтаграммы с использованием UDP - 64 Кбайт. Не нужно устанавливать соединение между двумя сторонами, прежде чем данные начнут течь. Однако есть нижняя сторона. Отправитель данных не будет уведомлен о невозможности получения данных получателем. С TCP у нас есть установление связи между двумя сторонами, что позволяет отправителю знать, что данные были получены, а если нет, может автоматически повторно передавать до тех пор, пока он не будет получен или мы не откажемся. С UDP и точно так же, как письмо, когда мы отправляем дейтаграмму, мы теряем из виду, действительно ли он прибывает благополучно в пункт назначения.

Сейчас самое время вернуться к IP-адресам и номерам портов. Мы должны начать понимать, что на ПК только одно приложение может прослушивать любой порт. Например, если мое приложение прослушивает порт 12345, тогда никакое другое приложение также не может прослушивать тот же порт ... не ваше приложение, а другое копия / экземпляр моего экземпляра.

Когда входящее соединение или датаграмма поступает на машину, оно прибыло, потому что IP-адрес отправленных данных соответствует IP-адресу устройства, на котором он был отправлен.

Затем мы маршрутизируем внутри устройства на основе номеров портов. И здесь я хочу уточнить детали. Мы прокладываем маршрут внутри машины на основе пары как протокола, так и номера порта.

Так, например, если запрос поступает на машину для порта 12345 по TCP-соединению, он маршрутизируется в порт наблюдения 123 приложения TCP. Если запрос поступает на тот же компьютер для порта 12345 через UDP, он направляется в UDP Порт наблюдения приложений 12345. Это означает, что мы можем иметь два приложения, прослушивающих один и тот же порт, но по разным протоколам. Если сделать это более формально, пространство распределения для номеров портов является функцией протокола, и двум приложениям не разрешается одновременно резервировать один и тот же порт в одном и том же пространстве распределения протокола.

Хотя я использовал историю ПК с несколькими приложениями, в нашем ESP32 история похожа, хотя мы просто запускаем одно приложение на устройстве. Если вашему одиночному приложению необходимо прослушивать несколько портов, не пытайтесь использовать один и тот же порт с тем же протоколом, что и второй вызов функции, первый из которых уже назначил порт. Это деталь, которую я рад за вас забыть, потому что вы редко сталкиваетесь с ней, но я хотел поймать ее здесь для полноты.

Чтобы запрограммировать UDP, мы снова используем сокеты. Чтобы снова настроить сервер сокетов с помощью UDP, мы вызываем socket () для создания сокета, и снова мы вызываем bind (), чтобы указать номер порта, который мы хотим прослушать. Нет необходимости в вызове listen (). Когда сервер готов принять входящий запрос, мы вызываем recvfrom (), который блокируется до получения дейтаграммы. Как только человек прибывает, мы просыпаемся и можем обрабатывать запрос.

Запрос содержит обратный адрес, и мы можем отправить ответ, используя sendto (), если мы пожелаем.

На стороне клиента мы создаем сокет (), а затем можем вызывать sendto (). Вызов sendto () принимает IP-адрес и порт цели как параметры, так и данные полезной нагрузки.

Например:

int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
sendto(socket_fd, data, size, 0, destAddr, destAddrLen);
  • socket
  • sendto
  • recvfrom

TLS, SSL and security

До сих пор мы думали о создании сокетов, которые формируют сетевое соединение, а затем отправляют и получают данные по этому соединению. Однако у нас есть проблема с безопасностью. Данные, которые протекают по проводу, не зашифрованы. Это означает, что если кто-то должен «обнюхать» или иным образом исследовать сетевые данные, мы увидим содержимое передаваемых данных. Например, если я отправляю пароль, используемый для аутентификации, если мы будем изучать содержимое данных, мы сможем определить пароль, который я использую.

На самом деле не так сложно распознать отправленные и полученные данные. Отличные инструменты, такие как wirehark, используются для отладки и могут быть легко использованы для проверки содержимого сетевых пакетов или потоков. Очевидно, что мы уже обмениваемся данными кредитной карты, электронной почтой и другой конфиденциальной информацией через Интернет, как это делается?

Ответ - это концепция под названием «Secure Socket Layer» или SSL. SSL обеспечивает возможность шифрования данных перед передачей, так что только предполагаемый получатель может ее расшифровать. И наоборот, любые ответы, отправленные получателем, также зашифровываются таким образом, что только мы можем расшифровать данные. Если кто-то должен был захватывать или иным образом проверять данные, отправляемые по проводам, им не удастся вернуться к исходному содержанию данных.

То, как это работает, - это концепция секретных ключей. Представьте себе, что я думаю о очень большом случайном числе (и большим я имею в виду ОЧЕНЬ большой). Мы называем этот частный номер своим личным ключом. Теперь представьте, что связанный с закрытым ключом соответствующий номер (открытый ключ), который можно использовать для дешифрования сообщения, которое было закодировано с помощью закрытого ключа. Теперь представьте себе, что я хочу, чтобы наравне с партнером. Я отправляю запрос (незашифрованный) партнеру и запрос его открытого ключа. Он отправляет это обратно, и я посылаю ему копию своего открытого ключа зашифрованного его открытым ключом. Поскольку для дешифрования данных можно использовать только подходящую пару открытых / закрытых ключей, только желаемый получатель может расшифровать сообщение, после чего у него будет копия моего открытого ключа.

Теперь в будущем я могу отправить ему сообщения, зашифрованные моим личным ключом и зашифрованные его открытым ключом, и он сможет их расшифровать с помощью своей копии своего закрытого ключа и моего открытого ключа, пока он может отправлять мне зашифрованные сообщения, закодированные его Закрытый ключ, который я могу расшифровать с помощью моей копии открытого ключа. Поменяв открытые ключи, мы теперь можем продолжать обмен данными, не опасаясь, что это увидит кто-то еще.

Все это шифрование данных происходит вне и выше знаний о сетях TCP/IP. TCP/IP обеспечивает доставку данных, но ничего не заботится о его содержании.

Таким образом, и на высоком уровне, если мы хотим обмениваться защищенными данными, мы должны выполнить шифрование и дешифрование с использованием алгоритмов и библиотек, которые живут за пределами API сокетов, и использовать сокеты в качестве транспорта для передачи и получения зашифрованных данных, которые Поданных и полученных от алгоритмов шифрования.

При использовании mbed TLS нам нужен большой размер стека. Я еще не знаю, как мало мы можем уйти, но я использовал 8000 байт.

See also:

  • mbed TLS
  • mbed TLS home page
  • mbed TLS tutoria l
  • mbed TLS API reference

mbedTLS app structure

Давайте начнем разбирать структуру приложения TLS, которое использует API-интерфейсы mbedTLS.

Во-первых, существует понятие сетевого контекста, который инициализируется вызовом mbedtls_net_init ().

mbedtls_net_context server_fd;
mbedtls_net_init(&server_fd);

Здесь больше нечего объяснять. Инициализированные данные являются «непрозрачными» для нас, и вызов этой функции является частью правил.

Далее идет инициализация контекста SSL с вызовом:

mbedtls_ssl_init().
mbedtls_ssl_context ssl;
mbedtls_ssl_init(&ssl);

Опять же, здесь больше нечего объяснять. Данные снова непрозрачны, и эта функция просто инициализирует его для нас. Вызов этой функции также является частью правил.

Теперь мы вызываем mebtls_ssl_config_init ().

mbedtls_ssl_config config;
mbedtls_ssl_config_init(&config);

Эти инициализации повторяются для других типов данных, включая:

mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_context entropy;
mbedtls_entropy_init(&entropy);
mbedtls_x509_crt cacert;
mbedtls_x509_crt_init(&cacert);

SSL использует хорошие генераторы случайных чисел. Что такое «хорошее» случайное число?

Поскольку компьютеры являются детерминированными устройствами, генерация случайного числа выполняется посредством выполнения алгоритма, и поскольку алгоритмы детерминированы, то последовательность чисел, порожденных этими функциями, может в принципе быть предсказуемой. Хорошим генератором случайных чисел является тот, где последовательность произведённых чисел вовсе не легко предсказуема и генерирует значения без смещения к их значениям с равной вероятностью любого числа в пределах выбранного диапазона.

Мы инициализируем генератор случайных чисел с вызовом bedtls_ctr_drbg_seed (). Примечание. Мы видим фразу «ctr_drbg» ..., которая является аббревиатурой «Генератор случайных байтов детерминированного режима». Это отраслевая стандартная спецификация / алгоритм для генерации случайных чисел.

[1]

С установкой под нашими ремнями пришло время начать рассмотрение связи на основе SSL. Поскольку мы рассматриваем SSL через сокеты, если мы не использовали сокеты TLS, мы бы выполнили вызов socket () для создания сокета, а затем connect () для подключения к нашему партнеру. В мире mbedtls мы называем mbedtls_net_connect (). Это имеет вид:

mbedtls_net_connect(&server_fd, <hostname>, <port>, MBEDTLS_NET_PROTO_TCP);

Имя хоста и порт определяют, к чему мы подключаемся. Обратите внимание на первый параметр.

Это структура mbedtls_net_context, которую мы инициализировали с помощью вызова mbedtls_net_init () ранее. Мы всегда должны проверять код возврата, чтобы убедиться, что соединение было успешным. Теперь мы настроим настройки SSL по умолчанию с вызовом mbedtls_ssl_config_defaults (). Например:

mbedtls_ssl_config_defaults(
   &conf, MBEDTLS_SSL_IS_CLIENT,
   MBEDTLS_SSL_TRANSPORT_STREAM,
   MBED_SSL_PRESET_DEFAULT)

Когда мы общаемся через SSL, мы обычно хотим подтвердить, что учетные данные, предоставленные нашим партнером, указывают на то, что они являются теми, кого они утверждают. Этот процесс называется аутентификацией. Мы можем определить, какую аутентификацию мы хотим выполнить, вызывая mbedtls_ssl_conf_authmode ().

mbedtls_ssl_conf_authmode(&ssl, MBEDTLS_SSL_VERIFY_NONE)

Ранее мы говорили, что SSL сильно зависит от хорошего генератора случайных чисел.

Теперь мы расскажем об окружающей среде, какой генератор случайных чисел мы хотим использовать:

mbedtls_ssl_conf_rng(&ssl, mbedtls_ctr_drbg_random, &ctr_drbg)

Затем мы создадим еще несколько настроек контекста SSL, вызывая mbedtls_ssl_set_hostname().

mbedtls_ssl_set_hostname(&ssl, "name")

Теперь мы инструктируем среду SSL, которая функционирует для отправки и получения данных, вызывая mbedtls_ssl_set_bio ().

mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL)

На этом этапе мы создали связь с нашим партнером и настроили среду SSL. Остается на самом деле читать и писать данные. Для записи данных мы называем mbed_ssl_write ().

mbedtls_ssl_write(&ssl, buf, len)

and to read data we call mbedtls_ssl_read().

mbedtls_ssl_read(&ssl, buf, len)

See also:

  • mbedtls_net_init
  • mbedtls_ssl_init
  • mbedtls_ssl_config_init
  • mbedtls_net_connect
  • mbedtls_ssl_config_defaults
  • mbedtls_ssl_conf_authmode
  • mbedtls_ssl_conf_rng
  • mbedtls_ssl_set_hostname
  • mbedtls_ssl_set_bio
  • mbedtls_ssl_write
  • mbedtls_ssl_read

mbedTLS Example

Вот примерная функция, которая была протестирована на ESP32, чтобы вызвать HTTPS-вызов на сервер HTTPS для получения некоторых результатов.

#include "mbedtls/platform.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/debug.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
#include "mbedtls/net.h"
#include "mbedtls/ssl.h"
#include "esp_log.h"
#include "string.h"
#include "stdio.h"
#define SERVER_NAME "httpbin.org"
#define SERVER_PORT "443"
static char tag[] = "callhttps";
static char errortext[256];

static void my_debug(void *ctx, int level, const char *file, int line, const char *str) {
   ((void) level);
   ((void) ctx);
   printf("%s:%04d: %s", file, line, str);
}
void callhttps() {
   ESP_LOGD(tag, "--> callhttps\n");
   mbedtls_net_context server_fd;
   mbedtls_entropy_context entropy;
   mbedtls_ctr_drbg_context ctr_drbg;
   mbedtls_ssl_context ssl;
   mbedtls_ssl_config conf;
   mbedtls_x509_crt cacert;
   int ret;
   int len;
   char *pers = "ssl_client1";
   unsigned char buf[1024];
   mbedtls_net_init(&server_fd);
   mbedtls_ssl_init(&ssl);
   mbedtls_ssl_config_init(&conf);
   mbedtls_x509_crt_init(&cacert);
   mbedtls_ctr_drbg_init(&ctr_drbg);
   mbedtls_entropy_init(&entropy);
   mbedtls_ssl_conf_dbg(&conf, my_debug, stdout);
   mbedtls_debug_set_threshold(2); // Log at error only
   ret = mbedtls_ctr_drbg_seed(
   &ctr_drbg,
   mbedtls_entropy_func, &entropy, (const unsigned char *) pers,             strlen(pers));
   if (ret != 0) {
      ESP_LOGE(tag, " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret);
      return;
   }
   ret = mbedtls_net_connect(&server_fd, SERVER_NAME, SERVER_PORT, MBEDTLS_NET_PROTO_TCP);
   if (ret != 0) {
      ESP_LOGE(tag, " failed\n ! mbedtls_net_connect returned %d\n\n", ret);
      return;
   }
   ret = mbedtls_ssl_config_defaults(
      &conf,
      MBEDTLS_SSL_IS_CLIENT,
      MBEDTLS_SSL_TRANSPORT_STREAM,
      MBEDTLS_SSL_PRESET_DEFAULT);
   if (ret != 0) {
      ESP_LOGE(tag, " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret);
   return;
   }
   mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
   mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
   ret = mbedtls_ssl_setup(&ssl, &conf);
   if (ret != 0) {
      mbedtls_strerror(ret, errortext, sizeof(errortext));
      ESP_LOGE(tag, "error from mbedtls_ssl_setup: %d - %x - %s\n", ret, ret, errortext);
      return;
   }
   ret = mbedtls_ssl_set_hostname(&ssl, "httpbin.org");
   if (ret != 0) {
         mbedtls_strerror(ret, errortext, sizeof(errortext));
         ESP_LOGE(tag, "error from mbedtls_ssl_set_hostname: %d - %x -       %s\n", ret, ret, errortext);
      return;
   }
   mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send,      mbedtls_net_recv, NULL);
   char *requestMessage = \
      "GET /ip HTTP/1.1\r\n" \
      "User-Agent: kolban\r\n" \
      "Host: httpbin.org\r\n" \
      "Accept-Language: en-us\r\n" \
      "Accept-Encoding: gzip, deflate\r\n" \
      "\r\n";
   sprintf((char *)buf, requestMessage);
   len = strlen((char *)buf);
   ret = mbedtls_ssl_write(&ssl, buf, len);
   if (ret < 0) {
      mbedtls_strerror(ret, errortext, sizeof(errortext));
      ESP_LOGE(tag, "error from write: %d -%x - %s\n", ret, ret,       errortext);
      return;
   }
   len = sizeof(buf);
   ret = mbedtls_ssl_read(&ssl, buf, len);
   if (ret < 0) {
      ESP_LOGE(tag, "error from read: %d\n", len);
      return;
   }
   printf("Result:\n%.*s\n", len, buf);
   mbedtls_net_free(&server_fd);
   mbedtls_ssl_free(&ssl);
   mbedtls_ssl_config_free(&conf);
   mbedtls_ctr_drbg_free(&ctr_drbg);
   mbedtls_entropy_free(&entropy);
   ESP_LOGV(tag, "All done");
}

Заметки:

При отладке MBED в Curl установите значение MBEDTLS_DEBUG на 1 в curl_config.h

OpenSSL

OpenSSL - популярная реализация стека SSL. В среде ESP32 выбранный стек для SSL / TLS является mbedTLS, который не совпадает с OpenSSL. В рамках ESP-IDF был предоставлен слой отображения, который предоставляет API OpenSSL поверх реализации mbedTLS.

Name Service

В Интернете серверные машины можно найти по их службе имен доменов (DNS). Это сервис, который переводит читаемое человеком представление машины, например «google.com», в необходимое значение IP-адреса (например, 216.58.217.206).

Чтобы это преобразование произошло, ESP32 должен знать IP-адрес одного или более DNS-серверов, которые затем будут использоваться для выполнения преобразования имени в IP-адрес.

Если мы используем DHCP, тогда больше ничего не нужно делать - DHCP-сервер автоматически предоставляет адреса DNS-серверов.

Однако, если мы не используем DHCP (например если мы используем статические IP-адреса), тогда нам нужно проинструктировать ESP32 о местоположения DNS-серверов вручную. Мы можем сделать это с помощью функции dns_setserver().

Это принимает IP-адрес как входной вместе с каким из двух возможных DNS-серверов для использования. ESP32 настроен на то, чтобы знать личность до двух внешних серверов имен.

Причина для двоих заключается в том, что если попытка достичь первого не удастся, мы будем использовать второй. Мы можем получить наши текущие идентификаторы DNS-сервера, используя dns_getserver (). Google публично предоставляет два сервера имен с адресами 8.8.8.8 и 8.8.4.4. Как только мы определим серверы имен, мы можем найти адрес имени хоста используя функцию gethostbyname().

Во время разработки мы можем протестировать конкретный DNS-сервер, чтобы проверить, что он может определить по имени хоста.

Отличным инструментом Linux для выполнения этой задачи является «nslookup». В нем есть многие варианты, но для наших целей мы можем предоставить им имя хоста для поиска и использовать DNS-сервер:

$ nslookup example.com 8.8.8.8
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: example.com
Address: 93.184.216.34

See also:

  • gethostbyname
  • dns_getserver
  • dns_setserver
  • Wikipedia: Domain Name System
  • Google: Public DNS

Multicast Domain Name Systems

В локальной сети с приходом и выходом динамических устройств мы можем захотеть, чтобы одно устройство находило IP-адрес другого устройства, чтобы они могли взаимодействовать друг с другом. Проблема состоит в том, что IP-адреса могут динамически выделяться сервером DHCP, работающим на точке доступа WiFi. Это означает, что IP-адрес устройства, скорее всего, не будет статичным. Кроме того, это не очень полезная история, чтобы ссылаться на устройства по их IP-адресам. Нам нужна некоторая форма службы динамического имени для поиска устройств по имени, где их IP-адреса не настроены администратором. Именно здесь вступает в действие система доменных имен многоадресной рассылки (mDNS). На высоком уровне, когда устройство хочет найти другое устройство с заданным именем, он передает запрос всем членам сети, запрашивая ответ от устройства с таким именем. Если машина считает, что она имеет эту личность, она отвечает своей собственной трансляцией, которая включает его имя и IP-адрес. Это не только удовлетворяет первоначальному запросу, но и другие машины в сети могут видеть это взаимодействие и кэшировать ответ для себя. Это означает, что если им нужно будет разрешить один и тот же хост в будущем, у них уже есть ответ. Используя многоадресную систему доменных имен (mDNS), ESP32 может попытаться разрешить имя хоста компьютера в локальной сети на его IP-адрес. Он делает это, передавая пакет, запрашивая, чтобы машина с этим идентификатором отвечала. Демоны службы имен реализованы Bonjour и nss-mdns (Linux). Обычно хосты, расположенные с использованием этого метода, принадлежат домену, заканчивающемуся «.local». Чтобы определить, участвует ли ваш компьютер в mDNS, вы можете проверить, прослушивает ли он порт UDP 5353. Это порт, используемый для связи mDNS.

См. Также:

  • Wikipedia – Multicast DNS
  • IETF RFC 6762: Multicast DNS
  • Multicast DNS
  • New DNS Technologies in the Lan
  • Avah i – Implementation of mDNS … source project for Unix machines
  • Adafruit – Bonjour (Zeroconf) Networking for Windows and Linux
  • chrome.mdns – API description for Chrome API for mDNS
  • Android – ZeroConf Browser

mDNS API programming

ESP-IDF предоставляет набор богатых API для программирования mDNS на ESP32.

В частности, мы можем либо рекламировать себя в mDNS, либо запросить существующую информацию mDNS.

Атрибуты записи сервера mDNS выглядят следующим образом:

  • hostname – mdns_set_hostname()
  • default instance – mdns_set_instance()
  • service – mdns_service_add()
    • type – _http, _ftp etc etc
    • protocol – _tcp, _udp etc etc
    • port – port number
  • instance name for service – mdns_service_instance_set()
  • TXT data for service – mdns_service_txt_set()

See also:

  • mdns_set_hostname
  • mdns_set_instance
  • mdns_service_add
  • mdns_service_instance_set
  • mdns_service_port_set

Installing Bonjour

Launch the Bonjour installer:

Если все пошло хорошо, мы найдем новую службу Windows, называемую "Bonjour Service": There is also a Bonjour browser available here …

Avahi

Реализация многоадресной DNS в Linux называется Avahi. Avahi работает как демон systemd под названием «avahi-daemon». Мы можем определить, работает ли он с:

$ systemctl status avahi-daemon
● avahi-daemon.service - Avahi mDNS/DNS-SD Stack
Loaded: loaded (/lib/systemd/system/avahi-daemon.service; enabled)
Active: active (running) since Wed 2016-01-20 22:13:35 CST; 1 day 13h ago Main PID: 384 (avahi-daemon)
Status: "avahi-daemon 0.6.31 starting up."
CGroup: /system.slice/avahi-daemon.service
├─384 avahi-daemon: running [raspberrypi.local]
└─426 avahi-daemon: chroot helper
The avahi-daemon utilizes a configuration file found at /etc/avahi/avahi-daemon.conf.

Имя по умолчанию, которое avahi рекламирует как локальное имя хоста. Когда выполняется разрешение имени хоста, системный файл с именем /etc/nsswitch.conf используется для определения порядка разрешения. В частности, запись хостов содержит разрешение имен. Примером может служить:

hosts: files mdns4_minimal [NOTFOUND=return] dns

Что говорит «сначала посмотрите в / etc / hosts, затем обратитесь к mDNS, а затем используйте полный DNS». Это означает, что устройство, которое рекламирует себя с помощью mDNS, может быть найдено с помощью поиска «<hostname> .local». Например, если я загружаю машину Linux, которая получает динамический IP-адрес через DHCP, а имя хоста этого компьютера - «chip1», то я могу связаться с ним с адресом домена «chip1.local». Если IP-адрес устройства изменяется, последующие разрешения имени домена будут продолжать корректно разрешаться.

Инструменты Avahi не устанавливаются по умолчанию, но могут быть установлены с использованием пакета «avahi-utils»:

$ sudo apt-get install avahi-utils

Чтобы просмотреть список устройств mDNS в вашей сети, мы можем использовать команду avahi-browse. Например:

$ avahi-browse -at
+ wlan1 IPv6 chip1 [ce:79:cf:21:db:95] Workstation local
+ wlan1 IPv4 chip1 [ce:79:cf:21:db:95] Workstation local
+ wlan0 IPv6 pizero [00:36:76:21:97:a3] Workstation local
+ wlan0 IPv6 raspi3 [b8:27:eb:9d:fc:60] Workstation local
+ wlan0 IPv6 chip1 [cc:79:cf:21:db:95] Workstation local
+ wlan0 IPv4 pizero [00:36:76:21:97:a3] Workstation local
+ wlan0 IPv4 raspi3 [b8:27:eb:9d:fc:60] Workstation local
+ wlan0 IPv4 chip1 [cc:79:cf:21:db:95] Workstation local
+ wlan0 IPv6 pizero Remote Disk Management
local
+ wlan0 IPv6 raspi3 Remote Disk Management
local
+ wlan0 IPv4 raspi3 Remote Disk Management
local
+ wlan0 IPv4 pizero Remote Disk Management
local
+ wlan0 IPv4 WDMyCloud Apple File Sharing local
+ wlan0 IPv4 WDMyCloud _wd-2go._tcp local
+ wlan0 IPv4 WDMyCloud Web Site local
+ wlan0 IPv4 Living Room _googlecast._tcp local
+ wlan0 IPv4 123456789 _teamviewer._tcp local

Чтобы получить доступ к объявленному сервером mDNS из Microsoft Windows, вам понадобится служба, аналогичная установленному Bonjour от Apple. Bonjour распространяется как часть продукта iTunes от Apple. После установки мы должны иметь доступ к опубликованным серверам по адресу <name> .local. Доступен инструмент партнерских окон под названием «Bonjour Browser» Который отображает список серверов mDNS на окнах.

See also:

  • avahi home page
  • man(1) – avahi-browse
  • man(5) – avahi-daemon.conf

Working with SNTP

SNTP is the Simple Network Time Protocol and allows a device connected to the Internet to learn the current time. In order to use this, you must know of at least one time server located on the Internet. The US National Institute for Science and Technology (NIST) maintains a number of these which can be found here:

http://tf.nist.gov/tf-cgi/servers.cg i

Other time servers can be found all over the globe and I encourage you to Google search for your nearest or country specific server.

Once you know the identity of a server by its host name or IP address, you can call either of the functions called sntp_setservername() or sntp_setserver() to declare that we wish to use that time server instance. The ESP32 can be configured with up to three different time servers so that if one or two are not available, we might still get a result.

The ESP32 must also be told the local timezone in which it is running. This is set with a call to sntp_set_timezone() which takes the number of hours offset from UTC. For example, I am in Texas and my timezone offset becomes "-5". Although this function is present, I would suggest using the POSIX tzset() function instead.

With these configured, we can start the SNTP service on the ESP32 by calling sntp_init(). This will cause the device to determine its current time by sending packets over the network to the time servers and examining their responses. It is important to note that immediately after calling sntp_init(), you will not yet know what the current time may be. This is because it may take a few seconds for the ESP32 to sends the time requests and get their responses and this will all happen asynchronously to your current commands and won't complete till sometime later.

When ready, we can retrieve the current time with a call to sntp_get_current_timestamp() which will return the number of seconds since the 1st of January 1970 UTC. We can also call the function called sntp_get_real_time() which will return a string representation of the time. While these functions obviously exist, I would not recommend using them. Instead look at the POSIX alternatives which are time() and asctime().

Here is an example of using SNTP to set the time:

ip_addr_t addr; sntp_setoperatingmode(SNTP_OPMODE_POLL); inet_pton(AF_INET, "129.6.15.28", &addr); sntp_setserver(0, &addr); sntp_init();

The time can be accessed from a variety of POSIX compliant functions including:

  • asctime – Build a string representation of time.
  • clock – Return processor time.
  • ctime – Build a string representation of time.
  • difftime – Calculate a time difference.
  • gettimeofday – Retrieve the current time of day.
  • gmtime – Produce a struct tm from a time_t.
  • localtime – Produce a struct tm from a time_t.
  • settimeofday – Set the current time.
  • strftime – Format a time_t to a string.
  • time – Get the current time as a time_t (seconds since epoch).

See also:

  • SNTP API
  • Timers and time
  • asctime
  • ctime
  • gmtime
  • localtime
  • strftime
  • IETF RFC5905: Network Time Protocol Version 4: Protocol and

Algorithms Specification

Java Sockets

The sockets API is the defacto standard API for programming against TCP/IP. My programming language of choice is Java and it has full support for sockets. What this means is that I can write a Java based application that leverages sockets to communicate with the ESP32. I can send and receive data through quite easily.

In Java, there are two primary classes that represents sockets, those are java.net.Socket which represents a client application which will form a connection and the second class is java.net.ServerSocket which represents a server that is listening on a socket awaiting a client connection. Since the ESP32 can be either a client or a server, both of these Java classes will come into play.

To connect to an ESP32 running as a server, we need to know the IP address of the device and the port number on which it is listening. Once we know those, we can create an instance of the Java client with: Socket clientSocket = new Socket(ipAddress, port);

This will form a connection to the ESP32. Now we can ask for both an InputStream from which to receive partner data and an OutputStream to which we can write data.

InputStream is = clientSocket.getInputStream(); OutputStream os = clientSocket.getOutputStream();

When we are finished with the connection, we should call close() to close the Java side of the connection:

clientSocket.close();

It really is as simple as that. Here is an example application: package kolban;

import java.io.OutputStream;
import java.net.Socket;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
public class SocketClient {
private String hostname;
private int port;
public static void main(String[] args) {
Options options = new Options();
options.addOption("h", true, "hostname");
options.addOption("p", true, "port");
CommandLineParser parser = new DefaultParser();
try {
CommandLine cmd = parser.parse(options, args);
SocketClient client = new SocketClient();
client.hostname = cmd.getOptionValue("h");
client.port = Integer.parseInt(cmd.getOptionValue("p"));
client.run();
} catch (Exception e) {
e.printStackTrace();
}
}
public void run() {
try {
int SIZE = 65000;
byte data[] = new byte[SIZE];
for (int i = 0; i < SIZE; i++) {
data[i] = 'X';
}
Socket s1 = new Socket(hostname, port);
OutputStream os = s1.getOutputStream();
os.write(data);
s1.close();
System.out.println("Data sent!");
} catch (Exception e) {
e.printStackTrace();
}
}
} // End of class
// End of file

To configure a Java application as a socket server is just as easy. This time we create an instance of the SocketServer class using:

SocketServer serverSocket = new SocketServer(port) The port supplied is the port number on the machine on which the JVM is running that will be the endpoint of remote client connection requests. Once we have a ServerSocket instance, we need to wait for an incoming client connection. We do this using the blocking API method called accept().

Socket partnerSocket = serverSocket.accept(); This call blocks until a client connect arrives. The returned partnerSocket is the connected socket to the partner which can used in the same fashion as we previously discussed for client connections.

This means that we can request the InputStream and OutputStream objects to read and write to and from the partner. Since Java is a ultithreaded language, once we wake up from accept() we can pass off the received partner socket to a new thread and repeat the accept() call for other parallel connections. Remember to close() any partner socket connections you receive when you are done with them.

So far, we have been talking about TCP oriented connections where once a connection is opened it stays open until closed during which time either end can send or receive independently from the other. Now we look at datagrams that use the UDP protocol.

The core class behind this is called DatagramSocket. Unlike TCP, the DatagramSocket class is used both for clients and servers. First, let us look at a client. If we wish to write a Java UDP client, we will create an instance of a DatagramSocket using:

DatagramSocket clientSocket = new DatagramSocket();

Next we will "connect" to the remote UDP partner. We will need to know the IP address and port that the partner is listening upon. Although the API is called "connect", we need to realize that no connection is formed. Datagrams are connectionless so what we are actually doing is associating our client socket with the partner socket on the other end so that when we actually wish to send data, we will know where to send it to.

clientSocket.connect(ipAddress, port);

Now we are ready to send a datagram using the send() method:

DatagramPacket data = new DatagramPacket(new byte[100], 100); clientSocket.send(data);

To write a UDP listener that listens for incoming datagrams, we can use the following:

DatagramSocket serverSocket = new DatagramSocket(port);

The port here is the port number on the same machine as the JVM that will be used to listen for incoming UDP connections. To wait for an incoming datagram, call receive().

DatagramPacket data = new DatagramPacket(new byte[100], 100); clientSocket.receive(data);

If you are going to use the Java Socket APIs, read the JavaDoc thoroughly for these classes are there are many features and options that were not listed here.

See also:

  • Java tutorial: All About Sockets
  • JDK 8 JavaDoc