当前位置: 首页 > article >正文

嵌入式八股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
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结构体中包括数据的发送函数等
    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协议封包–>底层网卡驱动把数据发送输出
4. LWIP参数配置?–lwipopts.h
根据自己的实际需求去配置了
比如是否启用哪些协议 / 堆栈内存的大小 / 是否需要硬件校验
5. 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
      int socket(int domain, int type, int protocol);
      
      1. 设置端口复用(可选):允许多个进程或线程共享同一端口号进行通信的技术 — 提高服务器并发能力,防止端口资源耗尽
      2. 使用函数bind()绑定IP地址,端口等信息到socket上,设置全通规则
          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()接收客户端上来的连接
      int listen(int sockfd, int backlog);  //backlog等待队列的长度
      
      1. 使用函数send()和recv(),或者read()和write()收发数据
      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()连接服务器
      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()
      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 的协同处理

http://www.kler.cn/a/610823.html

相关文章:

  • 自动化框架的设计与实现
  • 单例模式在Python中的实现和应用
  • 【每日论文】MetaSpatial: Reinforcing 3D Spatial Reasoning in VLMs for the Metaverse
  • GitLab 部署说明
  • AI比人脑更强,因为被植入思维模型【21】冯诺依曼思维模型
  • 6.4 模拟专题:LeetCode1419.数青蛙
  • Linux网站搭建(新手必看)
  • 基于k3s部署Nginx、MySQL、PHP和Redis的详细教程
  • 深度学习(practice) Note.2
  • idea 没有 add framework support(添加框架支持)选项
  • matplotlib——南丁格尔玫瑰
  • 2.Excel :快速填充和拆分重组
  • 自动驾驶VLA模型技术解析与模型设计
  • Kotlin的语言特性及使用场景
  • DDD领域驱动设计详解-Java/Go/JS/Python语言实现
  • 一周掌握Flutter开发--7、包管理
  • 【自学笔记】ELK基础知识点总览-持续更新
  • 2、pytest核心功能(进阶用法)
  • QT Quick(C++)跨平台应用程序项目实战教程 4 — QML基本使用方法
  • fastapi下载图片