ESP32 TCP/IP

Материал из razgovorov.ru
Версия от 00:24, 29 июня 2017; Разговоров Михаил (обсуждение | вклад) (Новая страница: «TCP / IP - это сетевой протокол, который используется в Интернете. Это протокол, который ESP32 и…»)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

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 байта в сетевой порядок байтов (большой конец).

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

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, время выполнения немедленно отклонит вызов. Это не только предотвращает потребление ресурсов при запуске на сервера, а также может использоваться в качестве индикатора для вызывающего абонента, что его лучше обслуживать в другом месте.

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

Handling errors

Most of the sockets APIs return an int return code. If this code is < 0 then an error has occurred. The nature of the error can be found using the global int called "errno". However, in a multitasking environment, working with global variables is not recommended. In the sockets area, we can ask a socket for the last error it encountered using the following code fragment: 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; } The meanings of the errors can be compared against constants. Here is a table of constants used in the current FreeRTOS implementation: Symbol Value Description 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

Within the "menuconfig" there are some settings that relate to TCP/IP and can be found within the lwIP settings. The settings are: • Max number of open sockets – integer – CONFIG_LWIP_MAX_SOCKETS – This is the 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()

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: • 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: • netinet/in.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

If we think of TCP as forming a connection between two parties similar to a telephone 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 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 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 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 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, 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 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 letter, when we send a datagram, we lose sight of whether or not it actually arrives safely at the destination. 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 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); sendto(socket_fd, data, size, 0, destAddr, destAddrLen); • socket • sendto • recvfrom

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 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 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? 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 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 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: • mbed TLS • mbed TLS home page • mbed TLS tutoria l • mbed TLS API reference

mbedTLS app structure

Let us start to break down the structure of a TLS application that uses the mbedTLS APIs. First there is the notion of a network context that is initialized by a call to mbedtls_net_init(). mbedtls_net_context server_fd; mbedtls_net_init(&server_fd); There is nothing more to explain here. The data that is initialized is "opaque" to us and 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(). mbedtls_ssl_context ssl; mbedtls_ssl_init(&ssl); Again, there is nothing more to explain here. The data is again opaque and this function merely initializes it for us. Calling this function is also part of the rules. Now we call mebtls_ssl_config_init(). mbedtls_ssl_config config; mbedtls_ssl_config_init(&config); These initializations repeat for other data types including: 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 utilizes good random number generators. What is a "good" random number? Since computers are deterministic devices, the generation of a random number is performed through the execution of an algorithm and since algorithms are deterministic, 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. We initialize the random number generator with a call to mbedtls_ctr_drbg_seed(). 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 With the setup under our belts, it is now time to start considering SSL based 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 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); The hostname and port define where we are connecting to. Notice the first parameter. 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_ssl_config_defaults(). For example: mbedtls_ssl_config_defaults( &conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBED_SSL_PRESET_DEFAULT) 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 process is called authentication. We can define what kind of authentication we wish to perform by calling mbedtls_ssl_conf_authmode(). mbedtls_ssl_conf_authmode(&ssl, MBEDTLS_SSL_VERIFY_NONE) Earlier we said that SSL is heavily dependent on a good random number generator. Now we tell the environment which random number generator we wish to use: 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(). mbedtls_ssl_set_hostname(&ssl, "name") Now we instruct the SSL environment which functions to use to send and receive data by calling mbedtls_ssl_set_bio(). 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 environment. What remains is to actually read and write data. To write data we call 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

Here is a sample function that has been tested on an ESP32 to make an HTTPS call to an HTTPS server to retrieve some results.

  1. include "mbedtls/platform.h"
  2. include "mbedtls/ctr_drbg.h"
  3. include "mbedtls/debug.h"
  4. include "mbedtls/entropy.h"
  5. include "mbedtls/error.h"
  6. include "mbedtls/net.h"
  7. include "mbedtls/ssl.h"
  8. include "esp_log.h"
  9. include "string.h"
  10. include "stdio.h"
  11. define SERVER_NAME "httpbin.org"
  12. 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"); } Notes: When debugging MBED in Curl set MBEDTLS_DEBUG to 1 in curl_config.h

OpenSSL

OpenSSL is a popular implementation of an SSL stack. In the ESP32 environment, the 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

В Интернете серверные машины можно найти по их службе имен доменов (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

The ESP-IDF provides a set of rich APIs for programming mDNS on the ESP32. Specifically, we can either advertise ourselves in mDNS or else query existing mDNS information. The attributes of an mDNS server entry appear to be: • 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: If all has gone well, we will find a new Windows service running called "Bonjour Service": There is also a Bonjour browser available here … http://hobbyistsoftware.com/bonjourbrowser

Avahi

An implementation of Multicast DNS on Linux is called Avahi. Avahi runs as the systemd daemon called "avahi-daemon". We can determine whether or not it is running with: $ 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. The default name that avahi advertizes itself as is the local hostname. 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 name resolution. An example would be: hosts: files mdns4_minimal [NOTFOUND=return] dns Which says "first look in /etc/hosts, then consult mDNS and then use full DNS". What 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 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 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" package: $ sudo apt-get install avahi-utils To see the list of mDNS devices in your network, we can use the avahi-browse command. For example: $ 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 To access an mDNS advertized server from Microsoft Windows, you will need a service similar to Apple's Bonjour installed. Bonjour is distributed as part of Apple's iTunes product. Once installed, we should be able to access the published servers at their <name>.local address. A partner windows tool called "Bonjour Browser" is available which displays an mDNS listing of servers on windows. 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 multithreaded 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