概说《TCP/IP详解 卷2》第1章 概述

原文链接https://mp.weixin.qq.com/s/U9lmkrTbagpuKH-4bJcwTg

本文要点

  • 《TCP/IP详解 卷2》原书章节简介

  • 第一个网络编程示例

  • 网络层次结构

  • 描述符

  • mbuf

  • 网络输出与输入

  • 中断与并发

  • 源码组织结构

《TCP/IP详解 卷2》原书章节简介

    原书共32个章节(如图1所示),详细的介绍了TCP/IP协议栈的实现原理与细节。从网络层次结构来讲分为接口层、协议层、插口层;而协议来讲分有以太网、ARP、SLIP、IP、ICMP、IGMP、UDP、TCP等协议。

                                                           图1 《TCP/IP详解 卷2》章节概要

 

第一个网络编程示例

    按照学习一门新编程语言的惯例,首先学会第一个"hello world"程序,下面将介绍第一个网络程序入门示例,如图2所示。

                                                                                   图2 源码示例

    1. 创建一个数据报接口

19-20 socket函数创建一个UDP插口,并且给进程返回一个描述符,保存在变量sockfd中。

    2. 将服务器地址存入socketaddr_in

21-24 设置服务器ip地址(140.252.1.32)和端口(13),htons将一个主机字节序列(可能是低字节在后)转换为网络字节序列(高字节在后),不理解的可以搜索大端/小端相关问题。

    3. 发送数据报给服务器

25-27 程序调用sendto发送一个150字节的数据报给服务器,150字节的数据是未知的,因为服务器不管收到什么数据都返回当时时间和日期。

    4. 读取从服务器返回的数据报

程序通过调用recvfrom来读取从服务器发回的数据,然后输出。

 

网络层次结构

    内核网络代码组织成三层结构,如图3所示:

                                                                        图3 网络层次结构

插口层 是一个到下层协议相关层的协议无关接口。所有的系统调用从协议无关的插口层开始。例如:在插口层中的bind系统调用的协议无关代码包含几十行代码,它们验证的第一个参数是一个有效的插口描述符,第二个参数是一个进程中的有效指针。然后调用下层协议相关代码,协议相关代码可能包含几百行代码。

协议层 包括四种协议族(TCP/IP, XNS, OSI和Unix域)的实现。每个协议话可能包含自己的内部结构。例如,在Internet协议族中,IP网络层在最低层,TPC和UDP两个运输层在IP层之上。

接口层 包括网络设备通信的设备驱动程序,属于数据链路层。

 

描述符

    在图2的示例中,一开始调用socket,要求指定插口类型,Internet协议族为PF_INET和数据报插口为SOCK_DGRAM,组合成一个UDP协议插口。

     socket函数的返回值就是一个描述符,它具有其它unix描述符的所有特性:可以通过它调用read和write;可能调用fcntl来改变它的属性;还可以作为sento和recvfrom函数的第一个参数。

    下面将介绍描述符在一个进程中所处位置以及相关数据结构,如下图所求:

                                                                              图4 socket在进程中的位置

    每个进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每一项表示一个描述符项,例如图4中的p_fd,它指向进程的filedesc结构。在这个结构中包含fd_ofileflags(一个字符数组指针,每个描述符有一个描述符标志)和fd_ofiles(一个指向文件表结构的指针数组的指针)。

    项fd_ofiles指向的数据结构用*file{ }[ ]来表示。它是一个指向file结构的指针数组。这个数组及描述符标志数组的下标就是描述符本身:0,1,2等(好好理解一下)。

    结构file的成员f_type指示描述符的类型是DTYPE_SOCKET和DTYPE_VNODE。v-node是一个通用机制,允许内核支持不同类型的文件系统,本文中关心的不是v-node,因为TCP/IP插口的类型总是DTYPE_SOCKET。

    结构file的成员f_data指向一个socket结构或者vnode结构,根据描述符类型而定。成员f_ops指向一个有5个函数指针的向量。这些函数指针用在read、readv、write、writev、ioctl、select和close系统调用中,这些系统调用需要一个插口描述符或者非插口描述符。图4中fileops中的fo_read等函数表示结构成员名称,左边对应的soo_read/vn_read等函数表示结构成员的实际内容。

    接下来我们来看一下在执行一个网络系统调用时的大致查找流程(假设ftype为DTYPE_SOCKET):

  1. 当进程执行一个系统调用时,如sendto,内核从描述符值开始,使用fd_ofiles索引到file结构指针向量到描述符所对应的file结构,file结构指向socket结构,结构socket带有指向结构inpcb(Internet协议控制块)的指针。

  2. 当一个UDP数据报达到一个网络接口时,内核搜索所有UDP协议控制块,寻找一个合适的,至少要根据目标UDP端口号,可能还要根据目标IP地址、源IP地址和源端口号。一旦定位所找的inpcb,内核就能通过inp_socket指针来找到相应的socket结构。

    其中,成员inp_faddr和inp_laddr包含远程和本地IP地址,成员inp_fport和inp_lport包含远程和本地端口号。IP地址和端口号组成一个插口。图54中udp是一个全局结构,它是所有UDP PCB(协议控制块)组成的链表表头。成员inp_next和inp_pre把所有的UDP PCB 组成一个双向环形链表。

 

