嵌入式八股RTOS与Linux---网络系统篇

前言

关于计网的什么TCP三次握手 几层模型啊TCP报文啥的不在这里讲,会单独分成一个计算机网络模块

这里主要介绍介绍lwip和socket

FreeRTOS下的网络接口--移植LWIP

实际上FreeRTOS并不自带网络接口,我们一般会通过移植lwip协议栈让FreeRTOS可以通过网络接口收发数据,具体可看博客:
一文带你掌握LWIP

  1. LWIP是什么
      LWIP是一个在嵌入式领域应用的TCP/IP协议栈,除了TCP/IP外还能支持DNS,DHCP等应用。LWIP只需要十几KB的RAM和几十KB的ROM就能使用了
  2. 如何在RTOS移植LWIP
       移植lwip前 结合着OSI模型先来说说LWIP帮我们做了哪些工作
       当我们的应用想要发起数据传输的时候,LWIP帮我们完成了TCP报文封装(传输层)-->IP报文封装(网络层)-->IP地址找到MAC地址以及对应封装(APR协议--数据链路层) 我们需要做的就是把这个层层封装好的报文(p_buf链表)通过我们实现的网络驱动接口发送出去
  • step1 :编写 sys_arch.c文件
      首先我们的lwip在OS下至少需要三种东西:消息邮箱/信号量/线程创建
         可是问题是,如果我用FreeRTOS,这三东西是这些API,我用UCOSIII又是一套API,这可怎么办呢? 那lwip就把这些所有需要的操作抽象出来,然后根据不同的RTOS环境填空就好,这就是sys_arch.c做的工作,我们要去自己写sys_arch的API
C 复制代码
err_t
sys_mutex_new(sys_mutex_t *mutex)
{
  LWIP_ASSERT("mutex != NULL", mutex != NULL);

  mutex->mut = xSemaphoreCreateRecursiveMutex();
  if(mutex->mut == NULL) {
    SYS_STATS_INC(mutex.err);
    return ERR_MEM;
  }
  SYS_STATS_INC_USED(mutex);
  return ERR_OK;
}
void
sys_mutex_lock(sys_mutex_t *mutex)
{
  BaseType_t ret;
  LWIP_ASSERT("mutex != NULL", mutex != NULL);
  LWIP_ASSERT("mutex->mut != NULL", mutex->mut != NULL);

  ret = xSemaphoreTakeRecursive(mutex->mut, portMAX_DELAY);
  LWIP_ASSERT("failed to take the mutex", ret == pdTRUE);
}
err_t
sys_sem_new(sys_sem_t *sem, u8_t initial_count)
{
  LWIP_ASSERT("sem != NULL", sem != NULL);
  LWIP_ASSERT("initial_count invalid (not 0 or 1)",
    (initial_count == 0) || (initial_count == 1));

  sem->sem = xSemaphoreCreateBinary();
  if(sem->sem == NULL) {
    SYS_STATS_INC(sem.err);
    return ERR_MEM;
  }
  SYS_STATS_INC_USED(sem);

  if(initial_count == 1) {
    BaseType_t ret = xSemaphoreGive(sem->sem);
    LWIP_ASSERT("sys_sem_new: initial give failed", ret == pdTRUE);
  }
  return ERR_OK;
}
  • step2: 实现底层网卡驱动程序
    这个就得我们根据硬件自己编写了

  • step3: 分配/设置/注册一个netif结构体
    netif结构体是吧我们的网卡驱动程序和lwip链接起来的关键,netif结构体中包括数据的发送函数等

    C 复制代码
    struct netif {
        struct netif *next;		// 以链表形式方便管理
        ip_addr_t ip_addr;		// 本地ip地址
        ip_addr_t netmask;		// 子网掩码
        ip_addr_t gw;				// 网关
        netif_output_fn output;  			// 供IP层封装完成后调用 一般就用 etharp_output()
        netif_linkoutput_fn linkoutput;	// ethernet_output()结束封装包后调用, 用于发送数据包
        netif_input_fn input;				// 用于向上层协议提交数据包
        
        // 以下是各种call_back没用上 直接不展示了
        netif_status_callback_fn status_callback;
        .....
        u16_t mtu;							// 最大传输字节 mtu = 1500一般
        u8_t hwaddr[NETIF_MAX_HWADDR_LEN];	// mac地址
        u8_t hwaddr_len;						// mac地址长度
        u8_t flags;							// 网卡的状态
        void * state;							// 私有数据 看自己怎么用
    };

我们需要配置好这些参数的内容 然后通过netif_setup来使能这个网卡

为什么会有多个netif?--IP协议会根据ip_route函数去找到最合适的netif把数据发送出去,不过一般来说只有一个网卡啦

具体如何初始化这个网卡的,可以看我上面提到的博客

  • step4: 初始化LWIP的核心线程
    tcpip_init()函数
  • step5: 配置lwip协议栈 lwip的参数(lwipopts.h )
  1. LWIP数据接收/发送过程?

接收过程: 底层网卡通过DMA/中断收到数据-->把数据转成p_buf结构体-->调用netif->input提交给上层协议栈-->LWIP的核心线程会来处理这个数据的

发送过程: 应用层发起操作-->TCP协议封包-->IP协议封包并找到最合适的netif结构体-->ARP协议封包-->底层网卡驱动把数据发送输出

  1. LWIP参数配置?--lwipopts.h

根据自己的实际需求去配置了

比如是否启用哪些协议 / 堆栈内存的大小 / 是否需要硬件校验

  1. LWIP的几种API

