首页 TCPIP协议规范及UIP处理流程

TCPIP协议规范及UIP处理流程

举报
开通vip

TCPIP协议规范及UIP处理流程目录 一、    简要历史    3 二、    TCP/IP协议族    3 2.1.    简介    3 2.2.    编址    3 2.2.1    物理地址    3 2.2.2    逻辑地址    4 2.2.3    端口地址    5 2.3.    分层数据包介绍    5 2.3.1    以太网帧    5 2.3.2    ARP报文格式    6 2.3.3    IP数据报格式    6 2.3.4    ICMP报文格式    7 2.3.5    IGMP报文格式    9 2...

TCPIP协议规范及UIP处理流程
目录 一、    简要历史    3 二、    TCP/IP 协议 离婚协议模板下载合伙人协议 下载渠道分销协议免费下载敬业协议下载授课协议下载 族    3 2.1.    简介    3 2.2.    编址    3 2.2.1    物理地址    3 2.2.2    逻辑地址    4 2.2.3    端口地址    5 2.3.    分层数据包介绍    5 2.3.1    以太网帧    5 2.3.2    ARP报文格式    6 2.3.3    IP数据报格式    6 2.3.4    ICMP报文格式    7 2.3.5    IGMP报文格式    9 2.3.6    UDP用户数据报首部格式    9 2.3.7    TCP报文段格式    10 2.4.    分层协议讲解    11 2.4.1    ARP和RARP    12 2.4.2    IP协议    13 2.4.3    ICMP协议    13 2.4.4    网际组管理协议(IGMP)    16 2.4.5    用户数据报(UDP)    17 2.4.6    传输控制协议(TCP)    18 三、    UIP处理流程    20 3.1.    简介    20 3.2.    层次结构    20 3.2.1    实现设备驱动与UIP对接需要的7个接口程序,定义在uip.h:    21 3.2.2    应用层要调用的函数,包括一些宏定义与函数,定义在uip.h:    24 3.2.3    UIP中所用到的主要结构体    27 3.2.4    uip的初始化与配置函数    31 3.2.5    Uip的主程序循环    32 3.2.6    主要的处理函数uip_process()    34 3.2.7    再来分析UIP_UDP_SEND_CONN,主要处理UDP报文的发送:    37 3.2.8    接下来,分析UIP_POLL_REQUEST    38 3.2.9    对定时器期满的处理流程UIP_TIMER    40 3.2.10    对UIP_UDP_TIMER的处理流程    41 3.2.11    原始套接字和原始线程    41 一、 简要历史 1973年,ARPANET核心组成员Vint Cerf 和 Bob Kahn 发表了一篇里程碑论文,阐述了实现分组的端到端交付的协议。这篇关于传输控制协议(TCP)的论文包括:封装、数据报,以及网关的功能。     后来,TCP被划分为两个协议:传输控制协议(TCP)和网际互联协议(IP)。IP处理数据报的路由选择,而TCP负责高层的一些功能,如分段、重装和差错检测。这个用来进行网际互联的协议后来就被称为TCP/IP。 二、 TCP/IP协议族 2.1. 简介 TCP/IP协议族由5层组成:物理层、数据链路层、网络层、运输层和应用层。前四层与OSI模型的前四层相对应,提供物理 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 、网络接口、网际互联、以及运输功能。而应用层与OSI模型中最高的三层相对应。 TCP/IP协议族中的各层包含了一些相对独立的协议。在物理层和数据链路层,TCP/IP并没有定义任何协议。在网络层TCP/IP支持网际互联协议(IP),而IP又由四个支撑协议组成:ARP、RARP、ICMP和IGMP。在传统上,TCP/IP协议族在运输层有两个运输协议:TCP和UDP,然而现在已经 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 出一个新的运输层协议SCTP以满足新的应用的需要。IP是主机到主机的协议,即把分组从一个物理设备交付到另一个物理设备。UDP和TCP是运输机协议,负责把报文从一个进程(运行着的程序)交付到另一个进程。 2.2. 编址 使用TCP/IP协议的互联网使用3个等级的地址:物理(链路)地址、逻辑(IP)地址以及端口地址。每一种地址属于TCP/IP体系结构中的特定层。 1 2 2.1 2.2 2.2.1 物理地址 物理地址也叫链路地址,是结点的地址,由它所在的局域网或广域网定义。物理地址包含在数据链路层使用的帧中。 以太网的地址是6字节(48位)长,通常用十六进制记法,如:07:01:02:01:2C:4B。以太网的地址共3种:单播、多播和广播。在单播地址中的第一个字节的最低位0;在多播地址中的第一个字节的最低位是1。广播地址是48个1。 2.2.2 逻辑地址 因特网的逻辑地址是32位地址,可以用来标志连接在因特网上的每个主机。在因特网上没有两个主机有相同的IP地址。同样,逻辑地址也可以是单播地址、多播地址和广播地址。 Internet被各种路由器和网关设备分隔成很多网段,为了标识不同的网段,需要把32位的IP地址划分成网络号和主机号两部分,网络号相同的各主机位于同一网段,相互间可以直接通信,网络号不同的主机之间通信则需要通过路由器转发。 把所有IP地址分为五类,如下图1所示: 图 2-1 A类 0.0.0.0到127.255.255.255 B类 128.0.0.0到191.255.255.255 C类 192.0.0.0到223.255.255.255 D类 224.0.0.0到239.255.255.255 E类 240.0.0.0到247.255.255.255 在分类编址的A类、B类、C类地址中,IP地址可划分为net-id(网络标识)和host-id(主机标识)。对于A类地址,1字节定义net-id而3字节定义host-id。对于B类地址,2字节定义net-id,2字节定义host-id。对于C类地址,3字节定义net-id而1字节定义host-id。D类地址和E类地址不划分net-id和host-id。 网络地址是一个地址块的第一个地址,向因特网的其余部分定义这个网络。路由器就是根据网络地址来选择分组的路由。若给出网络地址,我们就能够找出这个地址的类别、地址块以及这个地址块的地址范围。 这种划分 方案 气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载 有很大的局限性,它对网络的划分是flat的而不是层级结构(hierarchical)的。Internet上的每个路由器都必须掌握所有网络的信息,随着大量C类网络的出现,路由器需要检索的路由表越来越庞大,负担越来越重。 于是提出了新的划分方案,称为CIDR(Classless Interdomain Routing)。 网络号和主机号的划分需要用一个额外的子网掩码(subnet mask)来表示,而不能由IP地址本身的数值决定,也就是说,网络号和主机号的划分与这个IP地址是A类、B类还是C类无关,因此称为Classless的。这样,多个子网就可以汇总(summarize)成一个Internet上的网络。 IP地址与子网掩码做与运算可以得到网络号,主机号从全0到全1就是子网的地址范围。IP地址和子网掩码还有一种更简洁的表示方法,例如140.252.20.68/24,表示IP地址为140.252.20.68,子网掩码的高24位是1,也就是255.255.255.0。 目的地址为255.255.255.255,表示本网络内部广播,路由器不转发这样的广播数据包。 目的地址的主机号为全1,表示广播至某个网络的所有主机,例如目的地址192.168.10.255表示广播至192.168.10.0网络(假设子网掩码为255.255.255.0)。 2.2.3 端口地址 计算机是多进程设备,即可以在同一时间运行多个进程。因特网通信的最终目的是使一个进程能够和另一个进程通信。为了能够同时发生这些事情,需要有一种方法对不同的进程打上标号,就是说这些进程需要地址。 在TCP/IP体系结构中,给一个进程指派的标号叫做端口地址。TCP/IP中的端口地址是16位长,通常用10进制数表示。 2.3. 分层数据包介绍 2.3 2.3.1 以太网帧 图 2-2 目的地址(DA)  DA字段有6字节,是下一站的物理地址(也叫MAC地址)。 源地址(SA)    SA字段有6字节,是前一站的物理地址。 类型            类型字段有三种值,分别对应IP、ARP、RARP。 数据            携带从上层协议封装起来的数据。它的最小长度是46字节,最大长度是150 0字 个人自传范文3000字为中华之崛起而读书的故事100字新时代好少年事迹1500字绑架的故事5000字个人自传范文2000字 节。ARP、RARP的数据包长度不够46字节,要在后面补填充位。最大值1500称为以太网的最大传输单元(MTU),如果一个数据包从以太网路由到链路上,数据包的长度大于链路的MTU了,则需要对数据包进行分片 CRC              差错检测信息,4字节。 2.3.2 ARP报文格式 图 2-3 如上图3所示,ARP分组的格式如下: 硬件类型          16位字段,用来定义运行ARP的链路层网络的类型。以太网是类型1。 协议类型          16位字段,指要转换的地址类型。0x0800位IP地址。 硬件长度          8位字段,定义以字节为单位的物理地址长度。对以太网这个值为6。 协议长度          8位字段,定义以字节为单位的逻辑地址长度。对IPv4协议这个值是4。 操作              16位字段,定义分组的类型。为1表示ARP请求,为2表示ARP应答。 发送端硬件地址    可变长度字段,定义发送端的物理地址。 发送端协议地址    定义发送端的逻辑地址。 目标硬件地址      定义目标的物理地址。对于ARP请求报文,这个字段是全0,因为发送端不知道目标的物理地址。 目标协议地址      定义目标的逻辑(如,IP)地址。 3.4.1 IP数据报格式 图 2-4 如上图4所示,IP数据报的结构包括: 版本(VER)      这个4位字段定义IP协议的版本。 首部长度(HLEN)  这个4位字段定义IP首部总长度,以4字节为单位计算。当没有选项时,首部长度是20字节,这个字段的值是5(5*4=20)。当选项字段位最大值时,这个字段的值是15(15*4=60)。 服务类型(DS)    TOS位是4位子字段,共有5种不同的服务类型。 总长度            这个16位字段定义了以字节计的数据报总长度(首部加上数据)。要找出上层传来的数据长度,可以从总长度减去首部长度。总长度字段是16位,因此IP数据报的长度限制是65535(216 - 1)字节。 标识(Identification)  这个16位字段与源IP地址一起唯一地定义这个数据报。IP协议使用一个计数器来标志数据报,当IP协议发送数据时,就把这个计数器的当前值复制到标识字段中,并加1。当数据报被分片时,标识字段的值就复制到所有的分片中。换言之,所有的分片具有相同的标识号,即原始数据报的标识号。在终点重装数据报时,终点就知道所有具有相同标识号的分片必须组装成一个数据报。 标志(Flags)      3位字段。第一位保留。第二位为不分片位,为1表示不对数据报进行分片;为0表示在需要时对数据报进行分片。第三位为分片位,为1表示这个数据报不是最后的分片,在其后还有分片;为0表示这个数据报是最后的分片。 分片偏移(Fragment Offset)      这个13位字段表示该分片在整个数据报中的相对位置,以8字节为度量单位。 生存时间(TTL)    用来控制数据报所通过的最大路由跳数,这个生存时间的单位不是秒,而是跳(hop)。 协议              这个8位字段定义使用IP层服务的高层协议。如:TCP、UDP、ICMP和IGMP等。 检验和            IP分组中的检验和只在首部而不在数据部分进行。因为,所有将数据封装在IP数据报中的高层协议,都有覆盖整个分组的检验和;其次,,每经过一个路由器,IP数据报的首部就要改变一次,但数据部分不变。因此检验和只对发生变化的部分进行检验。 源地址            这个32位字段定义源点的IP地址。在IP数据报从源主机发送到目的主机的时间内,这个字段必须保持不变。 目的地址          这个32位字段定义了终点的IP地址。在IP数据报从源主机发送到目的主机的时间内,这个字段必须保持不变。 3.4.2 ICMP报文格式 类型              8位字段,定义ICMP报文的类型。ICMP报文的类型有:终点不可达、源点抑制、超时、参数问题、改变路由、回送请求或回答、时间戳请求或回答、地址掩码请求或回答、路由器询问和通告。 代码              8位字段,指明了发送这个特定报文类型的原因。 检验和(icmpchksum)  16位字段。在ICMP中,检验和的计算覆盖了整个报文(首部和数据)。 ICMP回送请求或回答报文头格式如下图5所示: 图2-5 ICMP终点不可达报文头格式如下图6所示: 图2-6 ICMP超时报文头格式如下图7所示: 图2-7 3.4.3 IGMP报文格式 图2-8 类型              8位字段,定义了查询、成员关系报告、退出报告三种报文类型,类型值分别为0x11、0x16、0x17。 最大响应时间      8位字段,定义了查询必须在多长时间内回答。它的值以十分之一秒位单位。在查询报文中这个值不是零,但在其他两种报文中则置为零。 检验和            16位字段,检验和在8字节的报文上计算。 组地址            在一般查询报文中这个字段的值为0,在特殊查询报文、成员关系报告报文以及退出报告报文中定义groupid(组多播地址)。 3.4.4 UDP用户数据报首部格式 图2-9 UDP数据报格式如上图9所示。 用户数据报有8个字节的固定首部。 源端口号          16位字段,定义源主机上运行的进程所使用的端口号。 目的端口号        16位字段,定义目的主机上运行的进程使用的端口号。 长度              16位字段,定义了用户数据报的总长度,首部加上数据。 检验和            16位字段,UDP的检验和包括三部分:伪首部、UDP首部以及从应用层来的数据。位首部是IP分组的首部的一部分,包括:源IP地址、目的IP地址、8位协议和16位UDP总长度。位首部可以保证在IP首部受到损伤时,用户数据报可以交付到正确的主机。协议字段的加入,可以确保这个分组是属于UDP而不是属于TCP。 3.4.5 TCP报文段格式 图2-10 如上图10所示,TCP报文段的结构包括: 源端口地址        这个16位字段定义发送报文段的应用程序端口号。 目的端口地址      这个16位字段定义了接收该报文段的应用程序端口号。 序号              这个32位字段定义了指派给本报文段第一个数据字节的一个号。为了保证连通性,要发送的每一个字节都要编号。序号告诉终点,这个序列中的哪一个字节是报文段中的第一个字节。在连接建立时,每一方使用随机数产生器产生初始序号(ISN)。 确认号            32位字段,定义了报文段接收端期望从对方接收的下一个序号。如果报文段的接收端成功地发送了对方发来的序号x,它就把确认号定义为x+1。 首部长度(tcpoffset)  4位字段,指出TCP首部共有多少个4字节字。即TCP数据在IP数据中的偏移大小。同IP首部长度,可以在5 至15之间。 保留位            该6位字段留待今后使用。 控制/标志位        该字段定义了6种不同的控制位或标志,在同一时间可设置一位或多位标志。 表2-1  控制字段各标志说明(从高位到低位) 标志 说      明 URG 紧急指针字段值有效 ACK 确认字段值有效 PSH 推送数据 RST 连接必须复位 SYN 在连接建立时对序号进行同步 FIN 终止连接 窗口值            该字段定义接收方必须维持的窗口值(以字节为单位)。注意,该字段是16位长,因此窗口值的最大长度为65535字节。这个值由接收端来确定,发送端必须服从接收端的决定。 检验和            这个16位字段包含检验和,TCP使用检验和是强制性的。 紧急指针          当紧急标志位置位时,这个16位字段才有效,这时的报文段中包括紧急数据。紧急指针定义了一个数,把这个数加到序号上就得出报文段数据部分中最后一个紧急字节。 选项              包括无操作(NOP)、最大报文段长度(MSS)、窗口扩大因子、时间戳等。 2.1. 分层协议讲解 总的来说,TCP/IP协议的多路选择过程可以表示为下图2-11: 图2-11 3.5 3.5.1 ARP和RARP 3.5.1.1 地址解析协议ARP 在任何时候,当主机或路由器有数据报要发送给另一个主机或路由器时,它必须有接收端的逻辑(IP)地址。但是IP数据报必须封装成帧才能通过物理网络。这就表示,发送端必须有接收端的物理地址,因此需要有从逻辑地址到物理地址的映射。 地址解析协议(ARP)用来把IP 地址与其物理地址联系起来。任何时候当主机或路由器需要找出这个网络上的另一个主机或路由器的物理地址时,它就发送ARP查询分组。这个分组包括发送端的物理地址和IP地址,以及接收端的IP地址。 因为发送端不知道接收端的物理地址,查询就在网络上广播。例如,数据包要发送给IP地址为192.168.0.1的主机,过程如下: 源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填FF:FF:FF:FF:FF:FF表示广播),目的主机接收到广播的ARP请求,发现其中的IP地址与本机相符,则发送一个ARP应答数据包给源主机,将自己的硬件地址填写在应答包中。 ARP报文格式如前所述。 ARP软件包由5个构件组成:     高速缓存表:     每台主机都维护一个ARP高速缓存表,由于高速缓存表的空间非常有限,所以缓存表中的表项有过期时间(一般为20分钟),如果20分钟内没有再次使用某个表项,则该表项失效,下次还要发ARP请求来获得目的主机的硬件地址。   队列:     队列用来在ARP试图解析硬件地址时保留IP分组。输出模块把未解析的分组发送到相应的队列,输入模块从一个队列中拿走一个分组,并连同解析出的物理地址一同发送给数据链路层来传输。   输出模块:     输出模块从IP软件等待IP分组。输出模块检查高速缓存表,寻找是否有某个项目对应于这个分组的目的IP地址。这个IP分组的目的IP地址必须与这个项目的协议地址相匹配。     输入模块:   输入模块一直等待,直到有ARP分组到达。检查高速缓存表,寻找对应这个ARP分组的项目。输入模块设置这个项目的超时时间TIME-OUT。若队列为空,则从相应队列中把分组一个接一个地取出,连同其硬件地址一起交给数据链路层来处理。   高速缓存控制模块:   负责维护高速缓存表,它周期性地逐项检查高速缓存表,判断有哪些项目到期,哪些队列需要撤销。 3.5.1.2 逆地址解析协议RARP     当一个主机知道自己的物理地址时,RARP可用来找出其逻辑地址。每一个主机或路由器都被指派一个或多个逻辑地址,这些地址与机器的物理地址无关。要创建IP数据报,主机或路由器要知道它自己的IP地址。可以使用RARP协议从物理地址得到逻辑地址。     知道物理地址后,先创建RARP请求,并在本地网络上广播。在本地网络上的另一个机器知道所有的IP地址,它就用RARP回答来响应。请求的机器必须运行RARP客户程序;而响应的机器必须运行RARP服务器程序。 3.5.1 IP协议 IP数据报的格式如前所述。 IP是不可靠的无连接协议,负责源点到终点的交付。 在IP层的分组叫做数据报。 数据链路层有自己的帧格式,在这个格式中有一个字段是“数据字段最大长度”。当数据报封装成帧时,数据报的总长度必须小于这个数据字段最大长度(MTU)。 对数据报进行分割,叫做分片。源站通常不对IP分组进行分片。运输层会进行分片工作,把数据划分成IP和在使用的数据链路层都可能接纳的大小。数据报在到达终点之前可以经过多次分片,可以被源主机或在其路径上任何路由器进行分片。然而数据报的重组却只能在目的主机上进行。 在IP分组中的检验和只在首部而不在数据部分心进行。因为,首先所有将数据封装在IP数据报中的高层协议,都有覆盖整个分组的检验和;其次,每经过一个路由器,IP数据报的首部就要改变一次,但数据部分不变。因此检验和只对发生变化的部分进行检验。 IP软件包包括8个构件:首部添加模块、处理模块、转发模块、分片模块、重装模块、路由表、MTU表以及重装表,还有输入和输出队列。 首部添加模块,从高层协议接收数据(连同其IP地址),添加IP首部后,把数据封装成IP数据报。 处理模块,从一个接口或从首部添加模块接收数据报,首先检查数据报是否为回环地址,还是这个分组已到达最后终点。 输入队列把从数据链路层或从高层协议发来的数据存放起来。 输出队列把要发送到数据链路层或高层协议的数据报存放起来,处理模块从中取出数据报,分片和重装模块则把这个数据报加入输出队列中。 路由表是在转发模块中使用的,用来确定分组的下一跳地址。 分片模块从转发模块接收IP数据报。转发模块给出IP数据报、下一站的IP地址。以及发送这个数据报所必须通过的接口号。分片模块使用MTU表以便找出对于特定接口的最大传送单元MTU。若数据报的长度大于MTU,则分片模块对数据报进行分片,为每一个分片添加首部,并把它们发送到ARP软件包进行地址解析和交付。 重装模块从处理模块接收已到达最终目的地的数据报分片。重装模块将未分片的数据报看成是属于仅有一个分片的数据报。使用重装表找出一个分片是属于哪一个数据报,将属于同一个数据报的各分片进行排序,并在所有分片到达时把它们重新组装成一个数据报。 3.5.2 ICMP协议 IP协议没有差错报告或差错纠正机制和管理查询机制。网际控制报文协议(ICMP)就是为了补偿这两个缺点而设计的。它是配合IP协议使用的。 ICMP本身是网络层协议,但是它的报文不是如设想的那样直接传送给数据链路层,而是首先要封装成IP数据报,再传送给下一层。 在IP数据报中的协议字段值是1就表示其IP数据是ICMP报文。 ICMP报文类型如下表2-2所示: 表2-2  ICMP报文类型 ICMP报文分为两大类:差错报告报文和查询报文。报文格式如前所述。 3.5.2.1 差错报告报文 差错报告报文报告当路由器或主机在处理IP数据报时可能遇到的一些问题。ICMP不能纠错,只能报告差错,差错纠正留给高层协议去做。 ICMP总是使用源IP地址把差错报文发送给数据报的源点。 一共有5种差错可处理:终点不可达、源点抑制、超时、参数问题以及改变路由。 ● 终点不可达报文    当路由器不能够给数据报找到路由或主机不能够交付数据报时,就丢弃这个数据报,然后这个路由器或主机就向发出这个数据报的源主机发回终点不可达报文。 ● 源点抑制          ICMP的源点抑制报文就是为了给IP增加一种流量控制而设计的。当路由器或主机因拥塞而丢弃数据报时,它就向数据报的发送端发送源点抑制报文。目的有二:第一,通知源点,数据报已被丢弃。第二,它警告源点,在路径中的某处出现了拥塞,因而源点需放慢发送过程。注意,必须为每一个丢弃的数据报向源点发送源点抑制报文。 ● 超时              超时有两种情况:第一,当路由器接收到生存时间字段值为零的数据报时,就丢弃这个数据报,并向源点发送超时报文;第二,当最后的终点在规定时间内没有收到所有的分片时,就丢弃已收到的分片,并向源点发送超时报文。 ● 参数问题 如果路由器或主机在数据报的首部中发现任何二义性,或在数据报的某个字段中缺少了某个值,就丢弃这个数据报,并发送参数问题报文。 ● 改变路由 路由器的路由选择是动态的,而主机为了提高效率,通常使用静态路由选择。当主机开始连网工作时,其路由表中的项目数很有限。它通常只知道默认路由器这一个路由器的IP地址,因此主机有可能会把某个数据报发送给一个错误的路由器。此时,收到这个数据报的路由器会把数据报转发给正确的路由器,并向主机发送改变路由报文,以更新主机中的路由表。 3.5.2.2 查询报文 查询报文都是成对出现的。 在这种类型的ICMP报文中,一个结点发送报文,然后由目的结点用特定的格式进行回答。 ● 回送请求和回答报文 为诊断目的而设计的。 主机或路由器可以发送回送请求报文给另一个主机或路由器。收到回送请求报文的主机或路由器产生回送回答报文,并将其返回给原来的发送者。 回送请求和回答报文可用来确定是否在IP这级能够通信。还可由主机使用,以检查另一个主机是否可达。在用户级,调用分组因特网搜寻器(ping)命令可做到这点。 ● 时间戳请求和回答 两个机器可使用时间戳请求和回答来确定IP数据报在这两个机器之间来往所需的往返时间。 ● 地址掩码请求和回答 主机通过向局域网上的路由器发送地址掩码请求报文来获得自己的掩码。若主机知道这个路由器的地址,则直接将请求发送给该路由器,若主机不知道,则广播这个请求报文。 路由器收到地址掩码请求报文,就以地址掩码回答报文进行响应,向主机提供所需的掩码。 ● 路由询问和通告 主机若想把数据发送给另一个网络上的主机,就需要知道连接到该网络上的路由器的地址。此外,这个主机还需要知道这些路由器是否正常工作。就可以通告路由询问和通告报文。 主机把路由器询问报文进行广播,收到询问的路由器就使用路由通告报文广播其路由选择信息。路由器发送通告报文时,不仅通告自己的存在,而且通告了它所知道的所有在这个网络上的路由器。 在ICMP中,检验和的计算覆盖了整个报文(首部和数据)。 3.5.3 网际组管理协议(IGMP) 网际组管理协议(IGMP)是与多播有关的一个必要的但不是充分的协议。IGMP并不是多播路由选择协议,而是个管理组成员关系的协议。每当主机需要加入或离开某个特定的多播群组时,该协议允许该主机去通知邻近的路由器。 该协议只用在主机与路由器之间的网络上。而且,协议只把计算机(不是应用进程)定义为群组成员。 如果在一个给定计算机上有多个进程要加入到一个多播群组,计算机必须要把接收到的每个数据报复制多个副本给每个进程。只有当最后一个进程离开群组时,计算机才利用IGMP通知本地的路由器,表明它不再是群组的成员了。IGMPv2有3种报文类型:查询、成员关系报告和退出报告。 IGMP可分为两个阶段: 第一阶段:当某个主机加入新的多播组时,该主机应向组播组的多播地址发送一个IGMP报文,声明自己要成为该组的成员。本地的多播路由器收到IGMP报文后,将组成员关系转发给因特网上的其他多播路由器。 第二阶段:因为组成员关系是动态的,因此本地多播路由器要周期性地探询本地局域网上的主机,以便知道这些主机是否还连续是组的成员。只要对某个组有一个主机响应,那么多播路由器就认为这个组是活跃的。但一个组在经过多次的探询后仍然没有一个主机响应,则多播路由器就认为本网络上的主机已经都离开这个组了因此就不再将该组的成员关系转发给其他的多播路由器。 IGMP报文格式如前所述。 IGMP协议的优点: ● 主机和多播路由器的所有通信使用IP多播,只要有可能,携带IGMP报文的数据报都使用硬件多播来传送。 ● 多播路由器在探询组成员关系时,只需要对所有多播组只发一个查询,而不是对每一个组发送一个查询,默认125S一次。 3.5.4 用户数据报(UDP) UDP数据报的格式如前所述。 UDP位于应用层和IP层之间,作为应用程序和网络操作的中介物。 IP是负责在计算机级的通信(主机到主机的通信),作为网络层协议,IP只能把报文交付给目的主机。但是,这是一种不完整的交付。这个报文还必须送交到正确的进程。UDP就是负责把报文交付给适当的进程。 完成进程到进程的通信最常用的方法是通过客户-服务器范例。在本地主机上叫做客户的进程主动发起请求, 远程主机上叫做服务器的进程被动地等待、接收和应答请求。客户端的IP地址和端口号唯一标识了该主机上的客户端进程,服务器的IP地址和端口号唯一标识了该主机上的服务端进。由于客户端是主动发起请求的一方,它必须知道服务器的IP地址和服务进程的端口号,所以,一些常见的网络协议有默认的服务器端口。 TCP/IP协议族中,端口号是在0~65535之间的整数。ICANN把端口号划分为3个范围:熟知端口号、注册端口号和动态(或专用)端口号。 熟知端口范围从0~1023;注册端口范围从1024~49151;动态端口范围从49152~65535. 已知UDP需要两个标识符,即IP地址和端口号,各用在一端以建立一条连接。一个IP地址和一个端口号合起来叫做套接字地址。这些信息是IP首部和UDP首部的一部分。 UDP提供物连接服务,即UDP发出的每一个用户数据报都是独立的数据报,每一个用户数据报可以走不同的路径到达目的进行。UDP缺少流量控制和差错控制。 要从一个进程把报文发送到另一个进程,UDP协议就要把报文进行封装和拆装。 ● 封装 当进程有报文要通过UDP发送时,它就把这个报文连同一对套接字地址以及数据的长度传递给UDP,加上UDP首部后,UDP把用户数据报连同套接字地址一起传递给IP。IP加上自己的首部,在协议字段使用值17,指出该数据是从UDP协议来的。再将IP数据报传递给数据链路层,数据链路层收到IP数据报后,再加上自己的首部传递给物理层。物理层将这些位编码为电信号或光信号,把它发送到远程机器。 ● 拆装 报文到达目的主机时,物理层对信号解码,将它变为位,传递给数据链路层。数据链路层使用这个首部(和尾部)检查数据。若无差错,则去掉首部和尾部,并把数据报传递给IP。IP软件进行检查,若无差错,就剥去首部,把用户数据报连同发送端和接收端的IP地址一起传递给UDP。UDP使用检验和对整个用户数据报进行检查。若无差错则剥去首部,把应用数据传递给接收进程。在需要回答收到的报文时,应把发送端的套接字地址一起传递给接收进程。     UDP软件包共包括5个构件:一个控制块表、若干个输入队列、一个控制块模块、一个输入模块和一个输出模块。在UDP中,队列是与端口相关联在一起的。这里的实现只创建与每一个进程相关联的输入队列,而不创建输出队列。 ● 控制块表 UDP控制块表来记录打开的端口。表中的每一个项目有最小的4个字段:状态(FREE或IN-USE)、进程ID、端口号以及相应的队列号。 ● 输入队列 使用了一组输入队列,每一个对应于一个进程。 ● 控制块模块 负责管理控制块表。当进程启动时,它就从操作系统请求得到一个端口号。操作系统把熟知端口号指派给服务器,而把短暂端口号指派给客户。进程把进程ID和端口号传递给控制块模块,以便在表中为这个进程创建一个项目。这个模块不创建队列。队列数字段值为零。 ● 输入模块 输入模块从IP接收用户数据报。它查找控制块表,查找具有和这个用户数据报同样端口号的项目。若找到这样的项目,模块就利用这项目中的信息把这个数据放入队列。若未找到这样的项目,它就产生ICMP“端口不可达”报文,并丢弃这个项目。 ● 输出模块 负责创建和发送用户数据报。 3.5.1 传输控制协议(TCP) TCP叫做面向连接的、可靠的运输协议。它提供进程到进程、全双工和面向连接的服务。TCP使用滑动窗口机制实现流量控制,来避免接收端因数据过多而过载;使用差错控制来提供可靠的服务。 两个设备之间使用TCP软件传送的数据单元叫做报文段,它有20~60字节的首部,首部后面是来自应用程序的数据。首部结构如前所述。 3.5.1.1 TCP连接 TCP的连接通常包括3个阶段:连接建立、数据传送和连接终止。 连接建立需要三向握手: ● 客户发送第一个报文段,SYN报文段,在这个报文段中只有SYN标志位置1.这个报文段的作用是使序号同步。SYN报文段是控制报文段,不携带任何数据,但是消耗一个序号。当数据传送开始时,每发送一个字节,序号应该加1. 在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况。 ● 服务器发送第二个报文段,SYN+ACK报文段,有两个标志位置1(SYN和ACK)。服务器使用这个报文段同步初始序号,以便从服务器向客户发送字节。使用ACK确认已从客户端收到了SYN报文段,确认号为客户端发送SYN报文段序号值加1. ● 客户发送第三个报文ACK,确认号为服务器发送报文段的序号值加1。该报文段的序号与SYN报文段使用的序号一样。 ACK报文段如果不携带数据就不消耗序号。 连接建立后,数据开始双向传送: 在数据传输过程中,ACK和确认序号是非常重要的,应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发。     以上情况只描述了最简单的一问一答的情景,事实上TCP协议为应用层提供了全双工(full-duplex)的服务,双方都可以主动甚至同时给对方发送数据。如果通讯过程只能采用一问一答的方式,收和发两个方向不能同时传输,在同一时间只允许一个方向的数据传输,则称为'''半双工(half-duplex)''',假设某种面向连接的协议是半双工的,则只需要一套序号就够了,不需要通讯双方各自维护一套序号了。 参加交换数据的双方中的任何一方都可以关闭连接, 连接终止的四向握手: ● 在正常情况下,客户机TCP接收到客户进程发来的关闭命令后,就发送第一个报文段——把FIN位置1。如果FIN报文段不携带数据,它消耗一个序号。同时更改状态为FIN_WAIT_1,关闭应用程序进程。 ● 服务器TCP在收到这个FIN报文段后,向自己对应的进程发送一个文件结束符EOF,同时更改状态为CLOSE_WAIT,并发送第二个报文段——ACK,以证实从客户端收到了FIN报文段。如果不携带数据,客户端接到ACK后状态更改为FIN_WAIT_2。 ● 服务器关闭应用程序进程,更改状态为LAST_ACK。并发送第三个报文段——FIN,若不携带数据,FIN消耗一个序号。 ● 客户TCP接收到FIN后,更改状态为TIME-WAIT,同时发送最后一个报文段——ACK,证实从TCP服务器收到了一个FIN报文段,该报文段的确认号等于从服务器发送的FIN报文段的序号加1 。 除上述的情况外,建立连接时,客户端和服务器端可以同时打开;关闭连接时,可以同时关闭或者进行三向握手。 TCP的状态机转换图如下所示: 图2-12 TCP的各种状态如下表2所示: 表2-3  TCP的各种状态 状态 说明 CLOSED     没有连接 LISTEN     收到了被动打开,等待SYN SYN-SENT     已发送SYN;等待ACK SYN-RCVD     已发送SYN+ACK;等待ACK ESTABLISHED     连接已建立;数据传送在进行 FIN-WAIT-1     第一个FIN已发送;等待ACK FIN-WAIT-2     对第一个FIN的ACK已收到;等待第二个FIN CLOSE-WAIT     收到第一个FIN,已发送ACK;等待应用程序关闭 TIME-WAIT     收到第二个FIN,已发送ACK;等待2MSL超时 LAST-ACK     已发送第二个FIN;等待ACK CLOSING     双方都已决定同时关闭 3.5.1.2 流量控制: 如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,而接收缓冲区的大小是固定的,就会丢失数据。TCP协议通过'''滑动窗口(Sliding Window)'''机制解决这一问题。 TCP在接收缓存上定义一个窗口,TCP发送数据的多少由滑动窗口协议定义。 为了完成流量控制,TCP使用滑动窗口协议。两个主机为向外通信(发送数据)各使用一个窗口,这个接收窗口覆盖了缓存的一部分。这个窗口有两个沿:一个在左边,另一个在右边。这个窗口叫做滑动窗口,因为左沿和右沿都可以滑动。窗口可以展开、合拢或缩回,这三种活动受接收端而不是发送端的控制(取决与网络上的拥塞状态),发送端必须听从接收端的命令。一般,窗口缩回必须避免。 TCP的滑动窗口是面向字节的,窗口大小取决于接收窗口(rwnd)和拥塞窗口(cwnd)中的较小值。 接收端在一段时间内不愿意从发送端接收任何数据时,可以发送rwnd为0的报文段来暂时关闭窗口,此时发送端窗口大小并非真正地缩回,而是暂停发送数据,直到一个新的通告收到为止。 3.5.1.3 差错控制: 差错控制由检验和、确认和超时来处理。受损伤的和重复的报文段要重传,重复的报文段要丢弃。数据可能不按序到达,接收端TCP把它们暂时存储下来,但TCP保证交付给进程的报文段都是按序的。 重传发生在:当重传超时(RTO)计时器时间到,或已到达3个重复的ACK报文段。 TCP使用拥塞机制来避免和检测网络中的拥塞。在拥塞控制中使用曼开始(指数增大)、拥塞避免(加法增大)和拥塞检测(乘法减小)等策略。 TCP在运行中使用4个计时器(重传计时器、持久计时器、保活计时器和时间等待计时器)。 一、 UIP处理流程 3.1. 简介 uIP协议栈去掉了完整的TCP/IP中不常用的功能,简化了通讯流程,但保留了网络通信必须使用的协议,设计重点放在了ARP/ IP/ICMP/UDP/TCP这些网络层和传输层协议上,保证了其代码的通用性和结构的稳定性。 由于uIP协议栈专门为嵌入式系统而设计,它具有如下的优点: ● 代码非常少,其协议栈代码不到6K; ● 占用的内存数非常少,RAM占用仅几百字节; ● 其硬件处理层、协议栈层和应用层共用一个全局缓存区,不存在数据的拷贝,且发送和接收都是依靠这个缓存区,极大的节省空间和时间。 ● 支持多个主动连接和被动连接并发; ● 通用性强,移植起来基本不用修改就可以通过; ● 对数据的处理采用轮循机制,不需要操作系统的支持。 3.2. 层次结构 uIP相当于一个代码库,通过一系列的函数实现与底层硬件和高层应用程序的通讯,对于整个系统来说它内部的协议组是透明的,从而增加了协议的通用性。 uIP协议栈与系统底层和高层应用之间的关系如图11所示: 硬件驱动 图3-1 4 4.5 4.6 4.6.1 实现设备驱动与UIP对接需要的7个接口程序,定义在uip.h: ● #define uip_input()        uip_process(UIP_DATA) ● #define uip_periodic(conn) do { uip_conn = &uip_conns[conn]; \                                 uip_process(UIP_TIMER); } while (0) ● #define uip_conn_active(conn) (uip_conns[conn].tcpstateflags != UIP_CLOSED) ● #define uip_periodic_conn(conn) do { uip_conn = conn; \                                     uip_process(UIP_TIMER); } while (0) ● #define uip_poll_conn(conn) do { uip_conn = conn; \                                 uip_process(UIP_POLL_REQUEST); } while (0) ● #define uip_udp_periodic(conn) do { uip_udp_conn = &uip_udp_conns[conn]; \                                 uip_process(UIP_UDP_TIMER); } while (0) ● #define uip_udp_periodic_conn(conn) do { uip_udp_conn = conn; \                                         uip_process(UIP_UDP_TIMER); } while (0) 还有一个变量,在接口中要用到: u8_t uip_buf[UIP_BUFSIZE+2];    对以上接口进行详细介绍: ♦ #define uip_input() 处理输入数据包。 当设备从网络上接收到数据包时调用此函数。在调用此函数之前,应将接收到的数据包内容存入uip_buf缓冲区,并将其长度赋给uip_len. 以太网内使用的uip需要用到ARP协议,因此在调用此函数之前先调用uip的ARP代码。 此函数返回时,如果系统有数据要输出,会直接将数据存入uip_buf,并将其长度值赋给uip_len。如果没有数据要发送,则uip_len值为0. 使用举例如下: uip_len = tapdev_read(uip_buf);     if(uip_len > 0)     {       if(BUF->type == htons(UIP_ETHTYPE_IP))       {           uip_arp_ipin();           uip_input();           if(uip_len > 0)         {             uip_arp_out();             tapdev_send(uip_buf,uip_len);           }       }       else if(BUF->type == htons(UIP_ETHTYPE_ARP))       {         uip_arp_arpin();           if(uip_len > 0)         {             tapdev_send(uip_buf,uip_len);           }       }   } ♦ #define uip_periodic(conn) 周期性的处理一个连接,需用到该连接的连接号,conn为将要轮询的连接号。 该函数对一个uip的TCP连接进行一些必要的周期性处理(如定时器、轮询等),它应该在周期性uip定时器期满消息到来时被调用。每一个连接都应该调用该函数,不论连接是否打开。 该函数返回时,若缓冲区内有需要被发送出去的数据包等待处理,就将uip_len的值置为大于零的数。以太网内使用的uip需要用到ARP协议,因此在调用驱动程序之前先调用uip的ARP代码uip_arp_out(),再调用设备驱动程序将数据包发送出去。 使用举例如下: for(uint32_t i = 0; i < UIP_CONNS; i++)       {           uip_periodic(i);         if(uip_len > 0)         {           uip_arp_out();           tapdev_send(uip_buf,uip_len);         }       } ♦ #define uip_conn_active(conn) ♦ #define uip_periodic_conn(conn) 对一个连接进行周期性处理,需用到指向该连接结构体的指针。 该函数与uip_periodic执行的操作是相同的,不同之处在于传入的参数是一个指向uip_conn结构体的指针。 此函数可用于对某个连接强制进行周期性处理。 ♦ #define uip_poll_conn(conn) 请求对特定连接进行轮询。 该函数功能与uip_periodic()相同,但是不执行任何定时器处理。通过轮询从应用程序得到新数据。 ♦ #define uip_udp_periodic(conn) 周期性处理连接号指定的连接。 此函数基本上与uip_periodic()相同,区别在于这里处理的是UDP连接。其调用方式也与uip_periodic()类似: for(i = 0; i < UIP_UDP_CONNS; i++) {         uip_udp_periodic(i);         if(uip_len > 0) {           uip_arp_out();           tapdev_send();         }       } ♦ #define uip_udp_periodic_conn(conn) 周期性处理一个UDP连接,需用到指向该连接结构体的指针。 此函数功能与uip_periodic_conn()相同,只是用来处理的是UDP连接。 ♦ u8_t uip_buf[UIP_BUFSIZE+2]; uip数据包缓冲区,长度固定。 Uip_buf数组用于存放接收、发送的数据包。设备驱动程序应将接收到的数据放入缓冲区。发送数据时,设备驱动程序从缓冲区中读取链路层的首部和TCP/IP首部。链路层头的大小在UIP_LLH_LEN中定义。 注:应用程序数据无需放入这个缓冲区中,而是需要设备驱动程序从uip_appdata指针所指的地方读取数据。 u16_t  uip_len; 全局变量,uip_buf缓冲区中数据包的长度。 当网络设备驱动调用uip输入函数时,uip_len要被设为传入数据包的大小。 当发送数据包时,设备驱动程序通过这个变量来确定要发送的数据包的大小。 4.3.1 应用层要调用的函数,包括一些宏定义与函数,定义在uip.h: 4.3.1.1 宏定义: ● #define uip_outstanding(conn) ((conn)->len) ● #define uip_datalen()      uip_len ● #define uip_urgdatalen()    uip_urglen ● #define uip_close()        (uip_flags = UIP_CLOSE) ● #define uip_abort()        (uip_flags = UIP_ABORT) ● #define uip_stop()          (uip_conn->tcpstateflags |= UIP_STOPPED) ● #define uip_stopped(conn)  ((conn)->tcpstateflags & UIP_STOPPED) ● #define uip_restart()        do { uip_flags |= UIP_NEWDATA; \                   uip_conn->tcpstateflags &= ~UIP_STOPPED; \                       } while(0) ● #define uip_udpconnection() (uip_conn == NULL) ● #define uip_newdata()  (uip_flags & UIP_NEWDATA) ● #define uip_acked()  (uip_flags & UIP_ACKDATA) ● #define uip_connected() (uip_flags & UIP_CONNECTED) ● #define uip_closed()    (uip_flags & UIP_CLOSE) ● #define uip_aborted()    (uip_flags & UIP_ABORT) ● #define uip_timedout()    (uip_flags & UIP_TIMEDOUT) ● #define uip_rexmit()    (uip_flags & UIP_REXMIT) ● #define uip_poll()      (uip_flags & UIP_POLL) ● #define uip_initialmss()            (uip_conn->initialmss) ● #define uip_mss()            (uip_conn->mss) ● #define uip_udp_remove(conn) (conn)->lport = 0 ● #define uip_udp_bind(conn, port) (conn)->lport = port ● #define uip_udp_send(len) uip_send((char *)uip_appdata, len) 4.3.1.2 函数: ● void uip_listen(u16_t port); ● void uip_unlisten(u16_t port); ● struct uip_conn *uip_connect(uip_ipaddr_t *ripaddr, u16_t port); ● void uip_send(const void *data, int len); ● struct uip_udp_conn *uip_udp_new(uip_ipaddr_t *ripaddr, u16_t rport); 4.3.1.3 对以上函数进行详细介绍: ♦ #define uip_outstanding(conn) 检查一个连接是否有特殊的(例如,未答复的)数据。 conn为指向该连接结构体的指针。 ♦ #define uip_datalen() uip_appdata缓冲区中,当前可用的传入数据的长度。必须先调用uip_data()查明是否有当前可用的传入数据。 ♦ #define uip_urgdatalen() 所有到达连接的缓冲区外的(紧急数据)数据长度。要使用此宏,应配置UIP_URGDATA宏为真。 ♦ #define uip_close() 此函数会以一种谨慎的方式关闭当前连接。 ♦ #define uip_abort() 中止(重置)当前连接,多用于出现错误导致无法使用uip_close()的场合。 ♦ #define uip_stop() 告诉发送主机停止发送数据。该函数会关闭接收者的窗口,以停止从当前连接接收数据。 ♦ #define uip_stopped(conn) 找出当前连接先前是否已经被uip_stop()停止了。 ♦ #define uip_restart() 如果连接先前被uip_stop()停止了,该函数会重启连接。接收者的窗口会被重新打开,并从当前连接开始接收数据。 ♦ #define uip_udpconnection() 检查当前连接是否是一个UDP连接。 ♦ #define uip_newdata() 如果uip_appdata指针所指之处有新的应用数据,就得到一个非零值。数据的大小可通过uip_len得到。 ♦ #define uip_acked() 若先前发送的数据得到了远程主机的确认信息,就得到一个非零值。这表示应用程序可以发送新数据了。 ♦ #define uip_connected() 如果当前与远程主机的连接建立,则得到一个非零值。这包括两种情形:连接被主动打开(uip_connect()),或者被动打开(uip_listen())。 ♦ #define uip_closed() 如果连接被远程主机关闭,则返回一个非零值。这时应用程序会做一些必要的清理工作。 ♦ #define uip_aborted() 如果连接被远程主机中止(重置),则返回一个非零值。 ♦ #define uip_timedout() 如果当前连接是因为多次重传而超时中止,则返回一个非零值。 ♦ #define uip_rexmit() 如果先前发送的数据在网络中丢失,应用程序需重传时,返回一个非零值。应用程序需调用uip_send()函数来重传与上一次所发送的完全一致的数据。 ♦ #define uip_poll() 解决连接是否由uip轮询的问题。 如果应用程序被调用的原因是当前连接因空闲太久而被uip轮询,则返回一个非零值。该轮询事件可以被用来发送数据,而无需等待远程主机发送数据。 ♦ #define uip_initialmss() 获得当前连接的初始最大报文段长度(MSS)。 ♦ #define uip_mss() 获得当前连接所能发送的最大报文段长度。该长度是由接收者的窗口大小和连接的MSS计算出来的。 ♦ #define uip_udp_remove(conn) 移除一个UDP连接。conn指向该连接的uip_udp_conn结构体。 ♦ #define uip_udp_bind(conn, port) 绑定一个UDP连接到本地端口。conn指向该连接的uip_udp_conn结构体,port为本地端口号,以网络字节序。 ♦ #define uip_udp_send(len) 在当前连接上发送长度为len的UDP数据包。该函数只有在答复一个UDP事件(轮询或有新数据)时才可被调用。数据必须提前放入uip_buf缓冲区中uip_appdata指针指向的地方。 ♦ void uip_listen(u16_t port); 开始监听指定的端口。 由于port应该为网络字节序,所以需要用到转换函数HTONS()或者htons()。 ♦ void uip_unlisten(u16_t port); 停止监听指定端口。 ♦ struct uip_conn *uip_connect(uip_ipaddr_t *ripaddr, u16_t port); 使用TCP协议连接到远程主机。 此函数用来与特定主机上的特定端口建立新的连接,它分配一个新的连接标识符,并将连接的状态转为SYN_SENT,将重传计时器设置为0.当该连接下次被周期性处理时,将会发送一个TCP 的SYN报文段。这个过程一般在uip_connet()被调用后0.5秒后完成。 该函数只有在主动打开配置项UIP_ACTIVE_OPEN被置1时可用。 ripaddr为远程主机的IP地址,port为网络字节序的端口号。该函数返回一个指向新连接的uip连接标识符的指针,当没有连接能被分配时返回NULL。 ♦ void uip_send(const void *data, int len); 在当前连接上发送数据。 该函数用来发送单个TCP数据报文段,只有为了事件处理而被uip调用的应用程序,才能发送数据。该函数能够发送的数据大小是由TCP所允许的最大数据量来决定的。uip会自动的分割数据,以保证发送出去的数据量是合适的。可以通过uip_mss()来查询uip实际将要发送的数据量。 该函数不保证发送的数据能到达目的地,如果数据在网络中丢失,uip_rexmit()事件将被置位。应用程序会被调用,并通过该函数来重新发送数据。 ♦ struct uip_udp_conn *uip_udp_new(uip_ipaddr_t *ripaddr, u16_t rport); 建立一个新的UDP连接。 该函数会自动为新连接分配一个不用的端口,但是在调用该函数之后,还可以通过uip_udp_bind()还重新选择一个端口。 ripaddr为远程主机(服务器)的IP地址,rport为远程主机网络字节序的端口号。该函数返回新连接的uip_udp_conn结构体指针,或者当没有连接能够分配时返回NULL。 4.3.1 UIP中所用到的主要结构体 ♦ Struct uip_conn {      //TCP连接         uip_ipaddr_t     ripaddr;  /**< The IP address of the remote host. */         u16_t lport;        /**< The local TCP port, in network byte order. */         u16_t rport;        /**< The local remote TCP port, in network byte order. */         u8_t rcv_nxt[4];    /**< The sequence number that we expect to receive next. */         u8_t snd_nxt[4];    /**< The sequence number that was last sent by us. */         u16_t len;        /**< Length of the data that was previously sent. */         u16_t mss;        /**< Current maximum segment size for the connection. */       u16_t initialmss;  /**< Initial maximum segment size for the connection. */   u8_t sa;          /**< Retransmission time-out calculation state variable. */     u8_t sv;          /**< Retransmission time-out calculation state variable. */     u8_t rto;          /**< Retransmission time-out. */     u8_t tcpstateflags;  /**< TCP state and flags. */     u8_t timer;        /**< The retransmission timer. */     u8_t nrtx;        /**< The number of retransmissions for the last segment sent. */     /** The application state. */     uip_tcp_appstate_t appstate; }; ♦ struct uip_udp_conn {      //UDP连接     uip_ipaddr_t ripaddr;  /**< The IP address of the remote peer. */     u16_t lport;          /**< The local port number in network byte order. */     u16_t rport;          /**< The remote port number in network byte order. */     u8_t  ttl;            /**< Default time-to-live. */     /** The application state. */     uip_udp_appstate_t appstate; }; ♦ struct uip_stats {      //统计信息结构体         struct {             uip_stats_t drop;    /**< Number of dropped packets at the IP layer. */             uip_stats_t recv;    /**< Number of received packets at the IP layer. */             uip_stats_t sent;    /**< Number of sent packets at the IP layer. */             uip_stats_t vhlerr;  /**< Number of packets dropped due to wrong IP version or header length. */             uip_stats_t hblenerr; /**< Number of packets dropped due to wrong IP length, high byte. */             uip_stats_t lblenerr; /**< Number of packets dropped due to wrong IP length, low byte. */             uip_stats_t fragerr;  /**< Number of packets dropped since they were IP fragments. */             uip_stats_t chkerr;  /**< Number of packets dropped due to IP checksum errors. */           uip_stats_t protoerr; /**< Number of packets dropped since they were neither ICMP, UDP nor TCP. */         } ip;                  /**< IP statistics. */         struct {             uip_stats_t drop;    /**< Number of dropped ICMP packets. */             uip_stats_t recv;    /**< Number of received ICMP packets. */             uip_stats_t sent;    /**< Number of sent ICMP packets. */             uip_stats_t typeerr; /**< Number of ICMP packets with a wrong type. */         } icmp;                /**< ICMP statistics. */         struct {             uip_stats_t drop;    /**< Number of dropped TCP segments. */             uip_stats_t recv;    /**< Number of recived TCP segments. */             uip_stats_t sent;    /**< Number of sent TCP segments. */             uip_stats_t chkerr;  /**< Number of TCP segments with a bad checksum. */             uip_stats_t ackerr;  /**< Number of TCP segments with a bad ACK    number. */             uip_stats_t rst;      /**< Number of recevied TCP RST (reset) segments. */             uip_stats_t rexmit;  /**< Number of retransmitted TCP segments. */             uip_stats_t syndrop;  /**< Number of dropped SYNs due to too few                 connections was avaliable. */             uip_stats_t synrst;  /**< Number of SYNs for closed ports,triggering a RST. */         } tcp;                  /**< TCP statistics. */ #if UIP_UDP         struct {             uip_stats_t drop;    /**< Number of dropped UDP segments. */ uip_stats_t recv;    /**< Number of recived UDP segments. */ uip_stats_t sent;    /**< Number of sent UDP segments. */ uip_stats_t chkerr;  /**< Number of UDP segments with a bad checksum. */ } udp;                  /**< UDP statistics. */ #endif /* UIP_UDP */ }; ♦ struct uip_tcpip_hdr {         /* IPv4 header. */         u8_t   vhl,     tos,     len[2],     ipid[2],     ipoffset[2],     ttl,     proto; u16_t   ipchksum; u16_t   srcipaddr[2], destipaddr[2]; /* TCP header. */ u16_t   srcport,         destport; u8_t   seqno[4],         ackno[4],         tcpoffset,         flags,         wnd[2]; u16_t   tcpchksum; u8_t   urgp[2]; u8_t   optdata[4]; } /*PACK_STRUCT_END*/; ♦ struct uip_icmpip_hdr {   /* IPv4 header. */     u8_t   vhl,             tos,             len[2],             ipid[2],             ipoffset[2],             ttl,             proto;     u16_t  ipchksum;     u16_t   srcipaddr[2],           destipaddr[2];   /* ICMP (echo) header. */     u8_t   type, icode;     u16_t   icmpchksum;     u16_t  id, seqno; } /*PACK_STRUCT_END*/; ♦ struct uip_udpip_hdr {   /* IP4 header. */     u8_t   vhl,             tos,             len[2],             ipid[2],             ipoffset[2],             ttl,             proto;     u16_t   ipchksum;     u16_t   srcipaddr[2],           destipaddr[2];   /* UDP header. */     u16_t   srcport,             destport;     u16_t   udplen;     u16_t   udpchksum; } /*PACK_STRUCT_END*/; ♦ struct timer {  //定时器结构体     clock_time_t     start;     clock_time_t     interval; }; ♦ struct uip_eth_hdr {  //以太网帧头     struct uip_eth_addr   dest;     struct uip_eth_addr   src;     u16_t               type; }/*PACK_STRUCT_END*/; ♦ struct psock {    //原始套接字结构体     struct pt     pt, psockpt;       /* Protothreads - one that's using the psock functions, and one that runs inside the psock functions. */     const u8_t   *sendptr;        /* Pointer to the next data to be sent. */     u8_t         *readptr;        /* Pointer to the next data to be read. */ char         *bufptr;          /* Pointer to the buffer used for buffering incoming data. */     u16_t       sendlen;        /* The number of bytes left to be sent. */     u16_t      readlen;        /* The number of bytes left to be read. */ struct psock_buf     buf;      /* The structure holding the state of the input buffer. */     unsigned int bufsize;          /* The size of the input buffer. */ unsigned char state;          /* The state of the protosocket. */ }; 4.3.1 uip的初始化与配置函数 4.3.1.1 初始化函数,定义在uip.h文件中: ● void uip_init(void); ● void uip_setipid(u16_t id); 4.3.1.2 配置函数,前6定义在uip.h文件中,最后一个定义在uip_arp.h文件中: ● #define uip_sethostaddr(addr) uip_ipaddr_copy(uip_hostaddr, (addr)) ● #define uip_gethostaddr(addr) uip_ipaddr_copy((addr), uip_hostaddr) ● #define uip_setdraddr(addr) uip_ipaddr_copy(uip_draddr, (addr)) ● #define uip_setnetmask(addr) uip_ipaddr_copy(uip_netmask, (addr)) ● #define uip_getdraddr(addr) uip_ipaddr_copy((addr), uip_draddr) ● #define uip_getnetmask(addr) uip_ipaddr_copy((addr), uip_netmask) ● #define uip_setethaddr(eaddr) do {uip_ethaddr.addr[0] = eaddr.addr[0]; \                               uip_ethaddr.addr[1] = eaddr.addr[1];\                               uip_ethaddr.addr[2] = eaddr.addr[2];\                               uip_ethaddr.addr[3] = eaddr.addr[3];\                               uip_ethaddr.addr[4] = eaddr.addr[4];\                                       uip_ethaddr.addr[5] = eaddr.addr[5];} while(0) 4.3.1.1 对以上函数的详细介绍: ♦ void uip_init(void); uip协议栈的初始化函数,用来加载uip协议栈。 ♦ void uip_setipid(u16_t id); 在加载uip时设置初始ip_id. ♦ #define uip_sethostaddr(addr) 设置该主机的IP地址。IP地址被表示成4字节的数组。Addr为指向uip_apaddr_t类型的IP地址的指针。 调用方式为:uip_ipaddr_t ipaddr; uip_ipaddr(ipaddr, 192,168,1,222); uip_sethostaddr(ipaddr); ♦ #define uip_gethostaddr(addr) 获得该主机的IP地址。 ♦ #define uip_setdraddr(addr) 设置默认路由器的IP地址。 ♦ #define uip_setnetmask(addr) 设置子网掩码。 ♦ #define uip_getdraddr(addr) 获得默认路由器的IP地址。Addr为uip_ipaddr_t类型的变量,用来存放默认路由器的IP地址。 ♦ #define uip_getnetmask(addr) 获得子网掩码。 ♦ #define uip_setethaddr(eaddr) 指定以太网MAC地址。 ARP代码需要知道以太网卡的MAC地址以回应ARP请求,并产生以太网帧头。该宏只能用来为ARP代码指明以太网的MAC地址,而不能改变以太网卡的MAC地址。 4.3.2 Uip的主程序循环 当所有的初始化、配置等工作完成后,uip就不停地在这个主循环里一直运行了,这个主循环实际上才真正说明了uip大部分时间里做了些什么。 基本处理流程图如下: 图3-2 while(1)   {   uip_len = tapdev_read(uip_buf);//首先uip要不停的读设备,看设备里有没有新的数据出现。实际上,驱动代码也只会在这个循环里看到,uip的其他代码是与设备无关的。     if(uip_len > 0)//如果len>0,表示设备里有新数据,下面对新数据进行处理。     {       if(BUF->type == htons(UIP_ETHTYPE_IP))//表示收到的是IP数据包       {           uip_arp_ipin();//ARP对收到的IP包进行处理,如果收到的IP包中的源IP地址是本地网络上主机的IP地址,则只进行ARP缓存表的更新(更新源IP地址对应的MAC地址)或者插入(若表中没有该IP地址项,插入源IP->MAC对应关系)操作           uip_input();//调用uip_process(UIP_DATA)对数据包进行处理           if(uip_len > 0)//表示数据包处理完后,立即产生了要发送的数据       {             uip_arp_out();//查看是否需要发送ARP请求,并为传出的ARP请求或IP包添加以太网头             tapdev_send(uip_buf,uip_len);//调用驱动程序发送缓存中的以太网帧       }     }       else if(BUF->type == htons(UIP_ETHTYPE_ARP))//表示要处理的是ARP包(请求或应答)       {             uip_arp_arpin();//直接进行ARP处理。若为应答,则从包中取出需要的MAC地址加入ARP缓存表;若为请求,则将自己的MAC地址打包成一个ARP应答,发送给请求的主机。             if(uip_len > 0)//表示有ARP应答要发送             {                 tapdev_send(uip_buf,uip_len);             }         }     }     else if(timer_expired(&periodic_timer))//设备里没有新数据,检查周期性定时器是否期满,若期满进行下面的处理。     {         timer_reset(&periodic_timer);//复位定时器,将开始时间设为当前时间,使重新开始计时         for(uint32_t i = 0; i < UIP_CONNS; i++)         {               uip_periodic(i);//对每一个连接进行周期性处理,这里实际上是调用uip_process(UIP_TIMER)进行处理             if(uip_len > 0)//表示缓存中有数据要发送             {                 uip_arp_out();//查看是否要发送ARP请求,为ARP请求或IP数据包添加以太网帧头                 tapdev_send(uip_buf,uip_len);             }         } #if UIP_UDP //如果有UDP连接,也对UDP连接进行处理         for(i = 0; i < UIP_UDP_CONNS; i++) {             uip_udp_periodic(i);             if(uip_len > 0) {                 uip_arp_out();                 tapdev_send();             }         } #endif       // Call the ARP timer function every 10 seconds. //检查ARP缓存表中的表项是否到期,若到期则将该表项清0(表项保存时间为10秒)         if(timer_expired(&arp_timer))         {             timer_reset(&arp_timer);             uip_arp_timer();         }     } }     由上可知,uip在不停的读设备、发数据,同时,当周期性定时器期满时,对定时器进行复位,并对连接和ARP表项进行周期性处理操作。 4.3.3 主要的处理函数uip_process() 该函数的处理过程总的来说分两种情况:有数据要处理;周期性定时器被激发。 4.3.3.1 数据处理又可细分为三种情况: ● #define UIP_DATA          1    /* Tells uIP that there is incoming data in the uip_buf                 buffer. The length of the data is stored in the global variable uip_len. */ ● #define UIP_POLL_REQUEST  3    /* Tells uIP that a connection should be polled. */ ● #define UIP_UDP_SEND_CONN 4    /* Tells uIP that a UDP datagram should be construc-    ted in the uip_buf buffer. */ 4.3.3.2 定时器处理分为两种情况: ● #define UIP_TIMER        2    /* Tells uIP that the periodic timer has fired. */ ● #define UIP_UDP_TIMER    5 ♦ 首先分析UIP_DATA,对接收到的IP数据报的处理,对应大循环里的uip_input(): { //首先进行基础的检查,判断IP数据报首部的合法性 //如果收到的数据报长度比IP首部中记录的长度要大,说明是数据报被补0了,将正确的长度赋给len;否则,IP数据报传送过程中被破坏了,将其丢弃。 //检查分片标志,若非第一个分片需进行重组;若没有定义分片操作,而分片偏移非0,则将该IP数据报丢弃 //若主机IP地址全0,若定义了ping操作,且高层协议为ICMP,则接收到的为回送请求报文,进入icmp_input进行处理;否则,丢弃该报文 //若主机IP地址非全0 ,IP广播被支持,且目的地址为全1,则接收到的应该为UDP广播报文, 重新设定检验和,并进入udp_input处理 //非以上两种情况,则比较目的IP地址与本主机IP地址是否相符,并检查检验和是否合法,不符则丢弃该报文 //若该IP数据报的上层协议为TCP,则进入tcp_input进行处理   //若上层协议为UDP,则进入udp_input进行处理 //若既非ping命令,非广播,非TCP报文,也非UDP报文,则应该为ICMP报文,若上层协议不是ICMP则将该IP数据报丢弃,并更新统计信息。 } ♦ icmp_input: { //首先接收到的ICMP报文数加1   //如果报文类型不是回送请求,就将报文丢弃,相应的统计数据加1。 //将目的IP地址存入 //将报文类型改为回送回答 //重新计算检验和   //交换源和目的IP地址 //将ICMP发送报文数加1,之后转到send进行回送回答报文 } ♦ Send: DEBUG_PRINTF("Sending packet with length %d (%d)\n", uip_len,           (BUF->len[0] << 8) | BUF->len[1]); //打印要发送了的IP数据报总长度UIP_STAT(++uip_stat.ip.sent);//将发送的IP数据报个数加1 uip_flags = 0; //将uip的标志位置0 return; //返回,并让被调用程序进行数据报发送(驱动程序tapdev_send()) ♦ udp_input: ♦ /* 我们不对UDP/IP头做任何更改,所有复杂的事情都交给UDP应用程序来处理。如果UDP应用程序设置了uip_slen, 表示有应用数据要发送*/ { //如果定义了检验和, //uip_len里存放应用数据长度             //获得应用数据的偏移地址             //判断检验和是否合法,不合法,将该数据包丢弃 //如果没有定义UDP检验和,则只取应用数据长度到uip_len //遍历UDP连接,如果本地端口号非零,且与目的端口号相等,同时远程端口号为0 或远程端口号与源端口号相等,且远程的IP地址为全0/全1或远程IP地址与源IP地址相同(有对应的UDP连接),就转到udp_found进行处 //没有找到对应的连接,则丢弃该UDP报文 } ♦ udp_found: UIP_STAT(++uip_stat.udp.recv); //接收到的UDP报文数据加1 uip_conn = NULL; uip_flags = UIP_NEWDATA; //表示远程主机的进程已向该主机进程发送了数据 uip_sappdata = uip_appdata = &uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN]; //获得发送数据的偏移地址 uip_slen = 0; UIP_UDP_APPCALL();// 调用对应的UDP应用程序接收数据 ♦ Tcp_input: { // TCP报文接收统计数据加1 //检查检验和是否合法,不合法就将该数据包丢弃 //检查所有活动连接,若找到对应的TCP连接,则转入found处理 //如果没有找到希望接收该报文的活动连接,要么该报文是旧的复制品,要么该报文是去往监听进程的SYN报文       //若没有设置SYN标志位,则该报文为旧的复制品,需要重置连接,转入reset处理。 //再来检查监听连接,进入found_listen处理 //若没有匹配的连接,则发送一个RST报文 } ♦ Found: { //若TCP标志为RST,就将连接状态改为CLOSED,将uip_flags设为UIP_ABORT,再调用UIP_APPCALL(),之后将产生的IP数据包丢弃。 //计算接收到的TCP数据长度。若接收到的报文段的序号非我们所希望接收到的下一个报文段,则转到tcp_send_ack发送包含正确序号的确认号ACK。 //若接收到的序号即为希望收到的报文段的序号,检查该报文段里是否有紧急数据,如果有,则更新上次发送报文段序号,计算往返时间RTT,并重置超时重传计数器和待确认数据长度。 //根据当前TCP连接的不同状态(UIP_SYN_RCVD、UIP_SYN_SENT、UIP_ESTABLISHED、UIP_LAST_ACK、UIP_FIN_WAIT_1、UIP_FIN_WAIT_2、UIP_TIME_WAIT、UIP_CLOSING),进行相应的处理。 } ♦ found_listen: uip_connr = 0; {     { //选择出关闭的连接或处于等待关闭且使用时间最久的旧连接作为新连接,并配置参数; //更新希望接收的下一个报文序号 } {       //若没有这样的连接则将该SYN报文丢弃 } //如果有选项的话,分析TCP MSS 选项(END、 NOOP、 MSS 等) } 4.3.1 再来分析UIP_UDP_SEND_CONN,主要处理UDP报文的发送: { //首先获得指向当前uip_conn的指针 //如果程序里要用到UDP协议,且连接标志位是UIP_UDP_SEND_CONN,则转到udp_send处理 } ♦ 接下来分析udp_send: udp_send: { //如果要发送的数据长度为0,即缓存中没有数据要发送,将数据包丢弃 //若缓存区有数据要发送,将uip_len设为要发送数据长度加上IP首部(20)和UDP首部(8)的长度。 注意: #define BUF ((struct uip_tcpip_hdr *)&uip_buf[UIP_LLH_LEN]) #define FBUF ((struct uip_tcpip_hdr *)&uip_reassbuf[0]) #define ICMPBUF ((struct uip_icmpip_hdr *)&uip_buf[UIP_LLH_LEN]) #define UDPBUF ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN]) //将uip_len的值放入IP首部中对应的位置,即总长度 //设置IP数据报在网络上的生存时间 //表示该IP数据报的高层协议类型为UDP。 //设定UDP数据报的长度域,为要发送的数据长度加上UDP头的长度。 //设置UDP检验和为0 //设置UDP数据报中的源和目的端口号为本地端口号和远程主机上进程端口号。 //设定IP数据报中源和目的IP地址,源IP地址为当前主机的IP地址,目的IP地址为存储在udp_conn的ripaddr中的值。 //取得应用数据的偏移地址 //如果用到UDP的检验和,重新计算检验和   //将udp发送数据包数目加1   //跳转到该处进行接下来的IP数据报的处理 } ♦ 再来分析ip_send_nolen: (略过IPV6部分) BUF->vhl = 0x45; BUF->tos = 0; BUF->ipoffset[0] = BUF->ipoffset[1] = 0; 设定IP数据报首部的内容,ipid为16位标识位 ++ipid; BUF->ipid[0] = ipid >> 8; BUF->ipid[1] = ipid & 0xff; /* Calculate IP checksum. */ BUF->ipchksum = 0; BUF->ipchksum = ~(uip_ipchksum()); DEBUG_PRINTF("uip ip_send_nolen: chkecum 0x%04x\n", uip_ipchksum()); UIP_STAT(++uip_stat.tcp.sent);  //将发送的TCP数据报个数加1 ♦ 接下来转入send,代码如前所示,准备进行数据包的发送。 4.3.2 接下来,分析UIP_POLL_REQUEST uip_sappdata = uip_appdata = &uip_buf[UIP_IPTCPH_LEN + UIP_LLH_LEN]; if(flag == UIP_POLL_REQUEST) {   if((uip_connr->tcpstateflags & UIP_TS_MASK) == UIP_ESTABLISHED && !uip_outstanding(uip_connr)) {//如果TCP连接的状态(低4位为state)为UIP_ESTABLISHED,且没有未确认数据 uip_flags = UIP_POLL;//将uip标志设定为POLL,对应用程序进行轮询,查看是否有应用数据要发送(uip_flags总是在调用应用程序之前被设置)     UIP_APPCALL();//调用宏定义的应用程序     goto appsend; } //否则,就将之前的数据丢弃   goto drop; } ♦ 再来分析appsend: if(uip_flags & UIP_ABORT) { //若应用程序希望中止连接,则将发送数据长度设为0,连接状态设为关闭,将TCP报文首部中的标志位设为TCP_RST | TCP_ACK。后转入tcp_send_nodata     uip_slen = 0;     uip_connr->tcpstateflags = UIP_CLOSED;     BUF->flags = TCP_RST | TCP_ACK;     goto tcp_send_nodata;//将uip_len设为IP和TCP首部长度之和       } if(uip_flags & UIP_CLOSE) { //若应用程序发起主动关闭请求,发送数据长度设为0,之前发送数据长度设为1,TCP连接状态变为UIP_FIN_WAIT_1     uip_slen = 0;     uip_connr->len = 1;     uip_connr->tcpstateflags = UIP_FIN_WAIT_1;     uip_connr->nrtx = 0;     BUF->flags = TCP_FIN | TCP_ACK;     goto tcp_send_nodata; } if(uip_slen > 0) {//若应用程序有数据要发送 if((uip_flags & UIP_ACKDATA) != 0) { //若已经收到发送数据的确认信息,则应用程序应该发送新的数据,而不是重传上次发送数据,将上次发送数据长度len置0       uip_connr->len = 0; }     if(uip_connr->len == 0) {  //取合适的发送数据长度       if(uip_slen > uip_connr->mss) {         uip_slen = uip_connr->mss;       }       uip_connr->len = uip_slen;     } else { //若存在未得到确认的数据,确定应用程序发送不超过上一次数据长度的数据(例如要进行重传时) } uip_connr->nrtx = 0;    4.3.1 对定时器期满的处理流程UIP_TIMER { //重组计数器清0 //对初始序号、uip_len、uip_slen进行设定 //若连接状态为UIP_TIME_WAIT或UIP_FIN_WAIT_2,且重传定时器刚启动,将连接置为CLOSED //若连接状态非前两种,也非UIP_CLOSED, //若存在未得到确认的已发送数据 //就将重传超时定时器RTO的值减1 //若RTO到期, //重传次数达到最大值, //或当前连接状态为UIP_SYN_SENT或UIP_SYN_RCVD且重传次数达到最大值, //将连接状态置为CLOSED //Uip_flags设为UIP_TIMEDOUT,表示连接因重传次数过多而被中止 //调用UIP_APPCALL() //向远程主机发送RST报文 //转到tcp_send_nodata //分情况进行重传,TCP重传统计值加1 UIP_SYN_RCVD:       重传SYNACK报文,tcp_send_synack UIP_SYN_SENT:       重传SYN报文,tcp_send_syn UIP_ESTABLISHED:   Uip_flags设为UIP_REXMIT, 调用UIP_APPCALL() 转到apprexmit进行重传处理 UIP_FIN_WAIT_1: UIP_CLOSING: UIP_LAST_ACK: 重传FINACK报文,tcp_send_finack。 //不需要进行重传,也连接状态为UIP_ESTABLISHED,则轮询应用程序来获得新数据 //转到appsend对接收到的新数据进行发送 //否则丢弃该消息  } 4.3.2 对UIP_UDP_TIMER的处理流程 { //若本地端口号已分配(非0), //将连接置为NULL //取得要发送数据在缓存区内的偏移地址 //将uip_len和uip_slen设为0 //对UDP应用程序UIP_UDP_APPCALL()进行轮询 //转到udp_send发送接收到的数据 //若本地没有建立UDP连接,则将该消息丢弃 } 4.3.3 原始套接字和原始线程 网络数据从用户进程传输到网络设备要经过四个层次,如图3—3所示: 图 3-3 一个套接字就是网络中的一个连接,和网络协议紧密地联系在一起,它是网络传输的入口。它是网络编程的入口,提供了大量的系统调用。 4.3.3.1 原始套接字和原始线程函数的宏定义 ● #define PSOCK_INIT(psock, buffer, buffersize)     psock_init(psock, buffer, buffersize) ● #define PSOCK_BEGIN(psock)                 PT_BEGIN(&((psock)->pt)) ● #define PSOCK_SEND(psock, data, datalen)            \             PT_WAIT_THREAD(&((psock)->pt), psock_send(psock, data, datalen)) ● #define PSOCK_SEND_STR(psock, str)              \ PT_WAIT_THREAD(&((psock)->pt), psock_send(psock, str, strlen(str))) ● #define PSOCK_GENERATOR_SEND(psock, generator, arg)    \             PT_WAIT_THREAD(&((psock)->pt),                    \               psock_generator_send(psock, generator, arg)) ● #define PSOCK_CLOSE(psock)                 uip_close() ● #define PSOCK_READBUF(psock)                \             PT_WAIT_THREAD(&((psock)->pt), psock_readbuf(psock)) ● #define PSOCK_READTO(psock, c)                \ PT_WAIT_THREAD(&((psock)->pt), psock_readto(psock, c)) ● #define PSOCK_DATALEN(psock)               psock_datalen(psock) ● #define PSOCK_EXIT(psock)                   PT_EXIT(&((psock)->pt)) ● #define PSOCK_CLOSE_EXIT(psock)                \             do {                                \                   PSOCK_CLOSE(psock);        \                   PSOCK_EXIT(psock);            \             } while(0) ● #define PSOCK_END(psock)                   PT_END(&((psock)->pt)) ● #define PSOCK_NEWDATA(psock)               psock_newdata(psock) ● #define PSOCK_WAIT_UNTIL(psock, condition)    \             PT_WAIT_UNTIL(&((psock)->pt), (condition)); ● #define PSOCK_WAIT_THREAD(psock, condition)  \             PT_WAIT_THREAD(&((psock)->pt), (condition)) 初始化: ● #define PT_INIT(pt)  LC_INIT((pt)->lc) 声明和定义: ● #define PT_THREAD(name_args) char name_args ● #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc) ● #define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \                   PT_INIT(pt); return PT_ENDED; } 阻塞等待: ● #define PT_WAIT_UNTIL(pt, condition)            \   do {                        \     LC_SET((pt)->lc);                \     if(!(condition)) {                \       return PT_WAITING;            \     }                        \   } while(0) ● #define PT_WAIT_WHILE(pt, cond)  PT_WAIT_UNTIL((pt), !(cond)) 分等级的原始线程: ● #define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) ● #define PT_SPAWN(pt, child, thread)        \   do {                        \     PT_INIT((child));                \     PT_WAIT_THREAD((pt), (thread));        \   } while(0) 退出和重启: ● #define PT_RESTART(pt)                \   do {                        \     PT_INIT(pt);                \     return PT_WAITING;            \   } while(0) ● #define PT_EXIT(pt)                \   do {                        \     PT_INIT(pt);                \     return PT_EXITED;            \   } while(0) 调用一个原始线程: ● #define PT_SCHEDULE(f) ((f) == PT_WAITING) ● #define PT_YIELD(pt)                \   do {                        \     PT_YIELD_FLAG = 0;                \     LC_SET((pt)->lc);                \     if(PT_YIELD_FLAG == 0) {            \       return PT_YIELDED;            \     }                        \   } while(0) ● #define PT_YIELD_UNTIL(pt, cond)        \   do {                        \     PT_YIELD_FLAG = 0;                \     LC_SET((pt)->lc);                \     if((PT_YIELD_FLAG == 0) || !(cond)) {    \       return PT_YIELDED;            \     }                        \   } while(0) 4.3.3.2 原始套接字函数详细介绍,定义在Psock.h中 ✓ #define PSOCK_INIT(psock, buffer, buffersize) 初始化一个原始套接字,必须在使用套接字之前被调用。该初始化同时为套接字指定了input buffer. Psocket (struct psock*)指向一个要被初始化的原始套接字,buffer(char*)指向原始套接字的输入缓存区,buffersize 为输入缓存区的大小。 ✓ #define PSOCK_BEGIN(psock) 在函数中启用原始套接字的原始线程。必须在它所使用的原始套接字在函数中被调用之前启用。 ✓ #define PSOCK_SEND(psock, data, datalen) 此宏通过原始套接字来发送数据。发送时,原始套接字的原始线程阻塞,直到所有的数据发送完毕并被确认已被远程TCP终端接收。 ✓ #define PSOCK_SEND_STR(psock, str) 通过原始套接字发送以0结束的字符串。Psock指向一个套接字,str指向要发送的字符串。 ✓ #define PSOCK_GENERATOR_SEND(psock, generator, arg) 调用一个函数产生数据,并发送数据。Generator指向产生数据的函数,arg为要传给函数的参数。 ✓ #define PSOCK_CLOSE(psock) 关闭一个套接字。该宏只能在包含该原始套接字的原始线程中被调用。Psock指向要关闭的套接字。 ✓ #define PSOCK_READBUF(psock) 该宏会一直阻塞等待数据,并将数据读入由PSOCK_INIT()所指定的input buffer,直到缓冲区被填满才停止读入数据。 ✓ #define PSOCK_READTO(psock, c) 该宏会一直阻塞等待数据,将数据读入由POSCK_INIT()指定的输入缓冲区,直到出现某一指定的字符时停止。 ✓ #define PSOCK_DATALEN(psock) 该宏返回之前被PSOCK_READTO() 或者 PSOCK_READBUF()读入的数据的长度。Psock指向保存这些数据的原始套接字。 ✓ #define PSOCK_EXIT(psock) 该宏停止一个原始套接字的原始线程,且该宏总是配合PSOCK_CLOSE()来一起完成操作。 ✓ #define PSOCK_CLOSE_EXIT(psock)    该宏选择一个原始套接字,并退出该原始套接字的原始线程。 ✓ #define PSOCK_END(psock) 此宏用来声明一个原始套接字的原始线程结束。必须与PSOCK_BEGIN()一起使用。 ✓ #define PSOCK_NEWDATA(psock) 该宏与PSOCK_WAIT_UNTIL()一起检查是否有数据到达该套接字。 ✓ #define PSOCK_WAIT_UNTIL(psock, condition) 该宏阻塞原始线程直到特定的条件为真。当线程等待时,PSOCK_NEWDATA()可以被用来检查是否有新数据到达。 4.3.1.1 原始线程函数详细介绍,定义在Pt.h中 ✓ #define PT_INIT(pt) 必须在执行原始线程之前初始化原始线程。 ✓ #define PT_THREAD(name_args) 该宏用来声明一个原始线程,所有的原始线程必须被该宏声明。Name_args为该原始线程执行的C函数的名字和参数。 ✓ #define PT_BEGIN(pt) 该宏用来声明一个原始线程的开始点。该宏应该被放在原始线程所在的函数的开始。所有在该宏之前的语句,在原始线程被调度时都将被执行。 ✓ #define PT_END(pt) 该宏用来声明一个原始线程结束。必须与PT_BEGIN()一起使用。 ✓ #define PT_WAIT_UNTIL(pt, condition) 阻塞原始线程直到特定条件出现。 ✓ #define PT_WAIT_WHILE(pt, cond) 当条件为真时一直阻塞等待。 ✓ #define PT_WAIT_THREAD(pt, thread) 阻塞并等到,直到子线程完成。 该宏调度一个子线程。当前的原始线程会阻塞直到该子线程完成。Thread为带参数的子线程。 ✓ #define PT_SPAWN(pt, child, thread) 产生一个子线程,并等待直到它退出。该宏只能在原始线程内使用。 ✓ #define PT_RESTART(pt) 该宏将阻塞,因为当正在运行的原始线程在PT_BEGIN()被调用时,该原始线程要重启执行。 ✓ #define PT_EXIT(pt) 该宏使原始线程退出。若果该原始线程由其他线程产生,父线程将不再阻塞,并可以继续执行。 ✓ #define PT_SCHEDULE(f) 该函数调度一个原始线程。当原始线程正在运行时,返回非零值,若该原始线程已经退出,则返回0 。 ✓ #define PT_YIELD(pt) 该宏使当前线程让位,并让其他处理过程先执行。 ✓ #define PT_YIELD_UNTIL(pt, cond) 该宏使当前线程让位,直到特定的值为真。 在真正调用应用程序的函数之前,若有新的连接建立,则先初始化应用程序状态结构里的套接字。 接下来,再调用真正进行通信管理的函数,进行数据通信。套接字函数必须返回整数值,但是不明确的返回——所有的返回值都被藏在PSOCK宏中。 例如:static int handle_connection(struct hello_world_state *s) {   PSOCK_BEGIN(&s->p);   …………………………   PSOCK_CLOSE(&s->p);   PSOCK_END(&s->p); }    
本文档为【TCPIP协议规范及UIP处理流程】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_014457
暂无简介~
格式:doc
大小:576KB
软件:Word
页数:49
分类:生活休闲
上传时间:2017-09-19
浏览量:49