SAL,是Socket Adapter Layer的简称。AliOS Things中SAL套件是针对MCU+外部通信模组的方式,提供标准Socket接口服务。SAL套件提供AT命令到Socket标准接口的转换。借助SAL套件,用户不用感知底层通信方式和介质(如WiFi、2G、4G等模组),可以使用标准Socket接口进行应用开发,使上层应用具有更好的可移植性。AliOS Things中SAL组件的架构图如下。
其中,组件包括:
- SAL Core:由AliOS Things提供,SAL核心组件(上图蓝色)。主要包括Socket连接管理、数据缓存、协议转换等功能,对上提供标准Socket接口服务,对下提供统一的HAL(Hardware Adapter Layer)接口规范(可以对接到不同厂商的AT模组)。
- SAL Driver:驱动,部分(如sim800、M5310)由AliOS Things提供(上图绿色),其他由用户自己提供(上图红色)。SAL驱动模块基于具体型号的通信模组提供的AT命令,实现SAL规范的HAL接口功能。
API列表
名称 | 说明 |
socket | 创建套接字,返回文件描述符。 |
connect | 与远端服务器建立一个连接。 |
select | 查询一个或者多个socket的可读性、可写性及错误状态信息。 |
gethostbyname |
域名解析,获取主机域名对应的IP,不可重入。 |
getaddrinfo | 域名解析,获取主机域名对应的IP,可重入。 |
freeaddrinfo | 释放addrinfo结构体,一般与getaddrinfo配合使用。 |
send | 向远端发送数据。 |
recv | 接收远端发送的数据。 |
sendto | 无连接模式下的发送数据。 |
recvfrom | 无连接模式下的接收数据。 |
close | 关闭socket,释放相关资源。 |
sal_init | SAL模块初始化,包括初始化底层驱动模块。 |
sal_add_dev | 配置驱动参数,例如串口通信串口号、波特率等,并添加设备。 |
使用
添加该组件