LWIP有RAW API / NETCOON API / SOCKET API三种

  1. LWIP的内存管理?

    LWIP提供了两种内存管理方式: 堆内存管理和内存池内存管理 这俩中内存管理方式是可以共存的,也可以强行只用一种---(忽略标准库的malloc和free)

    内存池的使用范围:固定大小的场景,比如TCP/IP的首部用内存池就更快

    • 内存池的定义:实际上就是一个大数组--通过DECRLAR宏定义

    堆内存管理的使用: 灵活的大小,比如我们的数据包大小就是不确定的 通过堆内存管理算法分配--

    • 内存堆的定义:实际上也是一个大数组--通过DECRLAR宏定义

    • 如何两者都启用(默认就是)?或者只启用一种

Linux下的网络接口--Socket

  1. 请说一下socket网络编程中客户端和服务端用到哪些函数?
    • TCP服务器(Server)

      1. 使用函数socket()创建一个socket
      C 复制代码
      int socket(int domain, int type, int protocol);
      1. 设置端口复用(可选):允许多个进程或线程共享同一端口号进行通信的技术 --- 提高服务器并发能力,防止端口资源耗尽
      2. 使用函数bind()绑定IP地址,端口等信息到socket上,设置全通规则
      C 复制代码
          struct sockaddr_in serv_addr;
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_port = htons(8080);
          serv_addr.sin_addr.s_addr = INADDR_ANY;
          bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); // 绑定IP地址和端口号
      1. 使用函数listen()设置监听,使用函数accept()接收客户端上来的连接
      C 复制代码
      int listen(int sockfd, int backlog);  //backlog等待队列的长度
      1. 使用函数send()和recv(),或者read()和write()收发数据
      C 复制代码
      ssize_t send(int sockfd, const void *buf, size_t len, int flags);
      ssize_t recv(int sockfd, void *buf, size_t len, int flags);
      1. 关闭网络连接
    • TCP客户端(Client)

      1. 使用函数socket()创建一个socket
      2. 使用函数connect()连接服务器
      C 复制代码
      int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
      1. 使用函数send()和recv(),或者read()和write()收发数据
      2. 关闭网络连接
        UDP是基于无连接的协议,发送数据时不需要先建立连接,而是直接把数据发送过去
    • UDP服务器(Server)

      1. 使用函数socket()创建一个socket
      2. 使用函数bind() 绑定IP地址、端口等信息到socket上
      3. 收发数据,用函数recvfrom(),sendto()
      C 复制代码
      ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
               struct sockaddr *src_addr, socklen_t *addrlen);
      ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);
      1. 关闭网络连接close()
    • UDP客户端(Client)

      1. 使用函数socket()创建一个socket
      2. 使用函数recvfrom(),sendto()收发数据
      3. 关闭网络连接close()
  2. 网络字节序是大小端?
  • 大端字节序(Big Endian):最高有效位存于最低内存地址处,最低有效位存于最高内存处;
  • 小端字节序(Little Endian):最高有效位存于最高内存地址,最低有效位存于最低内存处

网络字节序时大端字节序

//将主机字节序转换为网络字节序

unit32_t htonl (unit32_t hostlong);

unit16_t htons (unit16_t hostshort);

//将网络字节序转换为主机字节序

unit32_t ntohl (unit32_t netlong);

unit16_t ntohs (unit16_t netshort);

  • 为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而sin_family 需不需要呢?
    sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family 域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序

3 Socket的阻塞和非阻塞模式

  • 阻塞模式
    调用 send()/recv() 时,若数据未就绪或缓冲区满,线程会挂起,直到操作完成
  • 非阻塞模式
    调用 send()/recv() 立即返回,通过错误码(如 EWOULDBLOCK)通知需重试
    需配合 I/O 多路复用(如 select()/poll()/epoll)实现高效事件驱动
    • 非阻塞下的Socket
      在非阻塞模式下,connect() 会立即返回 EINPROGRESS(而不会等三次握手完成再返回),此时需通过 select/poll 监听 Socket 的可写事件,再通过 getsockopt(SO_ERROR) 检查连接是否成功。关键点包括:严格错误检查、超时控制、与非阻塞 IO 的协同处理
相关推荐
音视频牛哥15 分钟前
Nginx RTMP DASH 模块分析 (ngx_rtmp_dash_module.c)
运维·nginx·大牛直播sdk·dash·nginx rtmp服务器·nginx dash·dash播放
G探险者17 分钟前
项目日志是否应该启用文件压缩?
运维·后端
Edward-tan32 分钟前
Django 生成 ssl 安全证书,切换 https、wss协议(daphne 、nginx)
运维·服务器·django
网安墨雨36 分钟前
网络安全(一):常见的网络威胁及防范
网络·安全·web安全
拖孩1 小时前
[特殊字符]我在 Chatterbox(话匣子)中 Websocket 的使用-上篇(基本介绍)
网络·websocket·网络协议
字节程序员1 小时前
Jenkins 持续集成:Linux 系统 两台机器互相免密登录
linux·软件测试·ci/cd·jenkins
试水年华1 小时前
鸿蒙Next-集成HmRouter的路由模式
linux·运维·服务器·华为·harmonyos·ark-ts语言
Bigger1 小时前
Mac 命令行及 Linux 使用指南与示例
linux·前端·命令行
Karl_wei1 小时前
Flutter Linux应用初探
linux·前端·flutter
IT缺脑干1 小时前
CentOS 7 挂载与卸载文件系统
linux·运维·centos