《TCP/IP详解 卷1:协议》第1章 笔记

1. 概述

计算机网络是计算机技术和通信技术紧密结合的产物。

1.2 分层

协议族:比如TCP/IP,是一组不同层次上的多个协议的组合。

TCP/IP通常被认为是一个四层协议系统:

链路层:有时也被称作数据链路层网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡处理与电缆(或其他任何传输媒介)的物理接口细节

网络层处理分组在网络中的活动。网络层协议包括:IP协议(网际协议)、ICMP协议(Internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)。

运输层为两台主机上的应用程序提供端到端的通信。包括:TCP(传输控制协议)和UDP(用户数据报协议)。

应用层处理特定的应用程序细节。包括:telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传送协议)、SNMP(简单网络管理协议)等。

应用程序通常是一个用户进程,而下三层则一般在(操作系统)内核中执行。应用层关心的是应用程序的细节,而不是数据在网络中的传输活动。

构造一个互联网最简单的方法是把两个或多个网络通过路由器进行连接。它是一种特殊的用于网络互连的硬件盒。路由器的好处是为不同类型的物理网络提供连接:以太网、令牌环网、点对点的连接和FDDI(光纤分布式数据接口)等等。

关于网关的两个概念:

从历史上说,IP路由器被称为网关,在很多TCP/IP文献中都使用这个术语。现在网关这个术语用来表示应用层网关,一个连接两种不同协议族的进程。例如,TCP/IP和IBM的SNA。

过去网关指的是IP路由器,它的功能是连接异构网络,是一种硬件;现在的网关指的是连接异构网络协议的进程, 是一种软件。

应用层和传输层使用端到端协议。网络层提供的是逐跳(Hop-by-hop)协议。

网络层IP提供的是一种不可靠的服务。它只是尽可能快地把分组从源节点送到目的节点,但是并不提供任何可靠性保证。TCP在不可靠的IP层上提供了一个可靠的传输层

一个路由器具有两个或多个网络接口层(因为它连接了两个或多个网络)。一个主机也可以有多个接口,但一般不称作为路由器,除非它的功能只是单纯地把分组从一个接口传送到另一个接口。路由器并不一定指那种在互联网中用来转发分组的特殊硬件盒。大多数的TCP/IP实现允许一个多接口主机来担当路由器的功能。但是主机为此必须进行特殊的配置。在这种情况下,我们既可以称该系统为主机(当它运行一个应用程序时),也可以称之为路由器(当它把分组从一个网络转发到另一个网络时)。

连接网络的另一个途径是使用网桥。网桥是在链路层上对网络进行互连,而路由器则是在网络层上对网络进行互连。网桥使得多个局域网(LAN)组合在一起,这样对上层来说就好像是一个局域网。TCP/IP更倾向于使用路由器而不是网桥来连接网络

1.3 TCP/IP分层

ICMP是IP协议的附属协议。IP层用它来与其他主机或路由器交换错误报文和其他重要信息。尽管ICMP主要被IP使用,但应用程序也有可能访问它。Ping和Traceroute,它们都使用ICMP

IGMP是Internet组管理协议。它用来把一个UDP数据报多播到多个主机

ARP(地址解析协议)和RARP(逆地址解析协议),用来转换IP地址和网络接口层使用的地址(MAC地址)。

1.4 互联网的地址

IPv4的32位地址通常写成4个十进制数。即一个字节(8位)对应一个十进制数。这种表示方法称为“点分十进制表示法”。

多接口主机具有多个IP地址,其中每个接口都对应一个IP地址。

IP地址的管理机构是互联网络信息中心(Internet Network Information Center),称为InterNIC。InterNIC只分配网络号,主机号的分配由系统管理员来负责。

有三类IP地址:单播地址(目的端为单个主机)、广播地址(目的端为给定网络上所有主机)以及多播地址(目的端为同一组内的所有主机)。

在这里插入图片描述

