您好,登录后才能下订单哦!
这篇文章主要讲解了“LiteOS的SAL及socket编程方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“LiteOS的SAL及socket编程方法是什么”吧!
SAL全称Socket Abstract Layer,即套接字抽象层,主要作用是对上层应用提供一层统一的 socket 编程接口,屏蔽底层网络硬件的差异。
LiteOS的SAL架构如下:
SAL的优势从图中一看即知:
无论底层使用以太网+LwIP协议栈组合,还是使用ESP8266/M26+AT框架组合,经过SAL套接字抽象层之后,对用户提供的接口都是统一的,极大的提高了程序的可移植性。
SAL框架的源码及其实现在SDK中的IoT_LINK_1.0.0\iot_link\network\tcpip
目录:
除了sal文件夹之外,其余的文件夹分别对应着不同的sal实现,比如esp8266_socket对应的是基于AT框架和ESP8266的SAL实现。
SAL相关的头文件存放在IoT_LINK_1.0.0\iot_link\inc
文件夹中,如图:
sal.h
:SAL头文件,使用时需包含;
sal_imp.h
:抽象接口定义头文件;
sal_types.h
:socket编程中涉及到的类型定义;
sal_define.h
:socket编程中涉及到的宏定义;
link_endian.h
:socket编程中的大小端字节序转换函数定义;
Socket称为套接字,本质上是一种文件描述符,所以socket通信的过程和操作文件的方法基本类似。
TCP/IP协议族的传输层中,分为有连接的,可靠的TCP传输方式,和无连接的,不可靠的UDP传输方式,所以Socket分为两种:
流式Socket(SOCK_STREAM):提供可靠的、面向连接的通信流,使用TCP协议;
数据报Socket(SOCK_DGRAM):提供一种无连接的服务,使用UDP协议;
一个标准的Socket应该包括以下五部分:
协议类型
目的IP
目的端口
源ip
源端口
SAL提供了两种socket的结构体用于存放数据,sockaddr结构体和sockaddr_in结构体,定义均在sal_types.h
文件中。
sockaddr结构体的定义如下:
struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
参数说明如下:
sa_family:地址族,一般为AF_INET,表示IPv4协议;
sa_data:包含了源ip、源端口、目的ip、目的端口;
sockaddr_in结构体的定义如下:
struct sockaddr_in { sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* Port number. */ struct in_addr sin_addr; /* Internet address. */ unsigned char sin_zero[8]; /* Pad to size of `struct sockaddr'. */ };
sockaddr结构体将所有的ip和端口信息都放在了sa_data中,不利用编程,而sockaddr_in结构体本质上和sockaddr结构体一样,但是将目的ip和目的端口分离出来,容易编程,所以一般在使用的时候有如下技巧:
使用sockaddr_in结构体赋值,作为参数传递时强制转换为sockaddr类型传递。
在sockaddr_in结构体中填写sin_port和sin_addr这两个值时,需要注意:
in_port_t
是uint16_t
类型;
sin_addr
是uint32_t
类型;
这样就涉及到了两个转换问题:
ip地址的转换
ip地址通常是一个字符串,比如"192.168.1.100"
,但是此处需要转换为一个uint32_t类型的数据,SAL提供了一个转换函数,在之前提到的link_endian.h
文件中,函数如下:
字节序的转换
字节序分为大端存储和小端存储,为了保证统一性,屏蔽硬件差异,需要将ip地址和端口的值转换为网络字节序,SAL提供了本地字节序和网络字节序的互相转换函数,在link_endian.h
文件中,其中h表示host主机,n表示network网络字节序:
htonl(unsigned long int hostlong); htons(unisgned short int hostshort); ntohl(unsigned long int netlong); ntohs(unsigned short int netshort);
本实验中我们使用ESP8266+AT框架+SAL进行实验,所以需要开启使能AT框架和SAL。
关于AT框架具体的剖析,可以阅读上一篇教程。
在工程目录下的.sdkconfig
中手动配置开启驱动框架(串口使用)和AT框架:
实验中使用的是ESP8266,所以还需要配置路由器的SSID和PASSWD,在SDK目录中的IoT_LINK_1.0.0\iot_link\network\tcpip\esp8266_socket
目录下, 打开esp8266_socket_imp.h
文件:
在其中设置ESP8266连接的热点名称和密码,这里我的设置如下:
最后,需要修改同文件夹下的esp8266_socket_imp.mk
文件,将图中标出的两处TOP_DIR
改为SDK_DIR
:
SAL默认是未开启的,需要在工程目录下的.sdkconfig
中手动配置开启:
其中CONFIG_TCPIP_ENABLE = y
需要自己添加,CONFIG_TCPIP_TYPE
宏定义的值目前支持,可以根据自己的需求选择:
"lwip_socket"
"linux_socket"
"macos_socket"
"esp8266_socket"
"none"
注意:两个宏定义必须同时存在且使能,SAL才会生效。
使能了SAL之后,系统会自动进行初始化,在SDK目录中的IoT_LINK_1.0.0\iot_link
下的link_main.c
文件中即可看到:
在本实验中,TCP Server使用网络调试助手模拟,在本机8000端口开启一个TCP服务器,如图:
API原型如下:
int sal_socket(int domain, int type, int protocol);
参数说明如下:
参数 | 说明 | 常用值 |
---|---|---|
domain | 协议或地址族 | AF_INET,表示IPv4 |
type | socket类型 | SOCK_STREAM,表示TCP |
SOCK_DGRAM,表示UDP | ||
protocol | 使用的协议号 | 0,表示使用默认协议号 |
返回值 | socket描述符 | int类型值,-1则表示失败 |
API原型如下:
int sal_connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
addr | sockaddr结构体指针 |
addrlen | sockaddr结构体长度 |
API原型如下:
int sal_send(int sockfd,const void *buf,size_t len,int flags);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
buf | 发送数据 |
len | 发送数据长度 |
flags | 发送或接收标记,一般都设为0 |
API原型如下:
int sal_recv(int sockfd,void *buf,size_t len,int flags);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
buf | 接收数据缓冲区 |
len | 接收数据缓冲区长度 |
flags | 发送或接收标记,一般都设为0 |
API原型如下:
int sal_closesocket(int sockfd);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
打开之前创建的HelloWorld工程(如果没有,可以参考第一篇教程新建),创建下面的文件夹sal_test_demo
,并在该文件夹中新建一个测试文件sal_tcp_demo.c
:
编辑以下内容:
注意,其中的server_ip和server_port应该是服务器的实际情况相对应!
#include <osal.h> #include <sal.h> #define server_port 8000 #define server_ip "192.168.0.101" static int sal_tcp_demo_entry() { int sockfd; /* 创建TCP socket */ sockfd = sal_socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { printf("TCP Socket create fail.\r\n"); return -1; } else { printf("TCP Socket create ok.\r\n"); } /* 连接服务器 */ struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(server_port); server_addr.sin_addr.s_addr = inet_addr(server_ip); while(-1 == sal_connect(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))) { //连接失败,则1s后自动重连 printf("connect server fail, repeat...\r\n"); osal_task_sleep(1000); } printf("connect server ok.\r\n"); int nbytes; char buf[] = "hello server!"; //发送数据到服务器 nbytes = sal_send(sockfd, buf, sizeof(buf), 0); if(nbytes < 0) { printf("send dat %s fail.\r\n", buf); return -1; } else { printf("send [%d] bytes: %s.\r\n", nbytes , buf); } //等待接收服务器数据 char recv_buf[50]={0}; while( -1 == (nbytes = sal_recv(sockfd, recv_buf, 50, 0))); printf("recv [%d] bytes: %s.\r\n", nbytes, recv_buf); //关闭socket sal_closesocket(sockfd); printf("TCP socket closed.\r\n"); return 0; } int standard_app_demo_main() { osal_task_create("sal_tcp_demo",sal_tcp_demo_entry,NULL,0x800,NULL,12); return 0; }
然后在user_demo.mk中添加文件路径:
#example for sal_tcp_demo ifeq ($(CONFIG_USER_DEMO), "sal_tcp_demo") user_demo_src = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/sal_test_demo/sal_tcp_demo.c} endif
位置如下:
最后在.sdkconfig中配置选中该demo文件:
然后编译,下载,即可看到串口输出(前提是确保TCP服务器已开启):
在TCP服务端软件也可以看到:
在服务端发送数据,在串口可以看到客户端已接收:
在本实验中,UDP Server使用网络调试助手模拟,在本机8000端口开启一个UDP服务器,如图:
API原型如下:
int sal_connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
addr | sockaddr结构体指针 |
addrlen | sockaddr结构体长度 |
API原型如下:
int sal_sendto(int sockfd, const void *dataptr, size_t size, int flags, const struct sockaddr *to, socklen_t tolen);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
dataptr | 待发送的数据指针 |
size | 发送包数据大小 |
flags | 发送或接收标记,一般都设为0 |
addr | sockaddr结构体指针 |
addrlen | sockaddr结构体长度 |
API原型如下:
int sal_recvfrom(int sockfd, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
mem | 接收缓冲区数据指针 |
size | 接收数据大小 |
flags | 发送或接收标记,一般都设为0 |
addr | sockaddr结构体指针 |
addrlen | sockaddr结构体长度 |
API原型如下:
int sal_closesocket(int sockfd);
参数说明如下:
参数 | 说明 |
---|---|
sockfd | 创建成功的sockfd描述符 |
打开之前创建的HelloWorld工程(如果没有,可以参考第一篇教程新建),创建下面的文件夹sal_test_demo
,并在该文件夹中新建一个测试文件sal_udp_demo.c
:
编辑以下内容:
注意,其中的server_ip和server_port应该是服务器的实际情况相对应!
#include <osal.h> #include <sal.h> #define server_port 8000 #define server_ip "192.168.0.101" static int sal_udp_demo_entry() { int sockfd; /* 创建udp socket */ sockfd = sal_socket(AF_INET, SOCK_DGRAM, 0); if(sockfd < 0) { printf("udp Socket create fail.\r\n"); return -1; } else { printf("udp Socket create ok.\r\n"); } /* 服务端信息 */ struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(server_port); server_addr.sin_addr.s_addr = inet_addr(server_ip); /* 发送数据到服务器 */ int nbytes; char buf[] = "hello server!"; nbytes = sal_sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)); if(nbytes < 0) { printf("send dat %s fail.\r\n", buf); return -1; } else { printf("send [%d] bytes: %s.\r\n", nbytes , buf); } /* 等待接收服务器数据 */ char recv_buf[50]={0}; while( -1 == (nbytes = sal_recvfrom(sockfd, recv_buf, 50, 0, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)))); printf("recv [%d] bytes: %s.\r\n", nbytes, recv_buf); /* 关闭socket */ sal_closesocket(sockfd); printf("udp socket closed.\r\n"); return 0; } int standard_app_demo_main() { osal_task_create("sal_udp_demo",sal_udp_demo_entry,NULL,0x800,NULL,12); return 0; }
然后在user_demo.mk中添加文件路径:
#example for sal_udp_demo ifeq ($(CONFIG_USER_DEMO), "sal_udp_demo") user_demo_src = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/sal_test_demo/sal_udp_demo.c} endif
位置如下:
最后在.sdkconfig中配置选中该demo文件:
然后编译,下载,即可看到串口输出(前提是确保UDP服务器已开启):
在UDP服务端软件也可以看到:
在服务端发送数据,在串口可以看到客户端已接收:
感谢各位的阅读,以上就是“LiteOS的SAL及socket编程方法是什么”的内容了,经过本文的学习后,相信大家对LiteOS的SAL及socket编程方法是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。