OSI参考模型并非实际应用中的标准,而只是一种抽象化表示方法。目前真正被广泛使用的是TCP/IP参考模型,它是以OSI参考模型作为基础设计的。Internet的高速发展使TCP/IP参考模型被所有计算机所使用。
TCP/IP协议是一个协议集,其核心为TCP协议与IP协议。TCP/IP参考模型也是一个开放模型,能适用互联网等各种网络的需要,它具有如下4个特点。
TCP/IP是一种标准化的高级协议,同时提供了多种网络服务协议。
完善的网络地址分配方法,网络中每个点都具备独立的地址。
非专利技术,与操作系统及硬件结构无关。
与网络硬件无关,适合于各种网络结构。
TCP/IP参考模型有4个层次。其中应用层与OSI中的应用层对应,传输层与OSI中的传输层对应,网络层与OSI中的网络层对应,物理链路层与OSI中的物理层和数据链路层对应。TCP/IP中没有OSI中的表示层和会话层,如图所示。
1).应用层
应用层是TCP/IP参考模型的最高层,它向用户提供一些常用应用程序,如电子邮件等。应用层包括了所有的高层协议,并且总是不断有新的协议加入。应用层协议主要有:网络终端协议TELNET,用于实现互联网中的远程登录功能;文件传输协议FTP,用于实现互联网中交互式文件传输功能;简单电子邮件协议SMTP,实现互联网中电子邮件发送功能;域名服务DNS,用于实现网络设备名字到IP地址映射的网络服务;网络文件系统NFS,用于网络中不同主机间的文件系统共享。
2).传输层
传输层也称为TCP层,主要功能是负责应用进程之间的端-端通信。传输层定义了两种协议:传输控制协议TCP与用户数据包协议UDP。TCP协议是一种可靠的面向连接的协议,主要功能是保证信息无差错地传输到目的主机。UDP协议是一种不可靠的无连接协议,它与TCP协议不同的是它不进行分组顺序的检查和差错控制,而是把这些工作交给上一级应用层完成。
3).网络层
网络层又称为IP层,负责处理互联网中计算机之间的通信,向传输层提供统一的数据包。它的主要功能有以下3个方面:处理来自传输层的分组发送请求;处理接收的数据包;处理互联的路径。
4).物理链路层
物理链路层主要功能是接收IP层的IP数据包,通过网络向外发送,或接收处理从网络上传送来的物理帧,抽出IP数据包,向IP层发送。该层是主机与网络的实际连接层。
TCP/IP协议(Transmission ControlProtocol/Internet Protocol)是随着Internet而发展的网络协议,目前应用最为广泛。Internet最初是因为美国国防需要而建立的,用于保证美国政府的计算机网络间能够互通,并保证遭受核打击时不至于瘫痪。
TCP/IP很好地解决了不同网络互访问性和网路的健全性,领导着Internet发展。几乎所有的操作系统都支持TCP/IP协议,Linux系统更是将TCP/IP协议作为重要标准,成为了世界上最流行的网络服务器操作系统。
计算机网络技术在近50年的发展路程中,产生过多种不同的网络结构和通信协议,很多至今还在使用。
让不同网络可相互访问的结局方案有两种:第一种是选择一种组网络结构为标准,使所有网络都按照同一方法来组建。这种方案显然没有可行性,因为不但网络重建的费用太高,而且没有一种网络结构能满足所有应用。
因此,第二种方法被提出,该方法要求设计一种协议,能够让所有网络结构都能支持。TCP/IP协议由此诞生,解决网络互通问题的是IP协议。
IP协议又称为网际协议,对应于TCP/IP参考模型的网络层,是Internet中最重要的协议。IP协议规定数据包由数据包正文与报头两部分组成。数据包正文是要传递的数据,没有格式要求。报头包括发送主机的网络地址、接收主机的网络地址、数据包的报头校验和、数据包的长度信息。
IP协议的主要功能有数据包传输、数据包路由选择和拥塞控制。数据包采用“无连接”方式传递,即两台主机在通信之前不需要建立连接。网络主机间使用统一的IP数据包,这样能保持不同物理网络间能够传递和识别数据。如果目的地为同一网段的计算机,那么数据包将被直接传输过去。如果两台主机处于Internet上的不同子网中,IP协议将通过路由器获得主机间的传输路径,通过交换机或服务器接力的方式,将数据包传递过去。
路由器是网络中选择路径的专用计算机,它以图算法为核心,负责找到两点之间最短的距离。同时也会考虑网络的拥堵状况,找到实际最快的传输路径。一些比较大的数据被拆分为数据包后,很可能是以不同的路径传递到目的地。
IP互联网协议地址
所有Internet上的计算机都必须有一个Internet上唯一的编号作为其网络标识,这个编号称为IP地址。每个数据包中包含有发送方的IP地址和接收方的IP地址。IP地址是一个32位二进制数,即4个字节,为方便起见,通常将其表示为w.x.y.z的形式。其中,w、x、y、z分别为一个0至255的十进制整数,对应二进制表示法中的一个字节。这样的表示叫做点分十进制表示。
nIP地址的取得方式,简单地说是大的组织先向Internet的NIC(Network
Information Center)申请若干IP地址,然后将其向下级组织分配,下级组织再向更下一级的组织分配IP地址。各子网的网络管理员将取得的IP地址指定给子网中的各台计算机。IP地址分为3类。
1.A类地址
A类IP地址的最高位为0,其前8位为网络地址,是在申请地址时由管理机构设定的,后24位为主机地址,可以由网络管理员分配给本机构子网的各主机。A类地址的第一个十进制整数的值在1至126之间。一个A类地址最多可容纳224(约1600万)台主机,最多可有127个A类地址。当然这是纯从数学上讲的,事实上不可能达到,因为一个网络中有些地址另有特殊用途,不能分配给具体的主机和网络。下面在B类、C类地址中的数字也是同样的。
2.B类地址
B类IP地址的前16位为网络地址,后16位为主机地址,且第一位为1,第二位为0。B类地址的第一个十进制整数的值在128至191之间。一个B类网络最多可容纳216即65536台主机,最多可有214个B类地址。
3.C类地址
C类IP地址的前24位为网络地址,最后8位为主机地址,且第一位、第二位为1,第三位为0。C类地址的第一个整数值在192至223之间。一个C类网络最多可容纳28即256台主机,共有221个C类地址。
有几个特殊的IP地址,第一个是回送地址,该地址用于网络测试或本机进程间通信,十进制形式为127.0.0.1。第二个是广播地址,用于呼叫整个网络内的计算机,子网中最后一个地址即被用作广播地址,例如16.255.255.255用于A类网络16.0.0.0中所有计算机的呼叫。第三个是子网地址,用于识别子网,子网中第一个地址即是子网地址,例如192.168.0.0。
原始互联网使用的传输介质为电话线,计算机通过调制解调器将数值信号转为模拟信号,然后使用电流载波。因为电话线的噪声极大,很容易造成误码,因此TCP协议具有完善的循环校验机制。
TCP是重要的传输层协议,必须保证数据传递的完整性。另外,数据包报文中有计算机端口号信息,可以用来区别同一计算机上不同应用程序的数据。
数据包是很小的数据单位,而通过网络传递的连续数据往往是数据包长度的很多倍。因此,数据包报文中还有一个顺序编号,使接收的计算机能够根据编号重新按顺序还原数据。TCP协议的另一个重要功能就是把大的数据切成较小的数据包,或者将接收到的数据包按顺序还原为原始数据。如果发现某一个数据包丢失了,TCP协议会向源计算机发送请求,要求重新传递丢失的数据包。这种处理能力,被称之为全双工。
TCP协议最小的处理单位为字节,因此TCP是面向字节的顺序协议。数据包内的每个字节都会被分配一个顺序编号,以及为了验证数据真实性的奇偶校验位。虽然这种做法传递了过多的冗余数据,但根本原因是由早期网络极为不可靠造成的。
为可靠地完成数据传输任务,TCP将报文或数据分成可管理的长度并加上TCP头,并定义一些主要的字段,如图所示。
TCP报文中的字段定义如下。
源端口:源计算机指定的端口编号。
目的地端口:接收计算机的端口编号。
顺序号:分配给TCP包的编号。
应答号:接收计算机向源计算机发送的编号。
偏移位:指出TCP
头的长度(即TCP头中的32位字的数)。它表明数据开始和TCP头结束。对于正常的20
字节的头,这个字段设置成0101。
保留位:为将来使用而保留。必须设置为0。
控制位:用作个别控制位,如表所示。
窗口号:窗口字段也称接收窗口大小,表示在TCP连接上准备由主机接收的8位字节的数目。
校验位:一个差错检验数,用于确定被接收的数据包文在传输期间是否被讹误。包括TCP头和所有数据。
紧急指针:它指出了紧接紧急数据的字节的顺序编号。
可选项:长度变量,它考虑到TCP使用的各种选项:选项表的结束、无操作、最大分段长度。
TCP提供的主要服务有:
建立、维持和终结两个进程之间的连接。
可靠的包传递(经过确认过程)。
编序包(可靠的数据传送)。
控制差错的机制。
通过使用端口,允许在个别的源和目的地主机内部实现和不同进程多重连接的能力。
使用全双工操作的数据交换。
UDP协议:
UDP又称用户数据包文协议,也是TCP/IP的传输层协议,它是无连接的、不可靠的传输服务。
当接收数据时它不向发送方提供确认信息,它不提供输入包的顺序。如果出现丢失包或重复包的情况,也不会向发送方发出差错报文,与IP协议非常类似。
UDP的主要作用是分配和管理端口编号,以正确无误地识别运行在网络站点上的个别应用程序。由于它执行功能时具有低的开销,因而执行速度比TCP快。它多半用于不需要可靠传输的应用程序,例如网络管理域、域名服务器等。
任何与UDP相配合作为传输层服务的应用程序必须提供确认和顺序系统,以确保数据包是以发送时的顺序到达。也就是说,使用UDP的应用程序必须提供这类服务。传输层具有独特的、与所有其它层不相关的帧头。UDP报头及其数据被封装在IP报头内,由IP协议将这个数据包文发送到数据链路层,依次下去,数据链路层又使用它的帧头包装这个报文,最后将数据送到物理层实际传输。
当数据包被接收时,数据链路层将把地址解释为它自己的,剥去它的帧头,将包传递给IP层。IP层将根据IP报头上的正确IP地址接受包。剥去它的报头,最后将数据包交给UDP软件,UDP软件接受包必须按UDP报头上的端口编号进行译码。
3 .Socket套接字
Socket套接字由远景研究规划局(Advanced Research ProjectsAgency, ARPA)资助加利福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。
设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了Socket套接字。
Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件sys/socket.h中。
可见,Socket的通信机制与电话交换机制非常相似。Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。每一个Socket都用一个半相关描述。
{协议,本地地址,本地端口}。
一个完整的Socket则用一个相关描述:
{协议,本地地址,本地端口,远程地址,远程端口}。
每一个Socket有一个本地的唯一Socket号,由操作系统分配。
套接字有3种类型:流式套接字(SOCK_STREAM)、数据包套接字(SOCK_DGRAM)和原始套接字。
流式套接字提供可靠的、面向连接的通信流。如果通过流式套接字发送了顺序的数据:1、2。那么数据到达远程时候的顺序也是1、2。流式套接字可用于Telnet远程连接、WWW服务等需要使数据顺序传递的应用,它使用TCP协议保证数据传输的可靠性。
流式套接字的工作原理如图所示,网络中的两台主机分别作为服务器和客户机。
数据包套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠性。
数据包套接字使用数据包协议UDP,数据只是简单地传送到对方。
数据包套接字的工作原理如图所示。
(1).socket套接字的简介
原始套接字允许对底层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。
原始套接字主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。
(2).创建套接字
套接字是通过标准的UNIX文件描述符和其它的程序通信的一个方法。套接字在使用前必须先被建立,建立套接字的系统调用为socket(),它的一般形式是:
int socket(int domain,
int type,
int protocol);
创建出来的套接字是一条通信线路的一个端点,domain参数负责指定地址族,type参数负责指定与这个套接字一起使用的通信类型,而protocol参数负责制定所使用的协议。domain参数的取值范围如表所示。
最常用的套接字是AF_UNIX和AF_INET,前者用于通过UNIX文件系统实现的本地套接字,后者用于UNIX网络套接字。
AF_INET套接字可以用在穿过包括Internet在内的各种TCP/IP网络而进行通信的应用程序中。
参数type指定了与新套接字对应的通信特性。它的取值范围为枚举常量SOCK_STREAM和SOCK_DGRAM。
SOCK_STREAM是一个有序的、可靠的、基于连接的双向字节流。对于一个AF_INET域的套接字来说,如果在两个流式套接字的两端之间建立的是一个TCP连接,连接时默认值即为该特性。SOCK_DGRAM是一个数据报服务,可以用来发送最大长度是一个固定值的消息,但消息是否会被送达或者消息的先后次序是否会在网络传输中被重新安排并没有保证。对于AF_INET域的套接字来说,这种类型的通信是由UDP提供的。
通信所用的协议通常是由套接字的类型和套接字的域来决定,如果还有其它的协议可以选择,那么就在protocol参数里设置。protocol参数默认值为0,表示使用默认的协议。
socket系统调用返回的是一个描述符,它与文件描述符非常相似。当这个套接字和通信线路另一端的套接字连接好以后,就可以进行数据的传输和接收操作了。
(3).套接字地址
每个套接字域都有独特的地址格式。对于一个AF_UNIX套接字来说,它的地址是由一个包含在sys/un.h头文件里的sockaddr_un结构描述的。该结构的定义为:
structsockaddr_un
{
sa_family_tsun_family;
// AF_UNIX
char
sun_path[];
// 路径
};
因为不同类型的地址都需要传递到对套接字进程处理的系统调用里去,所以定义各种地址格式时使用的结构也都很相似,每个结构的开始都是一个定义地址类型(即套接字域)的数据项。sun_family_t是由X/Open技术规范定义的,在Linux系统上,它被声明为一个short类型。sun_path给出的路径长度是有限制的,Linux规定其最长不能超过108个字符。因为地址结构在长度方面是不固定的,所以许多套接字调用都要用到或输出一个用来复制特定地址结构的长度值。
AF_INET域里的套接字地址是由一个定义在netinet/in.h头文件里的sockaddr_in结构确定的。该结构的定义为:
structsockaddr_in {
short
intsin_family;
// AF_INET
unsigned short
intsin_port;
// 端口号
structin_addrsin_addr;
// Internet地址
};
其中Internet地址是netinet/in.h头文件中定义的另一个结构体,该结构体的定义为:
structin_addr {
unsigned long
ints_addr;};
一个AF_INET套接字完全可以由它的域、IP地址和端口号确定下来。从应用程序的角度看,各种套接字的行为就像是文件描述符,用一个独一无二的整数就可以把它们表示出来。
(4).套接字的名字
要使socket()调用创建的套接字能够被其它进程使用,程序就必须给该套接字起个名字。AF_UNIX套接字会关联到一个文件系统的路径名上去,AF_INET套接字将关联到一个IP端口号上去。为套接字命名可使用bind()系统调用,它的一般形式如下:
int bind(int socket, const
structsockaddr*address,
size_taddress_len);
bind()系统调用的作用是把参数address中给出的地址赋值给与文件描述符socket相关联的未命名套接字。地址结构的长度是通过address_len参数传递的。地址的长度和类型取决于地址族。bind()调用需要用一个与之对应的地址结构指针指向真正的地址类型。该调用成功时将返回0,否则返回–1,并将errno变量设置为表中的值。
AF_UNIX套接字对应的错误代码比上表要多出两个,分别是EACCESS,表示权限不足,不能创建文件系统中使用的名字;ENOTDIR/ENAMETOOLONG,表示路径错误或路径名太长。
(5).创建套接字队列
为能够在套接字上接受接入的连接,服务器程序必须创建一个队列来保存到达的请求。创建队列可使用系统调用listen()完成,它的一般形式为:
int listen(int socket, int backlog);
Linux系统可能会对队列里能够容纳的排队连接的最大个数有限制。在这个最大值的范围内,listen()将把队列长度设置为backlog个连接。在套接字上排队的接入连接个数最多不能超过这个数字,再往后的连接将被拒绝,用户的连接请求将会失败。这是listen()提供的一个机制,在服务器程序紧张地处理着上一个客户的时候,后来的连接将被放到队列里排队等号。backlog常用的值是5。
listen()函数成功时会返回0,否则返回–1,它的错误代码包括EBADF、EINVAL和ENOTSOCK,含义同bind()系统调用的错误代码相同。
(6).接受连接
服务器上的应用程序创建好命名套接字之后,就可以通过accept()系统调用来等待客户端程序建立对该套接字的连接了。 accept()的一般形式是:
int accept(int socket,
structsockaddr*address,
size_t*address_len);
accept()系统调用会等到有客户程序试图连接到由socket参数指定的套接字时才返回。该客户就是套接字队列里排在第一位的连接。
accept()函数将创建出一个新的套接字来与该客户进行通信,返回的是与之对应的文件描述符。新套接字的类型与服务器监听套接字的类型相同。
(6).请求连接
当客户想要连接到服务器的时候,它会尝试在一个未命名套接字和服务器的监听套接字之间建立一个连接。它们用connect()系统调用来完成这一工作,它的一般形式是:
int connect(int socket, const
structsockaddr*address,
size_taddress_len);
参数socket指定的套接字将连接到参数address指定的服务器套接字上去,服务器套接字的长度由参数address_len指定。套接字必须是通过socket调用获得的一个有效的文件描述符。如果操作成功,函数返回0,否则返回–1。该函数产生的错误代码如表所示。
如果连接不能立刻建立起来,connect()会阻塞一段不确定的倒计时时间,倒计时结束后,连接就会失败。若connect()调用是被信号所中断,且该信号得到了处理,connect还是会失败,但这次连接尝试是成功的,它会以异步方式继续尝试。
类似于accept()调用,connect()的阻塞特性可以用设置该文件描述符的O_NONBLOCK标志的办法来改变。在这种情况下,如果连接不能立刻建立起来,connect()会失败并把errno变量设置为EINPROGRESS,而连接将以异步方式继续尝试。
异步连接的处理是比较困难的,可以在套接字文件描述符上用一个select()调用来表明该套接字已经处于写就绪状态。