Zephyr网络使用-socket使用

Creative Commons
本作品采用知识共享署名

本文通过实现一个简单的http get演示在Zephyr下应用网络.并附带说明如何手动配置DNS server.

早期的Zephyr应用可以通过net_context API使用网络,随着Zephyr对socket支持的不断完善现在zephyr已经推荐完全使用socket进行网络应用开发而不再使用net_context. 本文通过说明如何使用socket实现一个简单的http get从openweathermap获取指定城市的天气.

配置

启用的配置项及说明如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 启用网络
CONFIG_NETWORKING=y

# http要走tcp,因此启用ipv4和tcp
CONFIG_NET_IPV4=y
CONFIG_NET_TCP=y

# 要通过dhcp拿ip地址
CONFIG_NET_DHCPV4=y

# http get要通过域名去拿数据,socket只接收ip地址,因此要开启DNS
CONFIG_DNS_RESOLVER=y

# 开启socket
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y

zephyr内原生的socket API是以zsock_为前缀开头,例如connect就是zsock_connect,为了增加应用的可移植性,zephyr对其进行了封装,当开启CONFIG_NET_SOCKETS_POSIX_NAMES=y后zephyr就支持标准的socket API了.

代码

代码可以分为下面几部分

  1. 域名解析: getaddrinfo
  2. 创建连接: socket/connect
  3. 请求数据: send
  4. 接收响应: recv
  5. 关闭连接: close
    代码和说明如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    //请求天气的API域名
    #define HTTP_HOST "api.openweathermap.org"
    //请求天气的动作,下面的[app_id], 需要替换为你自己再openweathermap的app id
    #define HTTP_PATH "/data/2.5/weather?q=Chengdu,CN&units=metric&APPID=[app_id]"
    //http使用80口
    #define HTTP_PORT "80"

    // 组合成http get的字符串
    #define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n"

    #define SSTRLEN(s) (sizeof(s) - 1)
    #define CHECK(r) { if (r == -1) { printk("Error: " #r "\n"); return 0; } }

    static int http_get(void)
    {
    static struct addrinfo hints;
    struct addrinfo *res;
    int st, sock;

    //对api.openweathermap.org:80进行域名解析,解析的结构放到res中
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    st = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res);
    printk("st = %d\n", st);
    if (st != 0) {
    return 0;
    }

    //创建socket
    sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    CHECK(sock);
    printk("sock = %d\n", sock);

    //连接到http服务器
    CHECK(connect(sock, res->ai_addr, res->ai_addrlen));

    printk("Request:\n\n");
    //发送http get的请求
    CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0));


    printk("Response:\n\n");

    while (1) {
    //接收响应数据
    int len = recv(sock, response, sizeof(response) - 1, 0);

    if (len < 0) {
    printk("Error reading response\n");
    return 0;
    }

    if (len == 0) {
    break;
    }

    //将数据打印出来
    response[len] = 0;
    printk("%s", response);
    }

    printk("\n");
    //关闭连接
    (void)close(sock);

    return 0;
    }

上面代码执行后就可以在串口看到拿到带有http header的数据, 后面跟的json格式就是天气数据

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
Server: openresty
Date: Thu, 27 Oct 2022 06:23:34 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 468
Connection: close
X-Cache-Key: /data/2.5/weather?APPID=xxxxxxxxxxxxxxxxxxxx&q=chengdu,cn&units=metric
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST

{"coord":{"lon":104.0667,"lat":30.6667},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"base":"stations","main":{"temp":16.94,"feels_like":16.99,"temp_min":16.94,"temp_max":16.94,"pressure":1021,"humidity":88},"visibility":10000,"wind":{"speed":3,"deg":10},"clouds":{"all":75},"dt":1666851763,"sys":{"type":1,"id":9674,"country":"CN","sunrise":1666826049,"sunse
t":1666866053},"timezone":28800,"id":1815286,"name":"Chengdu","cod":200}

从上面的代码可以看出完全使用的标准的posix socket API没有zephyr的API,这意味着使用posix socket API编写的网络应用可以很轻松的移植到zephyr上。

关于手动DNS

当Zephyr使用DHCP拿到ip地址后会自动去配置DNS server,如果是手动设置ip地址,就需要通过代码手动去配置DNS,否则getaddrinfo将返回-3无法进行域名解析. 手动配置DNS server方法如下:

1
2
3
4
5
6
7
8
9
//创建dns server list, 注意,一定要以NULL作为list的结尾
char *dns1 = "192.168.137.1";
char *dns2 = "8.8.8.8";
char *server_list[] = {dns1, dns2, NULL};

//配置DNS server
int status = dns_resolve_reconfigure(dns_resolve_get_default(),
server_list,
NULL);

小节

Zephyr网络使用-Wifi控制Zephyr网络使用-IP配置两篇文章加上本文,演示了从物理连接,协议栈配置启用,网络应用三个层次的配置和使用,涵盖了zephyr网络使用的一个基本过程,其它的网络应用和配置都可以以此为基础展开。