網絡通信原理
這里只是簡單的了解一下,勉強夠用就行。
TCP/IP協議
OSI模型共有七層,前面的物理層我應該已經用到了,就是前面調以太網的phy。TCP/IP協議中,自底而上因用法被簡化為4個層次,數據鏈路層、網絡層、傳輸層和應用層。
數據鏈路層實現網卡接口的網絡驅動程序,以處理數據在物理媒介。數據鏈路層兩個常用的協議是ARP,RARP。
網絡層實現數據包的選路和轉發。WAN通常使用眾多分級的路由器來連接分散的主機或LAN,因此,通信的兩台主機一般不是直接相連的,使得在傳輸層和網絡應用程序來看,通信的雙方是直接相連的。
網絡層最核心的協議是IP協議。IP協議根據數據包的目的IP地址來決定如何投遞它。如果數據包不能直接發送給目標主機,那么IP協議根據數據包的目的IP地址來決定如何投遞它。如果數據包不能直接發送給目標主機,那么IP協議就為它尋找一個合適的下一跳路由器,並將數據包交付給該路由器來轉發。逐跳(hop by hop)
傳輸層,兩個協議,TCP/UDP協議,這次我用的是UDP協議,目的端通過數據校驗發現數據錯誤而將其丟棄,UDP只是單獨通知應用程序發送失敗。使用UDO協議的應用程序通常要自己處理數據確認、超時重傳等邏輯。程序每次發送數據都要明確指定接收端的地址(IP地址等信息)。基於數據報的服務,區別於數據流服務,每個UDP數據報都一個長度,接收端必須以該長度為最小單位將其所有內容一次性讀出。
**網絡套接字**
socket,封裝了TCP/IP協議的通信編程接口,套接字,實際上是一個通信端點。需要主機IP和端口號才能建立連接。
UDP套接字編程模型
socket() : 創建套接字。
bind() : 綁定IP:Port
connect() :將套接字連接到目的地址。
listen() : 監聽
accept() : 接收連接請求。
send()/recv() 和 sendto()/recvfrom() : 發送和接收數據。
closesocket() : 關閉套接字
-------------------------------------------分割線--------------------------------------------
網絡通訊常識和邏輯過程。
任何一個socket通訊,都需要IP地址和port端口號的。不指定局域網內的某一設備,局域網所有設備如果監聽了這個端口號,那么都可以收到esp32的消息。
如果要指定的IP地址的設備,那么就需要指定明確的地址。
-------------------------------------------分割線--------------------------------------------
UDP通信,UDP函數參考:
uint8_t begin(IPAddress a,uint16_t p)
函數功能:啟動監聽來自與於某個地址發送給某個端口的數據或監聽某個端口的數據參數
a 為監聽的IP地址;p為監聽的端口號
int parsePacket()
函數功能:獲取接收數據信息
返回值:如果有數據包可用,則返回隊首數據包長度,否則返回0
IPAddress remoteIP()
函數功能:獲取目標設備的IP地址
返回值:目標IP地址
uint16_t remotePort()
函數功能:獲取目標設備的端口號
返回值:目標端口號
int read(char* buffer, size_t len)
函數功能:讀取數據
int beginPacket(IPAddress ip, uint16_t port)
函數功能:准備發送數據
參數
ip為目標IP
port為目標端口號
int endPacket()
函數功能:發送數據
void stop()
函數功能:停止監聽,釋放資源
------------------------------------------分割線---------------------------------------------
第一步:上電后連接路由器,獲取路由器分配的IP地址
第二步:系統消息監聽,如果收到IP地址成功獲取的回調,則開始創建socket
第三步:涉及到要連接服務器,是否存在?所以先判斷下是否存在,比較全面,雖然說UDP是不可靠的,但是這樣做,可以避免許多事情!或者連接成功路由器之后直接發送到指定的地址,不管是否存在。
第四步:一旦服務器有響應,我這里就發送一個字符串到服務器。
`/* BSD Socket API Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
include <string.h>
include <sys/param.h>
include "freertos/FreeRTOS.h"
include "freertos/task.h"
include "esp_system.h"
include "esp_wifi.h"
include "esp_event.h"
include "esp_log.h"
include "nvs_flash.h"
include "esp_netif.h"
include "protocol_examples_common.h"
include "lwip/err.h"
include "lwip/sockets.h"
include "lwip/sys.h"
include <lwip/netdb.h>
define PORT 3333
static const char *TAG = "example";
void udp_server_task(void *pvParameters)
{
char rx_buffer[128];
char addr_str[128];
int addr_family = (int)pvParameters;
int ip_protocol = 0;
struct sockaddr_in6 dest_addr;
while (1) {
if (addr_family == AF_INET) {
struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(PORT);
ip_protocol = IPPROTO_IP;
} else if (addr_family == AF_INET6) {
bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un));
dest_addr.sin6_family = AF_INET6;
dest_addr.sin6_port = htons(PORT);
ip_protocol = IPPROTO_IPV6;
}
int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
if (sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Socket created");
if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)
if (addr_family == AF_INET6) {
// Note that by default IPV6 binds to both protocols, it is must be disabled
// if both protocols used at the same time (used in CI)
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
endif
int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (err < 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
}
ESP_LOGI(TAG, "Socket bound, port %d", PORT);
while (1) {
ESP_LOGI(TAG, "Waiting for data");
struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
// Error occurred during receiving
if (len < 0) {
ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
break;
}
// Data received
else {
// Get the sender's ip address as string
if (source_addr.sin6_family == PF_INET) {
inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
} else if (source_addr.sin6_family == PF_INET6) {
inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);
}
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string...
ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str);
ESP_LOGI(TAG, "%s", rx_buffer);
int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
if (err < 0) {
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
close(sock);
break;
}
}
}
if (sock != -1) {
ESP_LOGE(TAG, "Shutting down socket and restarting...");
shutdown(sock, 0);
close(sock);
}
}
vTaskDelete(NULL);
}
`
這是udp的官方例程,我的收發現在沒有回應
我人暈了。。。。
不知道是哪里的問題,只能從頭開發分析,為了不煩躁和耐得下性子,我又開始邊工作邊寫博客。
現在我打算引入監控端口的函數,讓他顯示在monitor上
東搞西搞就搞出來了。。。。。。