头文件
对外头文件代码位于include/network/sal
,包括
sal/sal_arch.h
sal/sal_def.h
sal/sal_ipaddr.h
sal/sal_sockets.h
使用时只需包含
#include <network/network.h>
使用示例
SAL提供标准socket的API,编程方式也按照通用的socket编程。例如,与远端建立TCP连接并发送数据:
/* 域名解析 */
if ((rc = getaddrinfo(server_domain, servname, &hints, &addrInfoList)) != 0) {
LOGE(TAG, "getaddrinfo error: %d, errno = %d", rc, errno);
return;
}
for (cur = addrInfoList; cur != NULL; cur = cur->ai_next) {
if (cur->ai_family != AF_INET) {
LOGE(TAG, "Socket type error");
continue;
}
/* 创建socket */
fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if (fd < 0) {
LOGE(TAG, "Failed to create socket, errno = %d", errno);
continue;
}
/* 与远端连接 */
if (connect(fd, cur->ai_addr, cur->ai_addrlen) == 0) {
break;
} else {
LOGE(TAG, "Failed to connect addr, errno = %d", errno);
}
close(fd);
}
/* 向远端发送数据 */
if (send(fd, tcp_payload, strlen(tcp_payload), 0) <= 0) {
LOGE(TAG, "Failed to send data, errno = %d", errno);
goto ret;
}
更详细的例子请参考application/example/example_legacy/sal_app
API 详情
socket
原型
int socket(int domain, int type, int protocol);
接口说明
使用 SAL socket 通信之前,使用该函数创建套接字,返回文件描述符。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
domain | int | 输入 | 创建的套接字指定协议集,目前支持AF_INET |
type | int | 输入 | socket类型,目前支持SOCK_STREAM和SOCK_DGRAM |
protocol | int | 输入 | 实际使用的传输协议,默认为0 |
返回值说明
值 | 说明 |
---|---|
非负值 | 成功,即文件描述符,后续socket操作使用该值 |
负值 | 失败 |
接口示例
/* 创建UDPsocket */
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0){
LOGE(TAG, "Failed to create socket, errno = %d", errno);
return;
} else {
LOGD(TAG, "UDP socket create OK");
}
connect
原型
int connect(int s, const struct sockaddr *name, socklen_t namelen);
接口说明
用来与远端服务器建立一个连接。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
s | int | 输入 | socket文件描述符 |
name | struct sockaddr * | 输入 | 指向 sockaddr 结构的指针,存放要连接的服务器的 IP 地址和端口号等信息 |
namelen | int | 输入 | sockaddr 结构体的长度 |
返回值说明
值 | 说明 |
---|---|
0 | 连接成功 |
非0 | 失败 |
接口示例
/* 与远端建立连接 */
if (connect(fd, ai_addr, ai_addrlen) == 0) {
break;
} else {
LOGE(TAG, "Failed to connect addr, errno = %d", errno);
}
select
原型
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
struct timeval *timeout);
接口说明
用来查询一个或者多个socket的可读性、可写性及错误状态信息。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
maxfdp1 | int | 输入 | 最大文件描述符值加1 |
readset | fd_set * | 输入 | (可选)指向一组等待可读性检查的套接口 |
writeset | fd_set * | 输入 | (可选)指向一组等待可写性检查的套接口 |
exceptset | fd_set * | 输入 | (可选)指向一组等待错误检查的套接口 |
timeout | struct timeval * | 输入 | 最长等待时间,阻塞操作则为NULL |
返回值说明
值 | 说明 |
---|---|
正值 | 有读、写、错误事件 |
0 | 超时 |
负值 | 出错,具体错误通过errno获取 |
接口示例
FD_ZERO(&sets);
FD_SET(fd, &sets);
/* 对write set进行等待 */
ret = select(fd + 1, NULL, &sets, NULL, &timeout);
if (ret > 0) {
if (0 == FD_ISSET(fd, &sets)) {
ret = 0;
continue;
}
/* 发送数据 */
send(fd, buf + len_sent, len - len_sent, 0);
} else if (0 == ret) {
break;
}
gethostbyname
原型
struct hostent *gethostbyname(const char *name);
接口说明
域名解析,获取主机域名对应的IP。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
name | const char * | 输入 | 主机域名 |
返回值说明
值 | 说明 |
---|---|
非NULL | 成功 |
NULL | 失败 |
其中,hostent结构体定义见标准宏和结构体说明。
接口示例
struct sockaddr_in server_addr;
struct hostent *host;
/* 域名解析 */
if ((host = gethostbyname(host_addr)) == NULL) {
LOGE("Gethostname error, %s\n ", strerror(errno));
return -1;
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
/* 连接远端 */
connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr);
getaddrinfo
原型
int getaddrinfo(const char *nodename, const char *servname,
const struct addrinfo *hints, struct addrinfo **res);
接口说明
域名解析,获取主机域名对应的IP。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
nodename | const char * | 输入 | 主机域名 |
servname | const char * | 输入 | (可选)端口号字符传, |
hints | const struct addrinfo * | 输入 |
addrinfo结构体的指针,在这个结构中填入关于期望返回的信息类型的暗示,例如 hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; |
res | struct addrinfo ** | 输出 | 返回解析地址 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
非0 | 失败 |
接口示例
struct addrinfo hints;
hints.ai_family = AF_INET; // only IPv4
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
sprintf(service, "%u", port);
/* 域名解析 */
if ((rc = getaddrinfo(host, service, &hints, &addrInfoList)) != 0) {
return (uintptr_t)-1;
}
for (cur = addrInfoList; cur != NULL; cur = cur->ai_next) {
if (cur->ai_family != AF_INET) {
rc = -1;
continue;
}
/* 创建socket */
fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if (fd < 0) {
rc = -1;
continue;
}
/* 连接 */
if (connect(fd, cur->ai_addr, cur->ai_addrlen) == 0) {
rc = fd;
break;
}
close(fd);
}
freeaddrinfo
原型
void freeaddrinfo(struct addrinfo *ai);
接口说明
释放addrinfo结构体,一般与getaddrinfo配合使用。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
ai | struct addrinfo * | 输入 | addrinfo结构体指针 |
返回值说明
无
接口示例
/* 域名解析 */
getaddrinfo(host, service, &hints, &addrInfoList)
/* 释放addrinfo结构体 */
freeaddrinfo(addrInfoList);
send
原型
int send(int s, const void *data, size_t size, int flags);
接口说明
向远端发送数据。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
s | int | 输入 | socket 文件描述符 |
data | const void * | 输入 | 发送数据缓存指针 |
size | size_t | 输入 | 发送数据字节数 |
flag | int | 输入 | 控制选项,通常为 0 |
返回值说明
值 | 说明 |
---|---|
正值 | 成功,发送长度 |
非正值 | 失败 |
接口示例
/* 向已连接的远端发送数据 */
if (send(fd, tcp_payload, strlen(tcp_payload), 0) <= 0) {
LOGE(TAG, "Failed to send data, errno = %d", errno);
goto ret;
} else {
LOGD(TAG, "TCP socket send to server OK");
}
recv
原型
int recv(int s, void *mem, size_t len, int flags);
接口说明
接收远端发送的数据。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
s | int | 输入 | socket 文件描述符 |
mem | const void * | 输出 | 接收数据缓存指针 |
len | size_t | 输入 | 接收缓存大小 |
flags | int | 输入 | 控制选项,通常为 0 |
返回值说明
值 | 说明 |
---|---|
正值 | 成功,接收长度 |
非正值 | 失败 |
接口示例
while (total_received < HTTP_BUFF_SIZE) {
/* 从远端接收数据 */
bytes_received =
recv(sockfd, buffer, (HTTP_BUFF_SIZE - total_received), 0);
if (bytes_received == -1) {
return -1;
} else if (bytes_received == 0) {
return 0;
} else {
buffer = buffer + bytes_received;
}
total_received += bytes_received;
}
sendto
原型
int sendto(int s, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen);
接口说明
无连接的数据报 socket 模式下发送数据。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
s | int | 输入 | socket 文件描述符 |
data | const void * | 输入 | 发送数据缓存指针 |
size | size_t | 输入 | 发送数据字节数 |
flags | int | 输入 | 控制选项,通常为 0 |
to | const struct sockaddr * | 输入 | 指向 sockaddr 结构体的指针,存放目的主机的 IP 和端口号 |
tolen | socklen_t | 输入 | sockaddr 结构体的长度 |
返回值说明
值 | 说明 |
---|---|
正值 | 成功,发送长度 |
非正值 | 失败 |
接口示例
/* 向远端发送UDP数据 */
ret = sendto(fd, udp_payload, strlen(udp_payload), 0, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0){
LOGE(TAG, "udp sendto failed, errno = %d", errno);
close(fd);
return;
} else {
LOGD(TAG, "UDP socket sendto OK");
}
recvfrom
原型
int recvfrom(int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen
接口说明
无连接的数据报 socket 模式下接收数据。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
s | int | 输入 | socket 文件描述符 |
mem | const void * | 输出 | 接收数据缓存指针 |
len | size_t | 输入 | 接收缓存大小 |
flags | int | 输入 | 控制选项,通常为 0 |
from | struct sockaddr * | 输出 | 指向 sockaddr 结构体的指针,存放源主机的 IP 和端口号 |
fromlen | socklen_t * | 输出 | 指向 sockaddr 结构体的长度的指针 |
返回值说明
值 | 说明 |
---|---|
正值 | 成功,接收长度 |
非正值 | 失败 |
接口示例
/* 从远端接收UDP数据 */
ret = recvfrom(fd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&addr, &recvaddrlen);
if (ret < 0) {
LOGE(TAG, "udp sendto failed, errno = %d", errno);
close(fd);
return;
} else {
LOGD(TAG, "%d bytes data received.", ret);
}
close
原型
int sal_close(int s);
接口说明
关闭socket,释放相关资源。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
s | int | 输入 | socket 文件描述符 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
接口示例
/* 创建socket */
fd = socket(AF_INET, SOCK_DGRAM, 0);
/* 释放socket */
close(fd);
sal_init
原型
int sal_init(void);
接口说明
SAL模块初始化,包括初始化底层驱动模块。
参数说明
无
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
接口示例
sal_device_config_t data = {0};
/*
* 设置外接模组连接参数:
* UART配置:
* - 115200
* - 8n1
* - no flow control
* - tx/rx mode
*/
data.uart_dev.port = 1;
data.uart_dev.config.baud_rate = 115200;
data.uart_dev.config.data_width = DATA_WIDTH_8BIT;
data.uart_dev.config.parity = NO_PARITY;
data.uart_dev.config.stop_bits = STOP_BITS_1;
data.uart_dev.config.flow_control = FLOW_CONTROL_DISABLED;
data.uart_dev.config.mode = MODE_TX_RX;
/* 配置驱动参数 */
if (sal_add_dev("bk7231", &data) != 0) {
LOG("Failed to add SAL device!");
return -1;
}
/* 初始化SAL core */
sal_init();
sal_add_dev
原型
int sal_add_dev(char* driver_name, void* data);
接口说明
配置驱动参数,例如串口通信串口号、波特率等。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
driver_name | char * | 输入 | 驱动名称 |
data | void* | 输入 | 配置参数指针 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
接口示例
见sal_init示例
配置说明
SAL可配置项包括:
- 是否定义WITH_SAL宏,默认为是;
- 是否使用AOS HAL,默认为是;
- 是否开启SAL debug打印,默认为否;
- 配置SAL接收缓存大小,默认为32;
移植说明
SAL模块需要实现两类HAL,一类为模组连接操作HAL;另一类为OS基础HAL。
模组连接操作HAL
该类HAL头文件为
#include "hal_sal.h"
HAL函数以函数指针的方式,挂载在sal_opt_s结构体中。
add_dev
原型
int (*add_dev)(void* data);
接口说明
配置参数,添加设备。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
data | void* | 输入 | 配置参数指针 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
init
原型
int (*init)(void);
接口说明
初始化底层驱动
参数说明
无
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
start
原型
int (*start)(sal_conn_t *c);
接口说明
通过模组与远端建立socket连接。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
c | sal_conn_t* | 输入 | 连接参数指针 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
send_data
原型
int (*send_data)(int fd, uint8_t *data, uint32_t len,
char remote_ip[16], int32_t remote_port, int32_t timeout);
接口说明
通过模组向远端已建立socket连接发送数据。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
fd | int | 输入 | socket文件描述符 |
data | uint8_t* | 输入 | 发送缓存指针 |
len | uint32_t | 输入 | 发送长度 |
remote_ip | char [] | 输入 | 远端IP地址 |
remote_port | int32_t | 输入 | 远端端口 |
timeout | int32_t | 输入 | 超时时间,毫秒 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
domain_to_ip
原型
int (*domain_to_ip)(char *domain, char ip[16]);
接口说明
域名解析
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
domain | char * | 输入 | 主机域名 |
ip | char [] | 输出 | IP地址 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
finish
原型
int (*finish)(int fd, int32_t remote_port);
接口说明
关闭socket连接
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
fd | int | 输入 | socket文件描述符 |
remote_port | int32_t | 输入 | 远端port号 |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
deinit
原型
int (*deinit)(void);
接口说明
驱动模块去初始化,释放相关资源。
参数说明
无
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
register_netconn_data_input_cb
原型
int (*register_netconn_data_input_cb)(netconn_data_input_cb_t cb);
接口说明
注册数据接收回调函数。
参数说明
无
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
其中,
netconn_data_input_cb_t
定义如下
int (*netconn_data_input_cb_t)(int fd, void *data, size_t len, char remote_ip[16], uint16_t remote_port);
接口说明
接收数据回调函数。
参数说明
参数 | 数据类型 | 方向 | 说明 |
---|---|---|---|
fd | int | 输入 | socket文件描述符 |
data | void * | 输入 | 接收数据缓存指针 |
len | size_t | 输入 | 数据长度 |
remote_ip | char [] | 输入 | 数据源IP |
remote_port | uint16_t | 输入 | 远端port |
返回值说明
值 | 说明 |
---|---|
0 | 成功 |
负值 | 失败 |
OS基础HAL
接口 | 描述 |
sal_malloc |
malloc, 入参: s: 需要分配的内存大小 返回值:非NULL,分配的内存地址 NULL,分配内存失败 |
sal_free |
free,入参: p: 需要释放的内存指针 返回值:无 |
sal_msleep |
sleep,入参: ms: 毫秒数 返回值:无 |
sal_sem_new |
创建信号量,入参: sem:填充创建信号的地址 count:初始信号量值 返回值:0成功,-1错误 |
sal_sem_free |
销毁信号量,入参: sem:信号量地址 返回值:无 |
sal_sem_signal |
释放信号量,入参: sem: 信号量地址 返回值:无 |
sal_sem_valid |
检查信号量是否合法:入参: sem: 信号量地址 返回值:1合法,0非法 |
sal_arch_sem_wait |
等待信号量: sem:信号量地址 timeout:超时时间 返回值:(~0)为超时,其他等待时间 |
sal_mbox_new |
创建mbox: mb:填充创建mbox地址 size:mbox大小 返回值:0成功,-1错误 |
sal_mbox_free |
销毁mbox: mb:mbox地址 返回值:无 |
sal_mbox_post |
向mbox发送数据: mb:mbox地址 msg:数据地址 返回值:空 |
sal_mbox_trypost |
向mbox发送数据,并返回是否成功: mb:mbox地址 msg:数据地址 返回值:0成功,-1失败 |
sal_mbox_valid |
检查mbox是否合法: mb:mbox地址 返回值:1合法,0非法 |
sal_arch_mbox_fetch
|
从mbox获取数据: mb:mbox地址 msg:用于填充数据地址 timeout:超时时间 返回:(~0)为超时,其他等待时间 |
sal_arch_mbox_tryfetch |
从mbox获取数据,并返回是否成功: mb:mbox地址 msg:用于填充数据地址 返回:0成功,-1失败 |
sal_mutex_new |
创建锁: mutex:用于填充创建锁地址 返回:0成功,-1失败 |
sal_mutex_lock |
上锁: mutex:锁地址 返回:无 |
sal_mutex_unlock |
解锁: mutex:锁地址 返回:无 |
sal_mutex_free |
销毁锁: mutex:锁地址 返回:无 |
sal_mutex_valid |
判断锁是否合法: mutex:锁地址 返回值:1合法,0非法 |
标准宏和结构体说明
hostent结构体定义
struct hostent {
char *h_name; /* 主机正式域名 */
char **h_aliases; /* 主机的别名数组 */
int h_addrtype; /* 协议类型,对于 TCP/IP 为 AF_INET */
int h_length; /* 协议的字节长度,对于 IPv4 为 4 个字节 */
char **h_addr_list; /* 地址的列表*/
#define h_addr h_addr_list[0] /* 保持向后兼容 */
};
sal_op_t结构体定义
typedef struct sal_op_s {
struct sal_op_s * next; /* 下一个sal_op_s */
char *version; /* 版本信息 */
char *name; /* 外接模组名称 */
/* 添加模组 */
int (*add_dev)(void*);
/* 模组初始化 */
int (*init)(void);
/* 创建TCP/UDP连接 */
int (*start)(sal_conn_t *c);
/* 向远端发送数据 */
int (*send_data)(int fd, uint8_t *data, uint32_t len,
char remote_ip[16], int32_t remote_port, int32_t timeout);
/* 主动接收远端数据 */
int (*recv_data)(int fd, uint8_t *data, uint32_t len,
char remote_ip[16], int32_t remote_port);
/* 域名解析 */
int (*domain_to_ip)(char *domain, char ip[16]);
/* 关闭远端连接 */
int (*finish)(int fd, int32_t remote_port);
/* SAL去初始化 */
int (*deinit)(void);
/* 数据接收回调 */
int (*register_netconn_data_input_cb)(netconn_data_input_cb_t cb);
} sal_op_t;
sal_conn_t结构体定义
typedef struct {
int fd; /* 所用socket文件描述符 */
CONN_TYPE type; /* 连接类型,如tcp_client、udp_client */
char *addr; /* 远端地址 */
int32_t r_port; /* 远端端口号 */
int32_t l_port; /* 本地端口号,如果不使用,设置为-1 */
uint32_t tcp_keep_alive; /* tcp连接保活时间 */
} sal_conn_t;
在文档使用中是否遇到以下问题
更多建议
匿名提交