mbuf

    mbuf是存储器缓存,在整个网络代码中用于存储各种信息,是很核心的一个数据结构,我们将在下一章节详细介绍它,在这里我们先简单了解下它的常见用法。

1. 包含插口地址结构的mbuf

                                            图5 包含socketf地址的mbuf

    在图5中,mbuf的前20个字节是首部,它包含关于这个mbuf的一些基础信息,由四个4字节字段和两个2字节字段组成,mbuf总长为128个字节。

    m_next和m_nextpkt用于将多个mbuf连接起来;m_data指向mbuf中的数据,本例中数据为插口地址(sockaddr_in{}),m_len指示它的长度;m_type指示mbuf中的数据类型,本例中为MT_SONAME(插口名称),m_flags为标志位。

2. 包含数据的mbuf

                                                                  图6 包含数据的mbuf

    图6中的结构由两个mbuf组成,通过m_next指针指向下一个mbuf,这种方式叫作mbuf链表。与图5中的mbuf最大的差别是第一个mbuf首部增加了两个成员:m_pkthdr.len和m_pkthdr.rcvif。这两个成员组成了一个分组首部并且只用在链表的第一个mbuf中。m_flags的值为M_PKTHDR,指示这个mbuf包含一个分组首部。m_pkthdr.len包含整个mbuf链中的数据总长度(本例中为150),m_pkthdr.rcvif包含了一个指向接收分组的接收接口结构的指针。

3. 添加IP和UDP首部

 

                                                                   图7 添加ip和udp首

    在插口层,通常将目标插口地址结构复制到一个mbuf中,并把数据复制到mbu链中,然后与此插口描述符对应的协议层被调用。明确地说,UDP输出例程被调用,指向mbuf的指针被作为一个参数传递。这个例程在这150个字节数据前添加一个IP首部和一个UDP首部,然后将这个mbuf链传递给IP输出例程。

    在图7中,IP首部和UDP首部放置在第一个mbuf中的最后部分,同样的第一个mbuf中包含了m_pkthdr.len和m_pkthdr.rcvif,同时设置M_PKTHDR标志;而原来的分组首部(第2个mbuf中)空间现在未用,同时清除M_PKTHDR标志。

 

网络输出与输入

1. 输出过程

    图8给出了一个进程调用 sendto传输一个UDP数据报时的大致处理过程。

    IP输出例程要填写IP首部中剩余字段,包括IP检验和;确定数据报应由哪个输出接口发出;必要时对IP报文分片。假设输出接口是一个以太网接口,再次把mbuf链的指针作为参数传递给以太网输出函数。

    以太网输出函数第一个功能就是通过ARP地址解析将32位IP地址转换为48位以太网地址。然后将14字节(6字节的以太网目标地址、6字节的以太网源地址和2字节的以太网帧类型)的以太网首部添加到链表的第一个mbuf中,紧接在IP首部的前面,然后将mbuf链表加到此接口的输出队列中等待被发送。

 

                                 图8 三层处理一个简单UDP输出执行过程

2. 输入过程

    与输出处理不同,因为输入处理是异步的,是通过中断的方式来接收一个分组,而不同输出处理是通过进程的系统调用。

    当数据到达接口时(假设是以太网)产生中断,以太网设备驱动程序处理将数据从设备读取到mbuf链表中,同时根据帧类型将mbuf链表向上层协议提交,假设类型字段为IP数据报,则将mbuf链加入到IP输出队列中。

    此时,会产生一个软中断来执行IP输入例程,可见IP输入也是异步的。IP输入例程会循环处理它的输入队列中的每一个IP数据报,直到处理完后返回。

    IP输入例程会根据首部协议字段继续将数据传递到上层协议的缓存队列中,调用上层协议的输入例程处理数据,最终将数据提交给应用进程。

 

中断与并发

    输入处理是异步的中断驱动的方式。首先,一个设备中断引发接口层代码执行,然后它产生一个软中断引发协议层代码执行。当内核完成这些级别的中断后,执行插口代码。

    在这里给每个硬件和软件中断分配一个优先级。如下图9所示:

 

                                                                                图9 中断优先级

    对于不同优先级,比较关心的问题就是如何处理那些在不同级别的进程间共享的数据结构。在图3中显示三种不同优先级进程间共享的数据结构——插口队列、接口队列和协议队列。例如,当IP输入例程正在从它的输入队列中取出一个接收分组时,一个设备中断发生,抢占了协议层,并且那个设备驱动程序可能添加另外一个分组到IP输入队列。如果不协调它们对数据的访问,可能会破坏数据完整性。

    那么怎么解决这个问题呢?Net/3代码经常通过调用函数splimp和splnet。这两个调用总是与splx成对出现,splx使处理器返回到原来的优先级。

    如下面代码所示:

struct mbuf *m

int s;

s = splimt();

IF_DEQUEUE(&ipintrq, m);

splx(s);

