您好,登錄后才能下訂單哦!
這篇文章主要講解了“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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。