socket 工具_物联网项目开发快速入门(九):socket快速入门,esp32基于socket与手机进行简单通信...

d2f37f325f5b1ff63dc90268516427e4.png

这个只是一个简单的例子,主要是为了讲解socket的开发流程,在实际的开发的过程中代码会比这个例程复杂很多。

环境:

单片机:esp-idf

安卓手机:socket 调试工具

socket原理介绍

1.网络协议

在介绍socket之前先看看传说中的OSI网络七层协议

8fdf0c0609dde23d3f01cf3c9ae3a323.png

每层的意义只要了解就可以了,具体应用的时候在去深入研究某一个协议即可。

在说说TCP/IP协议,TCP/IP传输协议即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。TCP/IP传输协议对互联网中各部分进行通信的标准和方法进行了规定.TCP/IP传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中.它与前面说的七层结构的关系如下:

1493933bf40651115d3dc3457be1e4b0.png

2.LwIP

LwIP是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式设备中使用。

乐鑫官方已经在esp-idf中移植好了lwip,用户在基于esp-idf开发时只需要按照通用的socket开发接口调用函数即可完成socket网络套接字编程。

3. socket网络编程概念

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

熟悉Linux编程的人可以比较好理解socket编程,socket可以理解成是一个文件,用户通过open,read,write 等函数操作这个文件,进行数据的读写操作。只不过这个文件在打开之前需要看着固定格式设置(ip和端口)才能作为网络文件被用户读写。

4. socket具体操作

socket编程主要分为tcp和udp ,它们有什么区别请自行百度。tcp可以分为server(服务器)和client(客户端)。两者通信的过程和主要函数如下:

7835978ede40e88738196bfa3fcf2eb2.png

函数说明:

服务器:

socket() 创建socket(套接字)文件句柄。

bind() 为这个socket绑定ip地址和端口

listen() 开始监听端口

accept() 等待客户端连接

read()/write() 双方开始通信。read()读取客户端的数据。write向客户端发送数据。

(在Lwip中发送和接收数据使用recv()/send())

close()关闭此socket

客户端:

唯一不同的就是多了一个connet()函数,这个函数的作用是通过预先给出的ip地址和端口连接到服务器。

源码分析

代码位置:/esp-idf/examples/protocols/sockets/tcp_server/ 主要代码为:

/**
 * IPV4 和 IPV6 的配置,默认使用IPV4,对于应用开发来说没有区别,
 * 我们只需在应用时设置不同的配置即可.
 */
#ifdef CONFIG_EXAMPLE_IPV4  
        struct sockaddr_in destAddr;//socket 结构体,定义了ipv4的一些配置,
        destAddr.sin_addr.s_addr = htonl(INADDR_ANY);//目的ip地址为0.0.0.0,任何地址都可以连接此服务器
        /*此参数表示套接字要使用的协议簇,
         *一般设置为AF_INET表示TCP/IPV4
         *AF_INET6 为TCP/IPV6
         */
        destAddr.sin_family = AF_INET;
        /*服务器监听的端口,就说如果有客户端发送数据只有发送到这个端口,服务器才能收到*/
        destAddr.sin_port = htons(PORT);
        addr_family = AF_INET;//通信协议使用TCP/IPV4
        /*
         *  ip_protocol 指定此socket接收到协议包
         * 注意:次处没有使用IPPROTO_TCP  ,查看代码发现底层使用的是一套逻辑
        */
        ip_protocol = IPPROTO_IP;
        inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1);
#else // IPV6
        struct sockaddr_in6 destAddr;
        bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un));
        destAddr.sin6_family = AF_INET6;
        destAddr.sin6_port = htons(PORT);
        addr_family = AF_INET6;
        ip_protocol = IPPROTO_IPV6;
        inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1);
#endif
        /*
         * 创建 socket,第二个参数为SOCK_STREAM,
         * SOCK_STREAM的含义是提供面向连接的稳定数据传输,即TCP协议。
         * 还可以将这个参数配置成SOCK_DGRAM 或者SOCK_RAW ,
         * SOCK_DGRAM SOCK_DGRAM 是无保障的面向消息的socket,
         * 主要用于在网络上发广播信息就是UDP
         * 
         */
        int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
        if (listen_sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created");
        
        int err = bind(listen_sock, (struct sockaddr *)&destAddr, sizeof(destAddr));
        if (err != 0) {
            ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket binded");
         /**
         * 开始监听这个socket(套接字)连接的端口,
         * 注意第二个参数为允许连接这个服务器的最多连接数
         * 此处只允许一个客户端连接
        */
        err = listen(listen_sock, 1);
        if (err != 0) {
            ESP_LOGE(TAG, "Error occured during listen: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket listening");

        struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6
        uint addrLen = sizeof(sourceAddr);
        /**
         * 等待有客户端连接这个服务器,accept()是一个阻塞函数,
         * 在listen监听队列上没有链接时则死等在这里,当有连接时则往下执行
        */
        int sock = accept(listen_sock, (struct sockaddr *)&sourceAddr, &addrLen);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket accepted");
        /*连接成功,可以循环读写socket,完成网络通信*/
        while (1) {
            /**
             * 接收socket上的数据
            */
            int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
            // Error occured during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recv failed: errno %d", errno);
                break;
            }
            // Connection closed
            else if (len == 0) {
                ESP_LOGI(TAG, "Connection closed");
                break;
            }
            // Data received
            else {
                /**
                 * 接收len长度大于0 表示成功接收到了数据
                */
                // Get the sender's ip address as string
                if (sourceAddr.sin6_family == PF_INET) {//解析地址
                    inet_ntoa_r(((struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
                } else if (sourceAddr.sin6_family == PF_INET6) {
                    inet6_ntoa_r(sourceAddr.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 = send(sock, rx_buffer, len, 0);
                if (err < 0) {
                    ESP_LOGE(TAG, "Error occured during sending: errno %d", errno);
                    break;
                }
            }
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);/关闭套接字
        }

例程演示

1.进入例程 执行make menuconfig 命令配置esp32对应的串口

1b512c7acebc46c394dbbb3f3bb3706f.png

2 配置wifi 账户密码和服务器端口。

f9589e37bd6750fc89a16440dc2e49bb.png

3 执行make -j8 flash monitor,socket 创建成功 等待连接

ff0f72f2dcb995b35d4e09566cfe410f.png

4. 打开手机,在应用市场下载socket 测试app。打开app,创建一个tcp client。 输入esp32 对应的ip地址和端口,点击连接按钮。

注意:esp32 和手机必须在用一个网络中

5.连接成功,esp32终端会输出 socket accepted

0449a6fc6bccfbaae0c1226a078a5187.png

6.收发数据

手机端app 输入hi esp32 点击发送,esp32终端则会收到这串字符并返回给手机app:

be8716b064f963d5880ca0738c2d1726.png

20216ac43c67b47423a03457a67841ad.png

写着最后 :

socket网络套接字是网络编程中最简单也是使用最频繁的技术。如果有志成为物联网开发的工程师的话socket编程是必须掌握的基础知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值