LwIP协议栈 基础知识介绍
背景介绍
物联网(Internet of Things,简称IoT)是一种基于互联网的新型网络体系结构,它是一种将传感器、智能设备、云计算、大数据、人工智能等信息技术相结合的新型技术模式。物联网可以将物理世界中的任何物体与互联网连接起来。实现实时监测、智能控制、远程操作等功能,从而实现物与物、人与物之间的互联互通。
互联网的基础就是TCP/IP ,它是一个非常复杂的协议簇
网络协议简介
互联网对人类社会产生的巨大变革,大家是有目共睹的,它几乎改变了人类生活的方方面面。互联网通信的本质是数字通信,任何数字通信都离不开通信协议的制定,通信设备只有按照约定的、统一的方式去封装和解析信息,才能实现通信。互联网通信所要遵守的众多协议,被统称为TCP/IP簇。
TCP/IP协议中也有分层的概念,它是基于OSI七层模型,并将其抽象简化出来的,其中主要包括应用层,运输层,网络层,链路层,物理层。本课所提到的Lwip协议是一种轻量级的TCP/IP协议栈。它实现了TCP/IP协议簇中的多个层级,并不属于模型中的某一层,而是一个跨多层的协议栈!
OSI七层模型与TCP/IP四层模型的对应关系
OSI七层模型 | TCP/IP四层模型 | 对应网络协议 |
应用层(Application) | 应用层 | HTTP、TFTP, FTP, NFS, WAIS、SMTP |
表示层(Presentation) | Telnet, Rlogin, SNMP, Gopher | |
会话层(Session) | SMTP, DNS | |
传输层(Transport) | 传输层 | TCP, UDP |
网络层(Network) | 网络层 | IP, ICMP, ARP, RARP, AKP, UUCP |
数据链路层(Data Link) | 数据链路层 | FDDI, Ethernet, Arpanet, PDN, SLIP, PPP |
物理层(Physical) | IEEE 802.1A, IEEE 802.2到IEEE 802.11 |
LwIP概述
Light weight IP(LWIP)轻量化的 TCP/IP 协议,是瑞典计算机科学院(SICS)的 Adam Dunkels 开发的一个小型开源的 TCP/IP 协议栈。 LwIP 的设计初衷是:用少量的资源消耗(RAM)实现一个较为完整的 TCP/IP 协议栈, 在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。
LwIP既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在常见的嵌入式系统中使用。
LwIP的优缺点:
优点 | 缺点 |
资源开销低 | 性能限制,高性能网络处理能力表现不佳 |
几乎支持TCP/IP中所有常见的协议 | |
便于跨处理器和操作系统的移植 | 没有完整的实现TCP/IP协议栈 |
可配置性强 |
可以看到LwIP的功能强大,少数的几个缺点在资源本就受限且性能要求不严格的物联网设备中也不是不能接收!
LwIP官方文档中给出了三种编程接口:
我们将其优缺点进行对比总结如下:
- RAW/Callback API:
-
- 优点:有无操作系统均可使用,通过回调的方式可以提高应用程序的效率,节省内存开销
- 缺点:利用回调函数开发应用程序,实现过程比较复杂,可读性差;应用程序和内核程序处于同一线程,如果应用程序占用时间过长,会影响内核程序的执行,导致丢包等问题。
- NETCONN API:
-
- 优点:将内核程序和应用程序分离为独立的线程,LwIP内核线程只负责数据包的TCP/IP封装和拆封,应用程序负责处理应用数据,因此相对于RAW/Callback API的方式提高了协议栈对数据包的处理效率。
- 缺点:需要依赖操作系统,且IPC机制(信号量,邮箱)、任务切换需要耗费时间和内存,效率也比回调函数方式低。
- SOCKET API:
-
- 优点:基于NETCONN API 实现,进行了更高级的抽象,操作更加简单易用,有很好的移植性。
Socket英文原意是“孔”或者“插座”的意思,在网络编程中,通常将其称之为“套接字”,当前网络中的主流程序设计都是使用Socket进行编程的,因为它简单易用,更是一个标准。
LwIP中Socket编程步骤如下(客户端):
- Socket创建
- 设置服务器地址和端口
- 连接到服务器
- 数据发送与接收
LwIP Socket API详解
sockaddr_in结构体
该结构体包含了网卡的IP地址、端口号等重要的信息。
参数 | 含义 |
sin_port | 端口号信息 |
sin_addr | IP地址信息 |
sin_zero | 保留未用 |
socket()
这个函数的功能是向内核申请一个套接字,在本质上该函数其实就是对netconn_new()函数进行了封装,虽然说不是直接调用它,但是主体完成的工作就做了 netconn_new()函数的事情,且该函数本质是一个宏定义。
参数 | 含义 |
domain | 表示套接字使用的协议簇,该值始终为AF_INET |
type | 指定套接字使用的服务类型,可能的类型有三种 1.SOCK_STREAM(TCP协议) 2.SOCK_DGRAM (UDP协议) 3.SOCK_RAW(原始套接字) |
protocol | 指定套接字使用的协议 |
bind()
该函数的功能与netconn_bind()函数是一样的,用于服务器端绑定套接字与网卡信息, 实际上就是对netconn_bind()函数进行了封装,可以将一个申请成功的套接字与网卡信息进行绑定。
参数 | 含义 |
s | 表示要绑定的Socket套接字,即从socket()函数中返回的索引 |
name | 指向sockaddr结构体的指针,其中包含了网卡的各种信息 |
namelen | name结构体的长度 |
connect()
这个函数的作用与netconn_connect()函数的作用基本一致,因为就是封装了netconn_connect()函数。它用于客户端中,将Socket与远端IP地址、端口号进行绑定,在TCP客户端连接中,调用这个函数将发生握手过程(会发送一个TCP连接请求),并最终建立新的TCP连接;而对于UDP协议来说,调用这个函数只是在UDP控制块中记录远端IP地址与端口号,而不发送任何数据。
参数 | 含义 |
s | 表示要绑定的Socket套接字,即从socket()函数中返回的索引 |
name | 指向sockaddr结构体的指针,其中包含了网卡的各种信息 |
namelen | name结构体的长度 |
read()/recv()
read()与recv()函数的核心是调用recvfrom()函数,而recvfrom()函数是基于netconn_recv()函数来实现的, recv()与read()函数用于从Socket中接收数据,它们可以是TCP协议和UDP协议
参数 | 含义 |
men | 接收数据的缓存起始地址 |
len | 指定接收数据的最大长度 |
flags | 设置为0即可 |