UNP总结 Chapter 26~29 线程、IP选项、原始套接字、数据链路访问
此为UNP最后一段总结 Chapter30主要为代码实践 请参考UNP
一、线程
这里UNP的线程与APUE中讲得线程基本一致,但是APUE讲得更加细致,所以这里只列出主要内容(线程数据会稍微详细) 详细见APUE线程相关章节 或者本博文中的APUE专题
1.基本线程函数:创建与终止
2.线程特定数据
使用线程特定数据是使现成函数线程安全的常用技巧
这里重点提一下的是
1).每个系统支持有限数量的线程特定数据项。系统(很可能是线程库)为每个进程维护一个数据结构,我们称之为Key结构,如下图
2).Key结构中的标志指示这个数组元素是否正在使用,所有的标志初始化为"不在使用"。当一个线程调用pthread_key_create创建一个新线程特定数据项时,系统搜索其Key结构数组并找到第一个未被使用的项。其下标(0~127)称做键(key),这个下标返回给调用线程。我们很快谈到Key结构的另一个成员“析构函数指针”。
除了进程范围内的Key结构数组外,系统还为进程的每个线程维护多条信息。这些特定于线程的信息我们称之为Pthread结构,其部分内容是我们称之为pkey数组的一个128个元素的指针数组,如下图:
3).下图给出示意图:把malloc到的内存区和线程特定数据指针相关联
4).线程n初始化它的线程特定数据后的数据结构
3.互斥锁
4.条件变量
二、IP选项
1.概述
IPv4允许在20字节首部固定部分之后跟以最多40字节的选项。尽管已经定义的IPv4选项公有10种,最常用的是源路径选项,这些选项的访问途径是存取IP_OPTIONS套接字选项。
IPv6允许在固定长度40字节IPv6头部和传输层头部(如ICMPv6,TCP或UDP)之间出现扩展头部(extension header)。和IPv4不同的是,访问IPv6扩展头部的途径是通过函数接口进行,而不是强迫用户去理解头部如何出现在IPv6分组中的真实细节。
2.IPv4选项、IPv4源路径选项
1).读取和设置IP选项字段使用getsockopt和setsockopt(level参数为IPPROTO_IP,optname参数为IP_OPTIONS)。getsockopt和setsockopt的第四个参数是指向一个缓冲区(大于小于等于44字节)的指针,第五个参数是该缓冲区的大小。
2).使用setsockopt设置了IP选项之后,在相应套接字发送的所有IP数据报都将包括这些选项。套接字可以是TCP,UDP或原始IP套接口。为了清除这些选项,可以调用setsockopt,置第四个参数为空指针,或者置第五个参数(长度)为0
3).当调用getsockopt获取一个由accept创建的已连接TCP套接字的IP选项时,返回的是在相应监听套接字上收到的客户的SYN中源路径选项的逆转。源路径自动被TCP倒序,因为由客户指定的是从客户到服务器的源路径,服务器需要在发送到客户的数据中使用该路径的逆转
4).源路径是由IP数据报的发送者指定的一个IP地址列表。如果源路径是严格的(strict),那么数据报必须且只能逐一经过所列的节点。如果源路径是宽松的(loose),数据报必须逐一经过所列的节点,不过也可以经过未在源路径中列出的节点。
下图展现向内核的源路径
下图展现getsockopt返回的源路径选项格式
详细代码示例参见UNP
3.IPv扩展首部
- 步跳(hop_by_hop)选项必须紧跟40字节的IPv6头部。目前没有定义这种可供应用程序使用的选项。
- 目的(destination)选项:目前没有定义这种可供应用程序使用的选项。
- 路由头部(routing header):这是一个源路由选项,在概念上类似于我们在24.3节中描述的IPv4源路径选项。
- 分片头部(fragmentation hearder):该头部由将IPv6数据报分片的主机自动生成,有最终的目的主机重组片段时处理。
- 认证头部(authentication header, AH):
- 封装安全有效负载(encapsulating security payload, ESP)头部:
4.IPv6路由首部
下图展现IPv6路由首部:
5.IPv6粘附选项
- IPv6分组信息
- 外出跳限干或跳限
- 下一条地址
- 外出流通类别或者接受流通类别
- 步跳选项
- 目的地选项
- 路由首部
三、原始套接字
1.概述
原始套接口提供以下三种TCP及UDP套接口一般不提供的功能。
1). 有了原始套接字,进程可以读写ICMPv4,IGMPv4,ICMPv6分组。例如:Ping程序,就使用原始套接字发送ICMP回射请求,并接受ICMP回射应答。
2). 有了原始套接字,进程可以读写内核不处理其协议字段的IPv4数据报。
3). 有了原始套接字,进程还可以利用IP_HDRINCL套接字选项可以构造自己的IPv4首部。
2.原始套接字创建
创建一个原始套接字涉及以下几步:
1). 当第二个参数是SOCK_RAW时,调用socket函数,以创建一个原始套接字。第三个参数(协议)一般不应为0,例如,为了创建一个IPv4原始套接口,我们可以这样写:
int sockfd; sockfd = socket(AF_INET, SOCK_RAW, protocol);
其中protocol参数值为形如IPPROTO_xxx的常值,由<netinet/in.h>头文件定义,如IPPROTO_IGMP
2).可以设置IP_HDRINCL套接字选项:
const int on = 1; if ( setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0 ) 出错处理
3).可以在这个原始套接字上调用bind函数,不过不过比较少见。bind函数仅用来设置本地地址:因为原始套接字不存在端口号的概念。就输出而言,调用bind设置的是将用于从这个原始套接字发送的所有数据的源IP地址(只在IP_HDRINCL套接字选项未开启的前提下),若果不调用bind,内核就把IP地址设置为外出接口的主IP地址。
4).可以在这个原始套接字上可调用connect函数,不过也比较少见。connect函数仅仅设置外地地址,同样因为因为原始套接字不存在端口号的概念。对于输出而言,调用connect之后,我们可以把sendto调用改为write或send调用,因为目的IP地址已经指定了。
3.原始套接字输出
原始套接字的输出遵循以下规则:
1). 普通输出通过调用sendto或sendmsg并指定目的IP地址来完成。如果套接字已经连接,那么也可以调用write,writev或send
2). 如果IP_HDRINCL选项未开启,那么由进程让内核发送的数据的起始地址指的IP首部之后的第一个字节。因为内核将构造IP首部并把它置于来自进程的数据之前,内核把IPv4首部的协议字段设置成来自socket调用的第三个参数。
3). 如果IP_HDRINCL铜戒指选项已开启,那么由进程让内核写的数据起始地址指IP首部的第一个字节。
4). 内核会对超出外出接口MTU的原始分组执行分片。
4.原始套接字输入
内核把收到的IP数据报传递到原始套接字要遵循规则:
- 接收到的TCP分组和UDP分组决不会传递给任何原始套接字
- 大多数ICMP分组在内核处理完其中的ICMP消息后传递到给原始套接字
- 所有IGMP分组在内核完成处理其中的IGMP消息之后传递到原始套接口字
- 内核不认识其协议字段的IP数据报传递到原始套接字
- 如果某个数据报以片段形式到达,则该分组将在所有片段到达并重组后才传给原始套接字。
5.ping程序
ping程序的操作非常简单,往某个IP地址发送一个ICMP回射请求,该节点则以一个ICMP回射应答,概貌如下 程序详见UNP
6.traceroute程序
traceroute的用途是确定从我们的主机到目的地之间IP数据报行进的路径,traceroute使用IPv4的TTL字段或IPv6的跳限字段以及两个ICMP消息。它一开始向目的主机发送一个TTL(或跳限)为1的UDP数据报。这个数据报导致头一跳的路由器返回一个“time exceeded in transmit(传输中超时)”错。接着每次对TTL加1,并分别发出UDP数据报,这样可以逐步确定下一个路由器。当UDP数据报终于到达目的主机时,希望目的主机能返回一个“port unreachable(端口不可达)”错,这通过向一个期望未被使用的随机端口发送UDP数据报来实现。
7.一个ICMP消息守护程序
这里给出描述图 代码见UNP
从icmpd获取绑定在那个UDP套接字上的端口号之后就关闭该套接字的本地副本后,icmpd一旦收取由该应用进程通过绑定在它的UDP套接字上的端口发送的UDP数据报所引发的ICMP错误,就通过Unix域连接向该应用程序发送一条信息。
四、数据链路访问
这里仅列出主要内容
1.BPF:BSD分组过滤器
2.DLPI:数据链路提供接口
3.Linux:SOCK_PACKET和PF_PACKET
4.libpcap:分组捕获函数库
5.libnet:分组构造与输出函数库
6.检查UDP的校验与字段