1.5 域名系统

在一个互联网上,每个接口都用IP地址来标识,尽管用户习惯使用主机名而不是IP地址。域名系统为主机名和IP地址之间提供动态的映射。在TCP/IP领域中,域名系统(DNS)是一个分布的数据库,由它来提供IP地址和主机名之间的映射信息

任何应用程序都可以调用一个标准的库函数来查看给定名字的主机的IP地址。系统还提供一个逆函数——给定主机的IP地址,查看它所对应的主机名。大多数使用主机名作为参数的应用程序也可以把IP地址作为参数。例如,Telnet进行远程登录时,既可以指定一个主机名,也可以指定一个IP地址。

操作系统提供接口gethostbyname用来查看给定名字的主机IP地址。man手册对其接口定义及描述如下:

#include <netdb.h>
extern int h_errno;

struct hostent *gethostbyname(const char *name);

The gethostbyname() function returns a structure of type hostent for the given host name. Here name is either a hostname, or an IPv4 address in standard dot notation (as for inet_addr(3)), or an IPv6 address in colon (and possibly dot) notation. (See RFC 1884 for the description of IPv6 addresses.) If name is an IPv4 or IPv6 address, no lookup is performed and gethostbyname() simply copies name into the h_name field and its struct in_addr equivalent into the h_addr_list[0] field of the returned hostent structure. If name doesn’t end in a dot and the environment variable HOSTALIASES is set, the alias file pointed to by HOSTALIASES will first be searched for name (see hostname(7) for the file format). The current domain and its parents are searched unless name ends in a dot.

调用gethostbyname接口,可以返回给定主机名的hostent结构体(host entry)。给定的名字可以是主机名,也可以是标准格式的IPv4或IPv6地址。当给定名字是IP地址时,接口并不会执行查找动作,而是将ip地址简单拷贝到结果的h_name域,将struct in_addr(网络字节序的IP地址)拷贝到结果的h_addr_list[0] 域。如果名字不以点结尾并且环境变量设置了HOSTALIASES,首先搜索HOSTALIASES指向的别名文件的名字。除非名字以点结尾,否则当前域及其双亲域都会查找。

主机名host entry结构体的定义如下:

The hostent structure is defined in <netdb.h> as follows:

