LiteOS学习笔记-5通信模组之LiteOS的SAL及socket编程

一、SAL套接字抽象层

SAL全称Socket Abstract Layer,即套接字抽象层,主要作用是对上层应用提供一层统一的 socket 编程接口,屏蔽底层网络硬件的差异。无论底层使用以太网+LwIP协议栈组合,还是使用ESP8266/M26+AT框架组合,经过SAL套接字抽象层之后,对用户提供的接口都是统一的,极大的提高了程序的可移植性。
LiteOS的SAL架构如下:
在这里插入图片描述
SAL框架的源码及其实现在SDK中的IoT_LINK\iot_link\network\tcpip目录:
在这里插入图片描述
除了sal文件夹之外,其余的文件夹分别对应着不同的sal实现,比如esp8266_socket对应的是基于AT框架和ESP8266的SAL实现。
SAL相关的头文件存放在IoT_LINK\iot_link\inc文件夹中。

  • sal.h:SAL头文件,使用时需包含
  • sal_imp.h:抽象接口定义头文件
  • sal_types.h:socket编程中涉及到的类型定义
  • sal_define.h:socket编程中涉及到的宏定义
  • link_endian.h:socket编程中的大小端字节序转换函数定义

二、Socket套接字简介

Socket概述

Socket称为套接字,本质上是一种文件描述符,所以socket通信的过程和操作文件的方法基本类似。
TCP/IP协议族的传输层中,分为有连接的,可靠的TCP传输方式,和无连接的,不可靠的UDP传输方式,所以Socket分为两种:

  • 流式Socket(SOCK_STREAM):提供可靠的、面向连接的通信流,使用TCP协议
  • 数据报Socket(SOCK_DGRAM):提供一种无连接的服务,使用UDP协议

Socket结构体

一个标准的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类型传递。

字节序转换函数

ip地址的转换

ip地址通常是一个字符串,比如"192.168.1.100",但是此处需要转换为一个uint32_t类型的数据,SAL提供了一个转换函数,在之前提到的link_endian.h文件中,函数如下:

//define the normal addres function
#define swaps(value) ((((value)&((uint16_t)0xff00))>>8)|(((value)&((uint16_t)0x00ff))<<8))

#define swapl(value)  ((((value)&((uint32_t)0xff000000))>>24)|(((value)&((uint32_t)0xff0000))>>8)|\
                      (((value)&((uint32_t)0xff00))<<8)|(((value)&((uint32_t)0xff))<<24))

字节序的转换

字节序分为大端存储和小端存储,为了保证统一性,屏蔽硬件差异,需要将ip地址和端口的值转换为网络字节序,SAL提供了本地字节序和网络字节序的互相转换函数,在link_endian.h文件中,其中h表示host主机,n表示network网络字节序

#ifndef htons
#define htons      htobes      //translate the host endian to network endian (2 Bytes)
#endif

#ifndef htonl
#define htonl      htobel      //translate the host endian to network endian (4 Bytes)
#endif

#ifndef ntohs
#define ntohs      htobes      //translate the network endian to host endian (2 Bytes)
#endif

#ifndef ntohl
#define ntohl      htobel      //translate the network endian to host endian (4 Bytes)
#endif

三、编程配置

使用ESP8266通信模组,需要开启使能AT框架和SAL。
在工程目录下的.config中手动配置开启驱动框架(串口使用)和AT框架,SAL框架,使能ESP8266,并配置ESP8266参数:

CONFIG_UARTAT_RCVMAX=2048
CONFIG_UARTAT_BAUDRATE=115200
CONFIG_UARTAT_DEVNAME="atdev"

CONFIG_AT_ENABLE=y
CONFIG_AT_DEVNAME="atdev"
CONFIG_AT_OOBTABLEN=6
CONFIG_AT_RECVMAXLEN=1024
CONFIG_AT_TASKPRIOR=10

CONFIG_DRIVER_ENABLE=y
CONFIG_TCPIP_AL_ENABLE=y
CONFIG_ESP8266_ENABLE=y
CONFIG_ESP8266_SSID="qiushi-dong"
CONFIG_ESP8266_PWD="67996909"
CONFIG_ESP8266_RCVCACHE=1024
CONFIG_ESP8266_CMDTIMEOUT=2000

如下两个宏须同时使能,否则会造成SAL无实现或ESP8266无接口。

CONFIG_TCPIP_AL_ENABLE=y
CONFIG_ESP8266_ENABLE=y

四、编程实例

SAL提供的Socket客户端编程API

建立socket
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则表示失败
连接服务器
int sal_connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
参数说明
sockfd创建成功的sockfd描述符
addrsockaddr结构体指针
addrlensockaddr结构体长度
发送数据
int sal_send(int sockfd,const void *buf,size_t len,int flags)
参数说明
sockfd创建成功的sockfd描述符
buf发送数据
len发送数据长度
flags发送或接收标记,一般都设为0
返回值成功发送数据长度

UDP使用

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
to目标sockaddr结构体指针
tolen目标sockaddr结构体长度
接收数据

非堵塞

int sal_recv(int sockfd,void *buf,size_t len,int flags)
参数说明
sockfd创建成功的sockfd描述符
buf接收数据缓冲区
len接收数据缓冲区长度
flags发送或接收标记,一般都设为0
int sal_recvfrom(int sockfd, void *mem, size_t len, int flags,
      struct sockaddr *from, socklen_t *fromlen)
参数说明
sockfd创建成功的sockfd描述符
mem接收缓冲区数据指针
size接收数据大小
flags发送或接收标记,一般都设为0
from源sockaddr结构体指针
fromlen源sockaddr结构体长度
关闭socket
int sal_closesocket(int sockfd)

基于SAL的TCP客户端编程

实现函数:

#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;
}

基于SAL的UDP客户端编程

实现函数:

#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;
}

总结

对比两个客户端实现可知,TCP是可靠的有连接的通信,需要建联后通信,所以创建socket后,需要先connect再进行数据收发;UDP是无连接的,创建socket之后,向指定地址进行收发操作即可。
至此,LiteOS的通信学习完成了,基本流程为通过串口驱动向模组发送AT指令进行操作,将此操作抽象为AT框架,这样,针对不同的指令解析,可以使用统一接口来操作。
针对不同模组进行网络通信时,需要使用套接字编程,liteOS同样提供了SAL抽象了统一接口,不同模组有不同的实现。基于此可以实现运输层的TCP或UDP编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值