出版日期:2013年06月
ISBN:9787111425199
[十位:7111425197]
页数:345
定价:¥69.00
店铺售价:¥80.00
(为您节省:¥-11.00)
店铺库存:0
本
正在处理购买信息,请稍候……
我要买:
本
* 如何购买
联系店主:
18172612397
-
100分
满分
确认收货后30天未评价,系统默认好评!
[2021-09-24 22:12:13]
胡**
玉溪市
-
100分
满分
确认收货后30天未评价,系统默认好评!
[2021-09-14 22:09:44]
梁**
广州市
-
100分
满分
确认收货后30天未评价,系统默认好评!
[2021-09-11 00:01:48]
栗*
长治市
-
100分
满分
确认收货后30天未评价,系统默认好评!
[2021-09-10 13:41:09]
关**
廊坊市
-
100分
满分
确认收货后30天未评价,系统默认好评!
[2021-09-09 15:46:58]
张*
上海市
《Linux高性能服务器编程(Linux服务器编程领域经典著作,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐述编写高性能Linux服务器应用的方法、技巧和思想?)》内容提要:
本书是Linux服务器编程领域的经典著作,由**Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐释了编写高性能Linux服务器应用的方法、技巧和思想。不仅理论全面、深入,抓住了**和难点,还包含两个综合性案例,**实战意义。
全书共17章,分为3个部分:**部分对Linux服务器编程的核心基础——TCP/IP协议进行了深入的解读和阐述,包括TCP/IP协议族、TCP/IP协议,以及一个经典的TCP/IP通信案例;第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、**I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多线程编程、进程池和线程池等内容,原理、技术与方法并重;第三部分从侧重实战的角度讲解了高性能服务器的优化与监测,包含服务器的调制、调试和测试,以及各种实用系统监测工具的使用等内容。 本书另外免费赠送一个负载均衡服务器程序的完整实际项目的源代码!
《Linux高性能服务器编程(Linux服务器编程领域经典著作,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐述编写高性能Linux服务器应用的方法、技巧和思想?)》图书目录:
前言
**篇 TCPIP协议详解
第1章 TCPIP协议族
1.1 TCPIP协议族体系结构以及主要协议
1.1.1 数据链路层
1.1.2 网络层
1.1.3 传输层
1.1.4 应用层
1.2 封装
1.3 分用
1.4 测试网络
1.5 ARP协议工作原理
1.5.1 以太网ARP请求应答报文详解
1.5.2 ARP高速缓存的查看和修改
1.5.3 使用tcpdump观察ARP通信过程
1.6 DNS工作原理
1.6.1 DNS查询和应答报文详解
1.6.2 Linux下访问DNS服务
1.6.3 使用tcpdump观察DNS通信过程
1.7 socket和TCPIP协议族的关系
第2章 IP协议详解
2.1 IP服务的特点
2.2 IPv4头部结构
2.2.1 IPv4头部结构
2.2.2 使用tcpdump观察IPv4头部结构
2.3 IP分片
2.4 IP路由
2.4.1 IP模块工作流程
2.4.2 路由机制
2.4.3 路由表更新
2.5 IP转发
2.6 重定向
2.6.1 ICMP重定向报文
2.6.2 主机重定向实例
2.7 IPv6头部结构
2.7.1 IPv6固定头部结构
2.7.2 IPv6扩展头部
第3章 TCP协议详解
3.1 TCP服务的特点
3.2 TCP头部结构
3.2.1 TCP固定头部结构
3.2.2 TCP头部选项
3.2.3 使用tcpdump观察TCP头部信息
3.3 TCP连接的建立和关闭
3.3.1 使用tcpdump观察TCP连接的建立和关闭
3.3.2 半关闭状态
3.3.3 连接超时
3.4 TCP状态转移
3.4.1 TCP状态转移总图
3.4.2 TIME_WAIT状态
3.5 复位报文段
3.5.1 访问不存在的端口
3.5.2 异常终止连接
3.5.3 处理半打开连接
3.6 TCP交互数据流
3.7 TCP成块数据流
3.8 带外数据
3.9 TCP超时重传
3.10 拥塞控制
3.10.1 拥塞控制概述
3.10.2 慢启动和拥塞避免
3.10.3 快速重传和快速恢复
第4章 TCPIP通信案例:访问Internet上的Web服务器
4.1 实例总图
4.2 部署代理服务器
4.2.1 HTTP代理服务器的工作原理
4.2.2 部署squid代理服务器
4.3 使用tcpdump抓取传输数据包
4.4 访问DNS服务器
4.5 本地名称查询
4.6 HTTP通信
4.6.1 HTTP请求
4.6.2 HTTP应答
4.7 实例总结
第二篇 深入解析高性能服务器编程
第5章 Linux网络编程基础API
5.1 socket地址API
5.1.1 主机字节序和网络字节序
5.1.2 通用socket地址
5.1.3 专用socket地址
5.1.4 IP地址转换函数
5.2 创建socket
5.3 命名socket
5.4 监听socket
5.5 接受连接
5.6 发起连接
5.7 关闭连接
5.8 数据读写
5.8.1 TCP数据读写
5.8.2 UDP数据读写
5.8.3 通用数据读写函数
5.9 带外标记
5.10 地址信息函数
5.11 socket选项
5.11.1 SO_REUSEADDR选项
5.11.2 SO_RCVBUF和SO_SNDBUF选项
5.11.3 SO_RCVLOWAT和SO_SNDLOWAT选项
5.11.4 SO_LINGER选项
5.12 网络信息API
5.12.1 gethostbyname和gethostbyaddr
5.12.2 getservbyname和getservbyport
5.12.3 getaddrinfo
5.12.4 getnameinfo
第6章 **IO函数
6.1 pipe函数
6.2 dup函数和dup2函数
6.3 readv函数和writev函数
6.4 sendfile函数
6.5 mmap函数和munmap函数
6.6 splice函数
6.7 tee函数
6.8 fcntl函数
第7章 Linux服务器程序规范
7.1 日志
7.1.1 Linux系统日志
7.1.2 syslog函数
7.2 用户信息
7.2.1 UID、EUID、GID和EGID
7.2.2 切换用户
7.3 进程间关系
7.3.1 进程组
7.3.2 会话
7.3.3 用ps命令查看进程关系
7.4 系统资源限制
7.5 改变工作目录和根目录
7.6 服务器程序后台化
第8章 高性能服务器程序框架
8.1 服务器模型
8.1.1 CS模型
8.1.2 P2P模型
8.2 服务器编程框架
8.3 IO模型
8.4 两种**的事件处理模式
8.4.1 Reactor模式
8.4.2 Proactor模式
8.4.3 模拟Proactor模式
8.5 两种**的并发模式
8.5.1 半同步半异步模式
8.5.2 ***追随者模式
8.6 有限状态机
8.7 提高服务器性能的其他建议
8.7.1 池
8.7.2 数据复制
8.7.3 上下文切换和锁
第9章 IO复用
9.1 select系统调用
9.1.1 select API
9.1.2 文件描述符就绪条件
9.1.3 处理带外数据
9.2 poll系统调用
9.3 epoll系列系统调用
9.3.1 内核事件表
9.3.2 epoll_wait函数
9.3.3 LT和ET模式
9.3.4 EPOLLONESHOT事件
9.4 三组IO复用函数的比较
9.5 IO复用的**应用一:非阻塞connect
9.6 IO复用的**应用二:聊天室程序
9.6.1 客户端
9.6.2 服务器
9.7 IO复用的**应用三:同时处理TCP和UDP服务
9.8 **服务xinetd
9.8.1 xinetd配置文件
9.8.2 xinetd工作流程
第10章 信号
10.1 Linux信号概述
10.1.1 发送信号
10.1.2 信号处理方式
10.1.3 Linux信号
10.1.4 中断系统调用
10.2 信号函数
10.2.1 signal系统调用
10.2.2 sigaction系统调用
10.3 信号集
10.3.1 信号集函数
10.3.2 进程信号掩码
10.3.3 被挂起的信号
10.4 统一事件源
10.5 网络编程相关信号
10.5.1 SIGHUP
10.5.2 SIGPIPE
10.5.3 SIGURG
第11章 定时器
11.1 socket选项SO_RCVTIMEO和SO_SNDTIMEO
11.2 SIGALRM信号
11.2.1 基于升序链表的定时器
11.2.2 处理非活动连接
11.3 IO复用系统调用的超时参数
11.4 高性能定时器
11.4.1 时间轮
11.4.2 时间堆
第12章 高性能IO框架库Libevent
12.1 IO框架库概述
12.2 Libevent源码分析
12.2.1 一个实例
12.2.2 源代码组织结构
12.2.3 event结构体
12.2.4 往注册事件队列中添加事件处理器
12.2.5 往事件多路分发器中注册事件
12.2.6 eventop结构体
12.2.7 event_base结构体
12.2.8 事件循环
第13章 多进程编程
13.1 fork系统调用
13.2 exec系列系统调用
13.3 处理僵尸进程
13.4 管道
13.5 信号量
13.5.1 信号量原语
13.5.2 semget系统调用
13.5.3 semop系统调用
13.5.4 semctl系统调用
13.5.5 特殊键值IPC_PRIVATE
13.6 共享内存
13.6.1 shmget系统调用
13.6.2 shmat和shmdt系统调用
13.6.3 shmctl系统调用
13.6.4 共享内存的POSIX方法
13.6.5 共享内存实例
13.7 消息队列
13.7.1 msgget系统调用
13.7.2 msgsnd系统调用
13.7.3 msgrcv系统调用
13.7.4 msgctl系统调用
13.8 IPC命令
13.9 在进程间传递文件描述符
第14章 多线程编程
14.1 Linux线程概述
14.1.1 线程模型
14.1.2 Linux线程库
14.2 创建线程和结束线程
14.3 线程属性
14.4 POSIX信号量
14.5 互斥锁
14.5.1 互斥锁基础API
14.5.2 互斥锁属性
14.5.3 死锁举例
14.6 条件变量
14.7 线程同步机制包装类
14.8 多线程环境
14.8.1 可重入函数
14.8.2 线程和进程
14.8.3 线程和信号
第15章 进程池和线程池
15.1 进程池和线程池概述
15.2 处理多客户
15.3 半同步半异步进程池实现
15.4 用进程池实现的简单CGI服务器
15.5 半同步半反应堆线程池实现
15.6 用线程池实现的简单Web服务器
15.6.1 http_conn类
15.6.2 main函数
第三篇 高性能服务器优化与监测
第16章 服务器调制、调试和测试
16.1 *大文件描述符数
16.2 调整内核参数
16.2.1 procsysfs目录下的部分文件
16.2.2 procsysnet目录下的部分文件
16.3 gdb调试
16.3.1 用gdb调试多进程程序
16.3.2 用gdb调试多线程程序
16.4 压力测试
第17章 系统监测工具
17.1 tcpdump
17.2 lsof
17.3 nc
17.4 strace
17.5 netstat
17.6 vmstat
17.7 ifstat
17.8 mpstat
参考文献
《Linux高性能服务器编程(Linux服务器编程领域经典著作,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐述编写高性能Linux服务器应用的方法、技巧和思想?)》文章节选:
**篇
TCP/IP协议详解
第1章 TCP/IP协议族
第2章 IP协议详解
第3章 TCP协议详解
第4章 TCP/IP通信案例:访问Internet上的Web服务器
第1章 TCP/IP协议族
现在Internet(因特网)使用的主流协议族是TCP/IP协议族,它是一个分层、多协议的通信体系。本章简要讨论TCP/IP协议族各层包含的主要协议,以及它们之间是如何协作完成网络通信的。
TCP/IP协议族包含众多协议,我们无法一一讨论。本书将在后续章节详细讨论IP协议和TCP协议,因为它们对编写网络应用程序具有*直接的影响。本章则简单介绍其中几个相关协议:ICMP协议、ARP协议和DNS协议,学习它们对于理解网络通信很有帮助。读者如果想要系统地学习网络协议,那么RFC(Request For Comments,评论请求)文档无疑是**资料。
1.1 TCP/IP协议族体系结构以及主要协议
TCP/IP协议族是一个四层协议系统,自底而上分别是数据链路层、网络层、传输层和应用层。每一层完成不同的功能,且通过若干协议来实现,上层协议使用下层协议提供的服务,如图1-1所示。
1.1.1 数据链路层
数据链路层实现了网卡接口的网络驱动程序,以处理数据在物理媒介(比如以太网、令牌环等)上的传输。不同的物理网络具有不同的电气特性,网络驱动程序隐藏了这些细节,为上层协议提供一个统一的接口。
数据链路层两个常用的协议是ARP协议(Address Resolve Protocol,地址解析协议)和RARP协议(Reverse Address Resolve Protocol,逆地址解析协议)。它们实现了IP地址和机器物理地址(通常是MAC地址,以太网、令牌环和802.11无线网络都使用MAC地址)之间的相互转换。
网络层使用IP地址寻址一台机器,而数据链路层使用物理地址寻址一台机器,因此网络层必须先将目标机器的IP地址转化成其物理地址,才能使用数据链路层提供的服务,这就是ARP协议的用途。RARP协议仅用于网络上的某些无盘工作站。因为缺乏存储设备,无盘工作站无法记住自己的IP地址,但它们可以利用网卡上的物理地址来向网络管理者(服务器或网络管理软件)查询自身的IP地址。运行RARP服务的网络管理者通常存有该网络上所有机器的物理地址到IP地址的映射。
由于ARP协议很重要,所以我们将在后面章节专门讨论它。
1.1.2 网络层
网络层实现数据包的选路和转发。WAN(Wide Area Network,广域网)通常使用众多分级的路由器来连接分散的主机或LAN(Local Area Network,局域网),因此,通信的两台主机一般不是直接相连的,而是通过多个中间节点(路由器)连接的。网络层的任务就是选择这些中间节点,以确定两台主机之间的通信路径。同时,网络层对上层协议隐藏了网络拓扑连接的细节,使得在传输层和网络应用程序看来,通信的双方是直接相连的。
网络层*核心的协议是IP协议(Internet Protocol,因特网协议)。IP协议根据数据包的目的IP地址来决定如何投递它。如果数据包不能直接发送给目标主机,那么IP协议就为它寻找一个合适的下一跳(next hop)路由器,并将数据包交付给该路由器来转发。多次重复这一过程,数据包*终到达目标主机,或者由于发送失败而被丢弃。可见,IP协议使用逐跳(hop by hop)的方式确定通信路径。我们将在第2章详细讨论IP协议。
网络层另外一个重要的协议是ICMP协议(Internet Control Message Protocol,因特网控制报文协议)。它是IP协议的重要补充,主要用于检测网络连接。ICMP协议使用的报文格式如图1-2所示。
图1-2中,8位类型字段用于区分报文类型。它将ICMP报文分为两大类:一类是差错报文,这类报文主要用来回应网络错误,比如目标不可到达(类型值为3)和重定向(类型值为5);另一类是查询报文,这类报文用来查询网络信息,比如ping程序就是使用ICMP报文查看目标是否可到达(类型值为8)的。有的ICMP报文还使用8位代码字段来进一步细分不同的条件。比如重定向报文使用代码值0表示对网络重定向,代码值1表示对主机重定向。ICMP报文使用16位校验和字段对整个报文(包括头部和内容部分)进行循环冗余校验(Cyclic Redundancy Check,CRC),以检验报文在传输过程中是否损坏。不同的ICMP报文类型具有不同的正文内容。我们将在第2章详细讨论主机重定向报文,其他ICMP报文格式请参考ICMP协议的标准文档RFC 792。
需要指出的是,ICMP协议并非严格意义上的网络层协议,因为它使用处于同一层的IP协议提供的服务(一般来说,上层协议使用下层协议提供的服务)。
1.1.3 传输层
传输层为两台主机上的应用程序提供端到端(end to end)的通信。与网络层使用的逐跳通信方式不同,传输层只关心通信的起始端和目的端,而不在乎数据包的中转过程。图1-3展示了传输层和网络层的这种区别。
图1-3中,垂直的实线箭头表示TCP/IP协议族各层之间的实体通信(数据包确实是沿着这些线路传递的),而水平的虚线箭头表示逻辑通信线路。该图中还附带描述了不同物理网络的连接方法。可见,数据链路层(驱动程序)封装了物理网络的电气细节;网络层封装了网络连接的细节;传输层则为应用程序封装了一条端到端的逻辑通信链路,它负责数据的收发、链路的超时重连等。
传输层协议主要有三个:TCP协议、UDP协议和SCTP协议。
TCP协议(Transmission Control Protocol,传输控制协议)为应用层提供可靠的、面向连接的和基于流(stream)的服务。TCP协议使用超时重传、数据确认等方式来确保数据包被正确地发送至目的端,因此TCP服务是可靠的。使用TCP协议通信的双方必须先建立TCP连接,并在内核中为该连接维持一些必要的数据结构,比如连接的状态、读写缓冲区,以及诸多定时器等。当通信结束时,双方必须关闭连接以释放这些内核数据。TCP服务是基于流的。基于流的数据没有边界(长度)限制,它源源不断地从通信的一端流入另一端。发送端可以逐个字节地向数据流中写入数据,接收端也可以逐个字节地将它们读出。
UDP协议(User Datagram Protocol,用户数据报协议)则与TCP协议完全相反,它为应用层提供不可靠、无连接和基于数据报的服务。“不可靠”意味着UDP协议无法保证数据从发送端正确地传送到目的端。如果数据在中途丢失,或者目的端通过数据校验发现数据错误而将其丢弃,则UDP协议只是简单地通知应用程序发送失败。因此,使用UDP协议的应用程序通常要自己处理数据确认、超时重传等逻辑。UDP协议是无连接的,即通信双方不保持一个长久的联系,因此应用程序每次发送数据都要明确指定接收端的地址(IP地址等信息)。基于数据报的服务,是相对基于流的服务而言的。每个UDP数据报都有一个长度,接收端必须以该长度为*小单位将其所有内容一次性读出,否则数据将被截断。
SCTP协议(Stream Control Transmission Protocol,流控制传输协议)是一种相对较新的传输层协议,它是为了在因特网上传输电话信号而设计的。本书不讨论SCTP协议,感兴趣的读者可参考其标准文档RFC 2960。
我们将在第3章详细讨论TCP协议,并附带介绍UDP协议。
1.1.4 应用层
应用层负责处理应用程序的逻辑。数据链路层、网络层和传输层负责处理网络通信细节,这部分必须既稳定又**,因此它们都在内核空间中实现,如图1-1所示。而应用层则在用户空间实现,因为它负责处理众多逻辑,比如文件传输、名称查询和网络管理等。如果应用层也在内核中实现,则会使内核变得非常庞大。当然,也有少数服务器程序是在内核中实现的,这样代码就无须在用户空间和内核空间来回切换(主要是数据的复制),极大地提高了工作效率。不过这种代码实现起来较复杂,不够灵活,且不便于移植。本书只讨论用户空间的网络编程。
应用层协议很多,图1-1仅列举了其中的几个:
ping是应用程序,而不是协议,前面说过它利用ICMP报文检测网络连接,是调试网络环境的**工具。
telnet协议是一种远程登录协议,它使我们能在本地完成远程任务,本书后续章节将会多次使用telnet客户端登录到其他服务上。
OSPF(Open Shortest Path First,开放*短路径优先)协议是一种动态路由更新协议,用于路由器之间的通信,以告知对方各自的路由信息。
DNS(Domain Name Service,域名服务)协议提供机器域名到IP地址的转换,我们将在后面简要介绍DNS协议。
应用层协议(或程序)可能跳过传输层直接使用网络层提供的服务,比如ping程序和OSPF协议。应用层协议(或程序)通常既可以使用TCP服务,又可以使用UDP服务,比如DNS协议。我们可以通过/etc/services文件查看所有知名的应用层协议,以及它们都能使用哪些传输层服务。
1.2 封装
上层协议是如何使用下层协议提供的服务的呢?其实这是通过封装(encapsulation)实现的。应用程序数据在发送到物理网络上之前,将沿着协议栈从上往下依次传递。每层协议都将在上层数据的基础上加上自己的头部信息(有时还包括尾部信息),以实现该层的功能,这个过程就称为封装,如图1-4所示。
经过TCP封装后的数据称为TCP报文段(TCP message segment),或者简称TCP段。前文提到,TCP协议为通信双方维持一个连接,并且在内核中存储相关数据。这部分数据中的TCP头部信息和TCP内核缓冲区(发送缓冲区或接收缓冲区)数据一起构成了TCP报文段,如图1-5中的虚线框所示。
当发送端应用程序使用send(或者write)函数向一个TCP连接写入数据时,内核中的TCP模块首先把这些数据复制到与该连接对应的TCP内核发送缓冲区中,然后TCP模块调用IP模块提供的服务,传递的参数包括TCP头部信息和TCP发送缓冲区中的数据,即TCP报文段。关于TCP报文段头部的细节,我们将在第3章讨论。
经过UDP封装后的数据称为UDP数据报(UDP datagram)。UDP对应用程序数据的封装与TCP类似。不同的是,UDP无须为应用层数据保存副本,因为它提供的服务是不可靠的。当一个UDP数据报被成功发送之后,UDP内核缓冲区中的该数据报就被丢弃了。如果应用程序检测到该数据报未能被接收端正确接收,并打算重发这个数据报,则应用程序需要重新从用户空间将该数据报拷贝到UDP内核发送缓冲区中。
经过IP封装后的数据称为IP数据报(IP datagram)。IP数据报也包括头部信息和数据部分,其中数据部分就是一个TCP报文段、UDP数据报或者ICMP报文。我们将在第2章详细讨论IP数据报的头部信息。
经过数据链路层封装的数据称为帧(frame)。传输媒介不同,帧的类型也不同。比如,以太网上传输的是以太网帧(ethernet frame),而令牌环网络上传输的则是令牌环帧(token ring frame)。以以太网帧为例,其封装格式如图1-6所示。
以太网帧使用6字节的目的物理地址和6字节的源物理地址来表示通信的双方。关于类型(type)字段,我们将在后面讨论。4字节CRC字段对帧的其他部分提供循环冗余校验。
帧的*大传输单元(Max Transmit Unit,MTU),即帧*多能携带多少上层协议数据(比如IP数据报),通常受到网络类型的限制。图1-6所示的以太网帧的MTU是1500字节。正因为如此,过长的IP数据报可能需要被分片(fragment)传输。
帧才是*终在物理网络上传送的字节序列。至此,封装过程完成。
1.3 分用
当帧到达目的主机时,将沿着协议栈自底向上依次传递。各层协议依次处理帧中本层负责的头部数据,以获取所需的信息,并*终将处理后的帧交给目标应用程序。这个过程称为分用(demultiplexing)。分用是依靠头部信息中的类型字段实现的。标准文档RFC 1700定义了所有标识上层协议的类型字段以及每个上层协议对应的数值。图1-7显示了以太网帧的分用过程。
因为IP协议、ARP协议和RARP协议都使用帧传输数据,所以帧的头部需要提供某个字段(具体情况取决于帧的类型)来区分它们。以以太网帧为例,它使用2字节的类型字段来标识上层协议(见图1-6)。如果主机接收到的以太网帧类型字段的值为0x800,则帧的数据部分为IP数据报(见图1-4),以太网驱动程序就将帧交付给IP模块;若类型字段的值为0x806,则帧的数据部分为ARP请求或应答报文,以太网驱动程序就将帧交付给ARP模块;若类型字段的值为0x835,则帧的数据部分为RARP请求或应答报文,以太网驱动程序就将帧交付给RARP模块。
同样,因为ICMP协议、TCP协议和UDP协议都使用IP协议,所以IP数据报的头部采用16位的协议(protocol)字段来区分它们。
TCP报文段和UDP数据报则通过其头部中的16位的端口号(port number)字段来区分上层应用程序。比如DNS协议对应的端口号是53,HTTP协议(Hyper-Text Transfer Protocol,超文本传送协议)对应的端口号是80。所有知名应用层协议使用的端口号都可在/etc/services文件中找到。
帧通过上述分用步骤后,*终将封装前的原始数据送至目标服务(图1-7中的ARP服务、RARP服务、ICMP服务或者应用程序)。这样,在顶层目标服务看来,封装和分用似乎没有发生过。
1.4 测试网络
为了深入理解网络通信和网络编程,我们准备了图1-8所示的测试网络,其中包括两台主机A和B,以及一个连接到因特网的路由器。后文如没有特别声明,所有测试硬件指的都是该网络。我们将使用机器名来标识测试机器。
该测试网络主要用于分析ARP协议、IP协议、ICMP协议、TCP协议和DNS协议。我们通过抓取该网络上的以太网帧,查看其中的以太网帧头部、IP数据报头部、TCP报文段头部信息,以获取网络通信的细节。这样,以理论结合实践,我们就清楚TCP/IP通信具体是如何进行的了。作者编写的多个客户端、服务器程序都是使用该网络来调试和测试的。
对于路由器,我们仅列出了其LAN网络IP地址(192.168.1.1),而忽略了ISP(Internet Service Provider,因特网服务提供商)给它分配的WAN网络IP地址,因为全书的讨论都不涉及它。
1.5 ARP协议工作原理
ARP协议能实现任意网络层地址到任意物理地址的转换,不过本书仅讨论从IP地址到以太网地址(MAC地址)的转换。其工作原理是:主机向自己所在的网络广播一个ARP请求,该请求包含目标机器的网络地址。此网络上的其他机器都将收到这个请求,但只有被请求的目标机器会回应一个ARP应答,其中包含自己的物理地址。
1.5.1 以太网ARP请求/应答报文详解
以太网ARP请求/应答报文的格式如图1-9所示。
图1-9所示以太网ARP请求/应答报文各字段具体介绍如下。
?硬件类型字段定义物理地址的类型,它的值为1表示MAC地址。
?协议类型字段表示要映射的协议地址类型,它的值为0x800,表示IP地址。
?硬件地址长度字段和协议地址长度字段,顾名思义,其单位是字节。对MAC地址来说,其长度为6;对IP(v4)地址来说,其长度为4。
?操作字段指出4种操作类型:ARP请求(值为1)、ARP应答(值为2)、RARP请求(值为3)和RARP应答(值为4)。
?*后4个字段指定通信双方的以太网地址和IP地址。发送端填充除目的端以太网地址外的其他3个字段,以构建ARP请求并发送之。接收端发现该请求的目的端IP地址是自己,就把自己的以太网地址填进去,然后交换两个目的端地址和两个发送端地址,以构建ARP应答并返回之(当然,如前所述,操作字段需要设置为2)。
由图1-9可知,ARP请求/应答报文的长度为28字节。如果再加上以太网帧头部和尾部的18字节(见图1-6),则一个携带ARP请求/应答报文的以太网帧长度为46字节。不过有的实现要求以太网帧数据部分长度至少为46字节(见图1-4),此时ARP请求/应答报文将增加一些填充字节,以满足这个要求。在这种情况下,一个携带ARP请求/应答报文的以太网帧长度为64字节。
1.5.2 ARP高速缓存的查看和修改
通常,ARP维护一个高速缓存,其中包含经常访问(比如网关地址)或*近访问的机器的IP地址到物理地址的映射。这样就避免了重复的ARP请求,提高了发送数据包的速度。
Linux下可以使用arp命令来查看和修改ARP高速缓存。比如,ernest-laptop在某一时刻(注意,ARP高速缓存是动态变化的)的ARP缓存内容如下(使用arp-a命令):
Kongming20 (192.168.1.109) at 08:00:27:53:10:67 [ether] on eth0
?(192.168.1.1) at 14:e6:e4:93:5b:78 [ether] on eth0
其中,**项描述的是另一台测试机器Kongming20(注意,其IP地址、MAC地址都与图1-8描述的一致),第二项描述的是路由器。下面两条命令则分别删除和添加一个ARP缓存项:
$ sudo arp –d 192.168.1.109 #删除Kongming20对应的ARP缓存项
$ sudo arp –s 192.168.1.109 08:00:27:53:10:67 #添加Kongming20对应的ARP缓存项
1.5.3 使用tcpdump观察ARP通信过程
为了清楚地了解ARP的运作过程,我们从ernest-laptop上执行telnet命令登录Kongming20的echo服务(已经开启),并用tcpdump(详见第17章)抓取这个过程中两台测试机器之间交换的以太网帧。具体的操作过程如下:
$ sudo arp –d 192.168.1.109 #清除ARP缓存中Kongming20对应的项
$ sudo tcpdump -i eth0 -ent '(dst 192.168.1.109 and src 192.168.1.108)or
(dst 192.168.1.108 and src 192.168.1.109)' #如无特殊声明,抓包都在机器ernest-
laptop上执行
$ telnet 192.168.1.109 echo #开启另一个终端执行telnet命令
Trying 192.168.1.109...
Connected to 192.168.1.109.
Escape character is '^]'.
^](回车) #输入Ctrl+]并回车
telnet> quit(回车)
Connection closed.
在执行telnet命令之前,应先清除ARP缓存中与Kongming20对应的项,否则ARP通信不被执行,我们也就无法抓取到期望的以太网帧。当执行telnet命令并在两台通信主机之间建立TCP连接后(telnet输出“Connected to 192.168.1.109”),输入Ctrl+]以调出telnet程序的命令提示符,然后在telnet命令提示符后输入quit,退出telnet客户端程序(因为ARP通信在TCP连接建立之前就已经完成,故我们不关心后续内容)。tcpdump抓取到的众多数据包中,只有*靠前的两个和ARP通信有关系,现在将它们列出(数据包前面的编号是笔者加入的,后同):
1. 00:16:d3:5c:b9:e3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.109 tell 192.168.1.108, length 28
2. 08:00:27:53:10:67 > 00:16:d3:5c:b9:e3, ethertype ARP (0x0806), length 60: Reply 192.168.1.109 is-at 08:00:27:53:10:67, length 46
由tcpdump抓取的数据包本质上是以太网帧,我们通过该命令的众多选项来控制帧的过滤(比如用dst和src指定通信的目的端IP地址和源端IP地址)和显示(比如用-e选项开启以太网帧头部信息的显示)。
**个数据包中,ARP通信的源端的物理地址是00:16:d3:5c:b9:e3(ernest-laptop),目的端的物理地址是ff:ff:ff:ff:ff:ff,这是以太网的广播地址,用以表示整个LAN。该LAN上的所有机器都会收到并处理这样的帧。数值0x806是以太网帧头部的类型字段的值,它表示分用的目标是ARP模块。该以太网帧的长度为42字节(实际上是46字节,tcpdump未统计以太网帧尾部4字节的CRC字段),其中数据部分长度为28字节。“Request”表示这是一个ARP请求,“who-has 192.168.1.109 tell 192.168.1.108”则表示是ernest-laptop要查询Kongming20的IP地址。
第二个数据包中,ARP通信的源端的物理地址是08:00:27:53:10:67(Kongming20),目的端的物理地址是00:16:d3:5c:b9:e3(ernest-laptop)。“Reply”表示这是一个ARP应答,“192.168.1.109 is-at 08:00:27:53:10:67”则表示目标机器Kongming20报告其物理地址。该以太网帧的长度为60字节(实际上是64字节),可见它使用了填充字节来满足*小帧长度。
为了便于理解,我们将上述讨论用图1-10来详细说明。
关于该图,需要说明三点:
**,我们将两次传输的以太网帧按照图1-6所描述的以太网帧封装格式绘制在图的下半部分。
第二,ARP请求和应答是从以太网驱动程序发出的,而并非像图中描述的那样从ARP模块直接发送到以太网上,所以我们将它们用虚线表示,这主要是为了体现携带ARP数据的以太网帧和其他以太网帧(比如携带IP数据报的以太网帧)的区别。
第三,路由器也将接收到以太网帧1,因为该帧是一个广播帧。不过很显然,路由器并没有回应其中的ARP请求,正如前文讨论的那样。
1.6 DNS工作原理
我们通常使用机器的域名来访问这台机器,而不直接使用其IP地址,比如访问因特网上的各种网站。那么如何将机器的域名转换成IP地址呢?这就需要使用域名查询服务。域名查询服务有很多种实现方式,比如NIS(Network Information Service,网络信息服务)、DNS和本地静态文件等。本节主要讨论DNS。
1.6.1 DNS查询和应答报文详解
DNS是一套分布式的域名服务系统。每个DNS服务器上都存放着大量的机器名和IP地址的映射,并且是动态更新的。众多网络客户端程序都使用DNS协议来向DNS服务器查询目标主机的IP地址。DNS查询和应答报文的格式如图1-11所示。
16位标识字段用于标记一对DNS查询和应答,以此区分一个DNS应答是哪个DNS查询的回应。
16位标志字段用于协商具体的通信方式和反馈通信状态。DNS报文头部的16位标志字段的细节如图1-12所示。
图1-12中各标志的含义分别是:
?QR,查询/应答标志。0表示这是一个查询报文,1表示这是一个应答报文。
?opcode,定义查询和应答的类型。0表示标准查询,1表示反向查询(由IP地址获得主机域名),2表示请求服务器状态。
?AA,授权应答标志,仅由应答报文使用。1表示域名服务器是授权服务器。
?TC,截断标志,仅当DNS报文使用UDP服务时使用。因为UDP数据报有长度限制,所以过长的DNS报文将被截断。1表示DNS报文超过512字节,并被截断。
?RD,递归查询标志。1表示执行递归查询,即如果目标DNS服务器无法解析某个主机名,则它将向其他DNS服务器继续查询,如此递归,直到获得结果并把该结果返回给客户端。0表示执行迭代查询,即如果目标DNS服务器无法解析某个主机名,则它将自己知道的其他DNS服务器的IP地址返回给客户端,以供客户端参考。
?RA,允许递归标志。仅由应答报文使用,1表示DNS服务器支持递归查询。
?zero,这3位未用,必须都设置为0。
?rcode,4位返回码,表示应答的状态。常用值有0(无错误)和3(域名不存在)。
接下来的4个字段则分别指出DNS报文的*后4个字段的资源记录数目。对查询报文而言,它一般包含1个查询问题,而应答资源记录数、授权资源记录数和额外资源记录数则为0。应答报文的应答资源记录数则至少为1,而授权资源记录数和额外资源记录数可为0或非0。
查询问题的格式如图1-13所示。
图1-13中,查询名以一定的格式封装了要查询的主机域名。16位查询类型表示如何执行查询操作,常见的类型有如下几种:
?类型A,值是1,表示获取目标主机的IP地址。
?类型CNAME,值是5,表示获得目标主机的别名。
?类型PTR,值是12,表示反向查询。
16位查询类通常为1,表示获取因特网地址(IP地址)。
应答字段、授权字段和额外信息字段都使用资源记录(Resource Record,RR)格式。资源记录格式如图1-14所示。
图1-14中,32位域名是该记录中与资源对应的名字,其格式和查询问题中的查询名字段相同。16位类型和16位类字段的含义也与DNS查询问题的对应字段相同。
32位生存时间表示该查询记录结果可被本地客户端程序缓存多长时间,单位是秒。
16位资源数据长度字段和资源数据字段的内容取决于类型字段。对类型A而言,资源数据是32位的IPv4地址,而资源数据长度则为4(以字节为单位)。
至此,我们简要地介绍了DNS协议。我们将在后面给出一个DNS通信的具体例子。DNS协议的更多细节请参考其RFC文档(DNS协议存在诸多RFC文档,每个RFC文档介绍其一个侧面,比如RFC 1035介绍的是域名的实现和规范,RFC 1886则描述DNS协议对IPv6的扩展支持)。
1.6.2 Linux下访问DNS服务
我们要访问DNS服务,就必须先知道DNS服务器的IP地址。Linux使用/etc/resolv.conf文件来存放DNS服务器的IP地址。机器ernest-laptop上,该文件的内容如下:
# Generated by Network Manager
nameserver 219.239.26.42
nameserver 124.207.160.106
其中的两个IP地址分别是**DNS服务器地址和备选DNS服务器地址。文件中的注释语句“Generated by Network Manager”告诉我们,这两个DNS服务器地址是由网络管理程序写入的。
Linux下一个常用的访问DNS服务器的客户端程序是host,比如下面的命令是向**DNS服务器219.239.26.42查询机器www.baidu.com的IP地址:
$ host –t A www.baidu.com
www.baidu.com is an alias for www.a.shifen.com.
www.a.shifen.com has address 119.75.217.56
www.a.shifen.com has address 119.75.218.77
host命令的输出告诉我们,机器名www.baidu.com是www.a.shifen.com.的别名,并且该机器名对应两个IP地址。host命令使用DNS协议和DNS服务器通信,其-t选项告诉DNS协议使用哪种查询类型。我们这里使用的是A类型,即通过机器的域名获得其IP地址(但实际上返回的资源记录中还包含机器的别名)。关于host命令的详细使用方法,请参考其man手册。
1.6.3 使用tcpdump观察DNS通信过程
为了看清楚DNS通信的过程,下面我们将从ernest-laptop上运行host命令以查询主机www.baidu.com对应的IP地址,并使用tcpdump抓取这一过程中LAN上传输的以太网帧。具体的操作过程如下:
$ sudo tcpdump -i eth0 -nt -s 500 port domain
$ host –t A www.baidu.com
这一次执行tcpdump抓包时,我们使用“port domain”来过滤数据包,表示只抓取使用domain(域名)服务的数据包,即DNS查询和应答报文。tcpdump的输出如下:
1. IP 192.168.1.108.34319 > 219.239.26.42.53: 57428+ A? www.baidu.com. (31)
2. IP 219.239.26.42.53 > 192.168.1.108.34319: 57428 3/4/4 CNAME www.a.shifen.com., A 119.75.218.77, A 119.75.217.56 (226)
这两个数据包开始的“IP”指出,它们后面的内容描述的是IP数据报。tcpdump以“IP地址.端口号”的形式来描述通信的某一端;以“>”表示数据传输的方向,“>”前面是源端,后面是目的端。可见,**个数据包是测试机器ernest-laptop(IP地址是192.168.1.108)向其**DNS服务器(IP地址是219.239.26.42)发送的DNS查询报文(目标端口53是DNS服务使用的端口,这一点我们在前面介绍过),第二个数据包是服务器反馈的DNS应答报文。
**个数据包中,数值57428是DNS查询报文的标识值,因此该值也出现在DNS应答报文中。“+”表示启用递归查询标志。“A?”表示使用A类型的查询方式。“www.baidu.com”则是DNS查询问题中的查询名。括号中的数值31是DNS查询报文的长度(以字节为单位)。
第二个数据包中,“3/4/4”表示该报文中包含3个应答资源记录、4个授权资源记录和4个额外信息记录。“CNAME www.a.shifen.com.,A 119.75.218.77,A 119.75.217.56”则表示3个应答资源记录的内容。其中CNAME表示紧随其后的记录是机器的别名,A表示紧随其后的记录是IP地址。该应答报文的长度为226字节。
注意 我们抓包的时候没有开启tcpdump的-X选项(或者-x选项)。如果使用-X选项,我们将能看到DNS报文的每一个字节,也就能明白上面31字节的查询报文和226字节的应答报文的具体含义。限于篇幅,这里不再讨论,读者不妨自己分析。
1.7 socket和TCP/IP协议族的关系
前文提到,数据链路层、网络层、传输层协议是在内核中实现的。因此操作系统需要实现一组系统调用,使得应用程序能够访问这些协议提供的服务。实现这组系统调用的API(Application Programming Interface,应用程序编程接口)主要有两套:socket和XTI。XTI现在基本不再使用,本书仅讨论socket。图1-1显示了socket与TCP/IP协议族的关系。
���socket定义的这一组API提供如下两点功能:一是将应用程序数据从用户缓冲区中复制到TCP/UDP内核发送缓冲区,以交付内核来发送数据(比如图1-5所示的send函数),或者是从内核TCP/UDP接收缓冲区中复制数据到用户缓冲区,以读取数据;二是应用程序可以通过它们来修改内核中各层协议的某些头部信息或其他数据结构,从而精细地控制底层通信的行为。比如可以通过setsockopt函数来设置IP数据报在网络上的存活时间。我们将在第5章详细讨论这一组API。
值得一提的是,socket是一套通用网络编程接口,它不但可以访问内核中TCP/IP协议栈,而且可以访问其他网络协议栈(比如X.25协议栈、UNIX本地域协议栈等)。 ……