struct hostent {
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */

struct in_addr结构体定义如下:

typedef uint32_t in_addr_t;

struct in_addr {
    in_addr_t s_addr;
};

gethostbyname.c:

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

extern int h_errno;

void hostinfo_print(const struct hostent *ent_ptr)
{
    int i;

    printf("[hostname] %s\n", ent_ptr->h_name);

    for(i = 0; ent_ptr->h_aliases[i]; ++i)
        printf("[host alias %d] %s\n", i, ent_ptr->h_aliases[i]);

    for(i = 0; ent_ptr->h_addr_list[i]; ++i)
        printf("[host addr %d] %s\n", i, inet_ntoa(*(struct in_addr *)ent_ptr->h_addr_list[i]));
}

int main(int argc, char *argv[])
{
    struct hostent *ent_ptr;

    if(argc < 2) {
        perror("Usage: <cmd> <hostname>");
        exit(EXIT_FAILURE);
    }

    if(!(ent_ptr = gethostbyname(argv[1]))) {
        herror(argv[0]);
        exit(h_errno);
    }

    hostinfo_print(ent_ptr);

    return 0;
}

运行结果如下:

etc@ruc-etc:~$ cc gethostbyname.c -o gethostbyname -Wall -g
etc@ruc-etc:~$ ./gethostbyname www.qq.com
[hostname] ins-r23tsuuf.ias.tencent-cloud.net
[host alias 0] www.qq.com
[host addr 0] 111.30.178.240
[host addr 1] 111.30.185.195
etc@ruc-etc:~$ ./gethostbyname 111.30.178.240
[hostname] 111.30.178.240
[host addr 0] 111.30.178.240

操作系统还提供一个逆函数gethostbyaddr,功能是给定主机的IP地址,查看它所对应的主机名。man手册对其接口定义及描述如下:

The gethostbyaddr() function returns a structure of type hostent for the given host address addr of length len and address type type. Valid address types are AF_INET and AF_INET6. The host address argument is a pointer to a struct of a type depending on the address type, for example a struct in_addr * (probably obtained via a call to inet_addr(3)) for address type AF_INET.

给定主机IP地址addr,地址的长度len以及地址类型type就能得到主机的hostent结构体(host entry)。合法的地址类型为AF_INET(IPv4)以及AF_INET6(IPv6)。地址参数addr是一个指向特定结构的指针,这个结构取决于地址类型。比如说,当地址类型为AF_INET(IPv4)时,其地址参数addr的类型为struct in_addr *。

gethostbyaddr.c:

#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void hostinfo_print(const struct hostent *ent_ptr)
{
    int i;

    printf("[hostname] %s\n", ent_ptr->h_name);

    for(i = 0; ent_ptr->h_aliases[i]; ++i)
        printf("[host alias %d] %s\n", i, ent_ptr->h_aliases[i]);

    for(i = 0; ent_ptr->h_addr_list[i]; ++i)
        printf("[host addr %d] %s\n", i, inet_ntoa(*(struct in_addr *)ent_ptr->h_addr_list[i]));
}

int main(int argc, char *argv[])
{
    struct hostent *ent_ptr;
    struct in_addr inet_addr;

    if(argc < 2) {
        perror("Usage: <cmd> <hostname>");
        exit(EXIT_FAILURE);
    }

    if(!(inet_aton(argv[1], &inet_addr))) {
        perror("inet_aton");
        exit(EXIT_FAILURE);
    }

#ifdef DEBUG
    printf("%s\n", inet_ntoa(inet_addr));
    printf("%x\n", inet_addr.s_addr);
#endif

    if(!(ent_ptr = gethostbyaddr(&inet_addr, sizeof(inet_addr), AF_INET))) {
        herror(argv[0]);
        exit(h_errno);
    }

    hostinfo_print(ent_ptr);

    return 0;
}

运行结果如下:

etc@ruc-etc:~$ cc gethostbyaddr.c -o gethostbyaddr -Wall -g
etc@ruc-etc:~$ ./gethostbyaddr 111.30.178.240
./gethostbyaddr: Unknown host
etc@ruc-etc:~$ cc gethostbyaddr.c -o gethostbyaddr -Wall -g -D DEBUG
etc@ruc-etc:~$ !./
./gethostbyaddr 111.30.178.240
111.30.178.240
f0b21e6f
./gethostbyaddr: Unknown host
etc@ruc-etc:~$ ./gethostbyaddr 127.0.0.1
127.0.0.1
100007f
[hostname] localhost
[host addr 0] 127.0.0.1

由上述结果不难看出,IPv4地址长度是4个字节,实际上用一个32位无符号整型uint32_t存储它。对于一个IPv4的地址127.0.0.1,我们按照从左至右的阅读习惯,将第一个字节127,即0x7F当做数据的高位,那么可将该IPv4地址映射成一个32位无符号整型数0x7F000001。实际上在内存中,低地址空间存放0x7F,次低地址空间存放0x00…高地址空间存放0x01,这是按大端字节序(网络字节序)的方式存储。另外,gethostbyaddr可以反向解析回环地址127.0.0.1,但是还不能够逆向解析出111.30.178.240。

1.6 封装

当应用程序用TCP传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当做一串比特流送入网络。TCP传给IP的数据单元称作TCP报文段或简称为TCP段(TCP Segment)。IP传给网络接口层的数据单元称作数据报(IP datagram)。通过以太网传输的比特流称作(Frame)。

以太网数据帧的物理特性是其长度必须在46-1500字节之间。4.5节遇到最小长度的数据帧,在2.8节中遇到最大长度的数据帧。

网络接口层之间传送的数据单元应该是分组(packet)。分组既可以是一个IP数据报,也可以是IP数据报的一个片(fragment)。11.5节讨论IP数据报分片的详细情况。

UDP数据和TCP数据基本一致。唯一不同的是UDP传给IP的信息单元称作UDP数据报(UDP datagram),而且UDP的首部长为8字节。

IP在首部中存入一个长度为8bit的数值,称作协议域1表示ICMP协议2表示IGMP协议6表示为TCP协议17表示为UDP协议

TCP和UDP都用一个16bit的端口号来表示不同的应用程序。TCP和UDP把源端口号和目的端口号分别存入报文首部中。

1.7 分用

当目的主机收到一个以太网数据帧时,数据就开始从协议栈中由底向上升,同时去掉各层协议加上的报文首部。每层协议都要去检查报文首部中的协议标识,以确定接受数据的上层协议。这个过程称作分用(Demultiplexing)。

为协议ICMP和IGMP定位一直是一件很棘手的事情。把它们与IP放在同一层上,那是因为事实上它们是IP的附属协议;把它们放在IP层上面,是因为ICMP和IGMP报文都被封装在IP数据报中。

1.9 端口号

TCP和UDP采用16bit的端口号来识别应用程序(16位即64K/65536个端口号)。

FTP服务器的TCP端口号是21;每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。知名端口号介于1-255之间。256-1023之间的端口号通常都是由Unix系统占用,以提供一些特定的Unix服务。

客户端通常对它所使用的端口号并不关心,只需保证该端口号在本机上是唯一的就可以了。客户端口号又称作临时端口号(即存在时间很短暂)。因为它通常只是在用户运行该客户程序时才存在,而服务器则只要是主机开着的,其服务就运行。

大多数TCP/IP实现给临时端口号分配1024-5000之间的端口号大于5000的端口号是为其他服务器预留(Internet上并不常用的服务)。

大多数Unix系统文件/etc/services都包含了人们熟知的端口号:

etc@ruc-etc:~$ grep http /etc/services 
# Updated from http://www.iana.org/assignments/port-numbers and other
# sources like http://www.freebsd.org/cgi/cvsweb.cgi/src/etc/services .
http		80/tcp		www		# WorldWideWeb HTTP
http		80/udp				# HyperText Transfer Protocol
https		443/tcp				# http protocol over TLS/SSL
https		443/udp
http-alt	8080/tcp	webcache	# WWW caching service
http-alt	8080/udp

Unix系统有保留端口号的概念。只有具有超级用户特权的进程才允许给它自己分配一个保留端口号。这些端口号介于1-1023之间,一些应用程序(如Rlogin)将它作为客户与服务器之间身份认证的一部分。

1.12 标准的简单服务

如果仔细检查这些标准的简单服务以及其他标准的TCP/IP服务(如Telnet、FTP、SMTP等)的端口号时,我们发现它们都是奇数。因为这些端口号都是从NCP端口号派生出来的(NCP,即网络控制协议,是ARPNET的传输层协议,是TCP的前身)。NCP是单工的,因此每个应用程序需要两个连接,需要预留一对奇数和偶数端口号。当TCP和UDP成为标准的传输层协议时,每个应用程序只需要一个端口号,因此就使用了NCP中的奇数。

1.13 互联网

internet意思是用一个共同的协议族把多个网络连接在一起。而Internet指的是世界范围内通过TCP/IP互相通信的所有主机集合(超过1000万台)。Internet是一个internet,但internet不等于Internet。

1.15 应用程序接口

使用TCP/IP协议应用程序通常采用两种应用程序接口(API):socketTLI(传输层接口:Transport Layer Interface)。前者有时称作“Berkeley socket”,表明它是从伯克利版发展而来的。后者起初是由AT&T开发的,有时称作XTI(X/Open传输层接口),以来承认X/Open这个自己定义标准的国际计算机生产商所做的工作。XTI实际上是TLI的一个超集。

话说Glibc全部或部分实现了POSIX、BSD 、X/Open等标准,包括线程不同标准的实现也是不同的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书是“TCP/IP详解系列”,本书分成三个部分,每个部分覆盖了不同的内容。 (1) TCP事务协议,通常叫做T/TCP。这是对TCP的扩展,其设计目的是使客户-服务器事务更快、更高效和更可靠。这个目标的实现省略了连接开始时TCP的三次握手,并缩短了连接结束时TIME_WAIT状态的持续时间。我们将会看到,在客户-服务器事务中,T/TCP的性能与UDP相当,而且T/TCP具有可靠性和适应性,这两点相对UDP来说都是很大的改进。 事务是这样定义的:一个客户向服务器发出请求,接下来是服务器给出响应(这里的名词“事务”(transaction)并非数据库中的事务处理,数据库中的事务处理有封锁、两步提交和回退)。 (2) TCP/IP应用,特别是HTTP(超文本传送协议,WWW的基础)和NNTP(网络新闻传送协议,Usenet新闻系统的基础)。 (3) Unix域协议。这些协议是所有Unix中的TCP/IP实现中都提供的,在许多非Unix的实现中也都提供。这些协议提供了进程之间通信(IPC)的一种手段,采用了与TCP/IP中一样的插口接口。当客户与服务器进程在同一主机上时,Unix域协议通常要比TCP/IP快一倍。 第一部分,即对T/TCP的介绍,又分成两个小部分。第1~4介绍协议,并给出了大量实例来说明它们是怎样工作的。这些材料主要是对1中24.7节的补充,在那里对T/TCP只是做了简单的介绍。第2小部分,即第5~12,介绍T/TCP在4.4BSD-Lite网络代码(即,2中给出的代码)中的确切实现。由于最早的T/TCP实现迟至1994年9月才发布,已经是本书1出版一年以后了,那时2也快完成了,因此T/TCP的详细叙述,包括诸多实例和所有的实现细节都只好放在本系列书的3中了。 第二部分,即HTTP和NNTP应用,是1的第25~30中介绍TCP/IP应用的延续。在1出版后的两年里,随着Internet的发展,HTTP得到了极大的流行,而NNTP的使用则在最近的10多年中每年增长了大约75%。T/TCP对HTTP来说也是非常好的,可以这样来用TCP:在少量数据传输中缩短连接时间,因为这种时候连接的建立和拆除时间往往占总时间的大头。在繁忙的Web服务器上,成千上万个不同而且不断变化的客户对HTTP(因此也对TCP)的高负荷使用,也提供了唯一可以对服务器上确切的分组进行考查的机会(第14),可以观察1和2中给出的TCP/IP的许多特性。 第三部分中的Unix域协议原本是准备在2中介绍的,但由于2已多达1200页而删去了。在书名为《TCP/IP详解》这样的系列书中夹杂着TCP/IP以外的协议不免令人生奇,但Unix域协议几乎15年前就已经伴随着BSD版TCP/IP的实现在4.2BSD中发布了。今天,它们在任何一个从伯克利衍生而来的内核中都在频繁地使用,但它们的使用往往“被掩盖在后台”,大多数用户不知道它们的存在。除了在从伯克利衍生而来的内核中充当Unix管道的基础外,它们的另一个大用户是当客户程序和服务器程序在同一主机(典型的情况是工作站)上时的X Window系统。Unix域的插口也用于进程之间传递描述符,也是进程之间通信的一个强大工具。由于Unix域协议所用的插口API(应用编程接口)与TCP/IP所用的插口API几乎是相同的,Unix域协议以最小的代码变化提供了一个简单的手段来增强本地应用的性能。 以上三个部分的每个部分都可以独立阅读。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值