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 配置驱动参数,例如串口通信串口号、波特率等,并添加设备

使用

添加该组件

在Drivers里面选择"External module enable"在External module Configurations里选择"SAL DEVICE"。在SAL device selection里选择具体的外接模组。例如,使用WiFi模组bk7231,则选择wifi.bk7231。

头文件

对外头文件代码位于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;