六、传输层:
6.1、传输层的意义:
首先注意,传输层只针对于网络层是IP协议的传输通道而言的,比如自定义以太网类型的以太网报文、ARP报文都不需要传输层。
网络层实现了网络中每个主机(节点)之间的报文送达,但真正使用这些报文的是每个主机的一个个的应用程序,无法用网络层的IP地址标识这一个个的应用程序,这就需要传输层,引入了端口的概念,好比网络层能实现城市之间的铁路交通,由网络层可实现从一个城市的火车站到另一个城市的火车站,而下了火车到这个城市的各个地方就需要传输层来实现,所以可以理解为,要去某个地方,目的地址是:该城市和该城市(网络层的目的IP)的某个地方(传输层的目的端口);
传输层协议包括TCP、UDP,还有一种由应用程序配置和解析的不带传输层协议头的原始IP报文(用于实现一些特殊应用如ICMP),其中TCP协议为面向连接的传输协议,其连接的创建和释放都有明确的交互规范,报文传输过程中也含有细致的流控、拥塞避免机制,不会丢包,而UDP和RAW IP均没有这些机制。总之可以理解为,如果应用程序使用TCP协议,那么它会得到内核协议栈提供的许多“保障”,但因为这些“保障”的环节所以比UDP稍慢并且环节复杂需要应用程序额外做的事情也更多,而UDP则整好相反,应用程序需要做的很少并且速度快,但就没了TCP的“保障”。
说到传输层不得不提socket,传输层再往上是socket层,socket层是应用程序访问TCP/IP协议栈的接口,应用程序需要收发什么样的报文,比如是ARP协议报文,还是自定义以太网类型的以太网报文,还是IP报文或是其他什么报文,如果是IP报文的话,是使用TCP协议传输还是UDP协议……每一个报文流的传输通道都需要应用程序去确定,而确定的方式是应用程序在TCP/IP协议栈上去配置这一个个报文流的传输通道,配置的方式就是socket编程接口,不仅是linux,大多数OS,应用程序都是通过socket接口配置报文流的传输通道,所以基于socket接口的网络编程是网络应用程序最重要的重点之一;
所以,传输层和socket层是互相连接的,传输层本身就是为了应用程序服务,它引出的端口的概念就是为了标识不同的应用程序;所以传输层自身的框架和socket层有很多共同起作用的部分,个人认为这部分和TCP协议的部分是全面理解传输层的两大重点,而且这部分对socket层的理解有极大的帮助。
6.2、传输层的框架:
6.2.1、由UDP看传输层的框架:
下面是个比较熟悉的代码片段,分别是UDP通讯的server端和client端伪代码:
首先服务器和客户端均通过socket系统调用创建了一个“网络层为IP协议,传输层为UDP协议的数据报”的传输通道,此时该传输通道既不知道源地址是什么,也不知道目的地址是什么;
然后服务器端通过bind系统调用绑定了自己的地址,这个地址包括:这个服务器所处主机的IP地址、这个服务器应用程序的端口号,这将在内核中注册一个等待接收发往该地址报文的“需求”,之后如果传输层收到了这样的报文,就会把报文打入这个“需求”供应用程序获取;
与此同时客户端并没有bind,这并不是说客户端不需要bind,事实上它一会调用send/sendto时就自然会bind,只是它不需要现在就bind罢了,此时的客户端有可能会执行connect系统调用,这将告诉内核这个传输通道的目的地址即服务器的地址(服务器主机IP和服务器应用程序的端口号),这样以后发送报文时,无需再必须使用sendto指定目的地址;
至此,服务器和客户端的情况分别如下:
服务器创建了一个传输通道,它所在机器的内核已经知道了有这么一个应用程序需要接收目的地址为server_addr的报文;
客户端创建了一个传输通道,它所在机器的内核可能已经知道了有这么一个应用程序想要给目的地址为server_addr的应用程序发报文(如果调用了connect系统调用的话);
接下来客户端就可以发送报文了,如果它之前没有进行过connect,这里它的内核会自动给它bind,注意客户端的端口号就由内核自动选取了,然后它的内核会根据服务器的地址在路由表中找一个路由,然后发出报文;
同期服务器的传输层接收到客户端发送的这个报文,然后从已经bind在内核中的“需求”中查找是否存在匹配这个报文的,找到后把这个报文打入这个“需求”;然后把客户端的地址client_addr记录下来;
之后服务器可能会给客户端发送报文,这时它已经知道了这个客户端的地址client_addr,所以可以顺利发送;同时客户端已经bind过了自己的地址client_addr,所以可以顺利接收;
以上就是服务器和客户端的UDP/IP传输通道的创建和形成的过程,有些内容是socket层的,有些内容是传输层的,这里重点讨论下传输层相关的内容,包括传输层报文的接收和发送,这包括传输层和网络层的接口、传输层和socket层的接口、传输层框架。
另外之所以用UDP举例,是因为UDP协议事实上没有什么协议,下面是UDP协议头,可见它只包含传输层共有的内容:端口:6.2.1.1、传输层内容在内核的初始化:
传输层相关内容的初始化并不在一个独立的模块中,因为传输层是以网络层为IP协议为前提的,所以它的初始化也和IP协议初始化在一起,下面是对IP协议的初始化(inet_init函数)进行分解描述:
1、三类传输层ops:我们重点看的不是函数proto_register如何创建三种传输层协议ops的slab和如果注册传输层协议ops,而是要记住,这三个东西就是传输层协议,和传输层相关的函数挂在这里;
2、接收方向上,网络层与传输层的接口:首先是sock_register函数,它是网络层的内容,注册了创建AF_INET族套接字在内核将调用函数inet_create,这是个很重要的东西,然后的ip_static_sysctl_init显然是sysctl相关的调试手段内容;
我们关注的重点是后边的inet_add_protocol函数注册四个传输层报文处理函数,注意这里注册了接收方向上传输层和网络层的接口,注册之后,接收方向上,在网络层到传输层的最后一站----函数ip_local_deliver_finish中,就可以根据报文的IP协议头中指示的传输层协议号知道应该把报文交给哪个传输层报文处理函数,代码中IPPROTO_ICMP、IPPROTO_IGMP、IPPROTO_TCP、IPPROTO_UDP分别是相应协议id,分别由函数icmp_rcv、igmp_rcv、tcp_v4_rcv、udp_rcv函数处理接收到的报文;
3、注册三类传输层ops:真正常用的socket只有三种,流式套接字、数据报套接字、原始套接字,大多数情况下,对于TCP协议使用流式套接字,UDP协议使用数据报套接字,原始套接字用于既不是TCP也不是UDP的原始IP报文,用于一些其他情况;记住这里把保存了三种常用套接字的描述符复制到inetsw数组的对应成员中;
这里真正要关注的重点是这三种常用套接字的描述符:它们用于控制IP报文的传输控制,标识了三种不同传输控制方式的IP报文对应的socket编程接口类型及其ops、传输层协议及其ops,如下图:上图中比较重要的是:type字段标识了不同的套接字类型,ops字段标识了不同套接字类型的不同操作ops;而protocol标识了传输层协议类型,注意raw方式的IPPROTO_IP值为0意为原始IP报文,prot字段标识了不同传输控制方式的专用ops,它们之前的proto_register函数中出现过;
arp_init、ip_init分别是ARP协议和IP协议相关,和传输层无关,tcp_v4_init是TCP协议初始化,udp_init是UDP协议初始化,udplite4_register是udplite的初始化,udplite是一种轻量级UDP暂不用分析,icmp_init是ICMP协议初始化;事实上这些传输层协议没有什么需要初始化的,主要是slab、hash表和调试手段的初始化;
ip_mr_init是IP组播路由的初始化,ipv4_proc_init是proc文件系统中IPv4协议的调试手段初始化,ipfrag_init是IP协议分帧功能的初始化,最后把IP协议的以太网类型0x0800注册进链路层。
综上所述,协议栈中传输层部分的重点是:
1、流式套接字、数据报套接字、原始套接字的ops、不同传输层协议的专用ops,用于应用程序通过socket层配置;
2、网络层和传输层之间的接收方向的接口,根据不同的传输层协议定义;
3、传输层依赖于网络层IP协议,所以应用程序的socket编程接口在配置实现IP传输通道时,是同时针对IP部分和传输层部分;
接下来仍以4.2.1中的UDP通讯的例子,一步步描述传输层的框架,这里将包括不少socket层的内容;