if (m == 0):

    return

    当协议层IP输入函数执行时,先调用splimp把CPU优先级升高到网络设备驱动程序级,防止任何网络设备驱动程序中断发生。原来的优先级存储到变量s中。然后执行宏IF_DEQUEUE把IP输入队列ipintrq头部的第二个分组删去,并将指向mbuf链表的指针存储在变量m中。最后通过splx恢复CPU优先级。由于在splimp和splx之间所有的网络设备驱动程序的中断被禁止,所以在这两个调用间代码应尽量少。

 

源码组织结构 

    本系列文章所有源码均是基于Net/3版本,其源代码组织结构如图10所示:   

 

                                                                        图10 源代码组织结构

    本系列文章的重点在目录netinet,它包含了所有TCP/IP源代码

更多最新文章尽在公众号:大白爱爬山,欢迎关注!

TCP/IP详解·2:实现》完整而详细地介绍了TCP/IP协议是如何实现的。书中给出了约500个图例,15000行实际操作的C代码,采用举例教学的方法帮助你掌握TCP/IP实现。《TCP/IP详解·2:实现》不仅说明了插口API和协议族的关系以及主机实现与路由器实现的差别。还介绍了4.4BSD-Lite版的新的特点。《TCP/IP详解·2:实现》适用于希望理解TCP/IP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 目录 · · · · · · 第一 概述 1.1 引言 1.2 源代码表示 1.3 历史 1.4 应用编程接口 1.5 程序示例 1.6 系统调用和库函数 1.7 描述符 1.8 网络实现概述 1.9 mbuf与输出处理 1.10 输入处理 1.11 网络实现概述 1.12 中断级别与并发 1.13 源代码组织 1.14 测试网络 1.15 小结 第二 mduf:存储器缓存 2.1 引言 2.2 代码介绍 2.3 mduf的定义 2.4 mduf结构 2.5 简单的mduf宏和函数 2.6 m_devget和m_pullup函数 2.7 mduf宏和函数的小结 2.8 Net/3联网数据结构小结 2.9 m_copy和簇引用记数 2.10 其他选择 2.11 小结 第三 接口层 3.1 引言 3.2 代码介绍 3.3 ifnet结构 3.4 ifaddr结构 3.5 sockaddr结构 3.6 ifnet与ifaddr的专用化 3.7 网络初始化概述 3.8 以太网初始化 3.9 SLIP初始化 3.10 环回初始化 3.11 if_attach函数 3.12 ifinit函数 3.13 小结 第四 接口:以太网 4.1 引言 4.2 代码介绍 4.3 以太网接口 4.4 ioctl系统调用 4.5 小结 第五 接口:SLIP和环回 5.1 引言 5.2 代码介绍 5.3 SLIP接口 5.4 环回接口 5.5 小结 第六 IP编址 6.1 引言 6.2 代码介绍 6.3 接口和地址小结 6.4 sockaddr_in结构 6.5 in_ifaddr结构 6.6 地址指派 6.7 接口ioctl处理 6.8 internet实用函数 6.9 ifnet实用函数 6.10 小结 第七 域和协议 7.1 引言 7.2 代码介绍 7.3 domain结构 7.4 protosw结构 7.5 IP的domain和protosw结构 7.6 pffindproto和pffindtype函数 7.7 pfctlinput函数 7.8 IP初始化 7.9 sysctl系统调用 7.10 小结 第八 IP:网际协议 8.1 引言 8.2 代码介绍 8.3 IP分组 8.4 输入处理:ipintr函数 8.5 转发:ip_forward函数 8.6 输出处理:ip_output函数 8.7 Internet检验和:in_cksum函数 8.8 setsockopt和getsockopt系统调用 8.9 ip_sysctl函数 8.10 小结 第九 IP选项处理 9.1 引言 9.2 代码介绍 9.3 选项格式 9.4 ip_dooptions函数 9.5 记录路由选项 9.6 源站和记录路由选项 9.7 时间戳选项 9.8 ip_insertoptions函数 9.9 ip_pcbopts函数 9.10 一些限制 9.11 小结 第十 IP的分片与重装 10.1 引言 10.2 代码介绍 10.3 分片 10.4 ip_optcopy函数 10.5 重装 10.6 ip_optcopy函数 10.7 ip_slowtimo函数 10.8 小结 第十一 ICMP:Internet控制报文协议 第十二 IP多播 第十三 IGMP:Internet组管理协议 第十四 IP多播选路 第十五 插口层 第十六 插口I/O 第十七 插口选项 第十八 Radix树路由表 第十九 选路请求和选路消息 第二十 选路接口 第二十一 ARP:地址解析协议 第二十二 协议控制块 第二十三 UDP:用户数据报协议 第二十四 TCP:传输控制协议 第二十五 TCP的定时器 第二十六 TCP输出 第二十七 TCP的函数 第二十八 TCP的输入 第二十九 TCP的输入(续) 第三十 TCP的用户需求 第三十一 BPF:BSD分组过滤程序 第三十二 原始IP 结束语 附录A 部分习题的解答 附录B 源代码的获取 附录C RFC 1122的有关内容 参考文献
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值