传输层在协议栈中的位置
我们可以给应用层的这些应用程序提供我们想要传输的数据,比如说我们想用微信传一张图片,或者想用QQ发一串字符。那这些数据是由我们用户直接提供的,那么我们的数据交给了应用层的某一个进程之后。这个进程可能会在我们提供的这个数据前面加一些应用层的首部信息。
应用层的首部H5,这个部分有可能存在,也有可能不存在,具体来说就是取决于应用程序的实现逻辑。
比如说微信传一张照片,也许微信的开发者认为在这个照片的数据的前面需要增加一些有必要的信息,比如说这张照片是什么时候发送的?以及这张照片的大小?还有这张照片的格式等等。有可能微信的开发者认为这些信息有必要写在H5,也就是应用层的首部这个位置。那在这种情况下H5这个部分的数据就会存在。
换一个例子,假设QQ的这个应用程序开发者认为如果用户给我丢来一张照片的数据。那么,我不需要在这张照片前面加任何东西,我只需要直接把这张照片通过这个传输层的TCP协议或者传输层的udp协议传给另一个QQ进程就可以了。
所以如果说应用程序的开发者,他认为在这个用户数据的前面没必要加任何东西的话,那么事实上这个应用程序。可以直接把用户数据直接交给这个传输层的TCP或者udp进行传输。
那如果传输层使用的是TCP协议。那么H4这个部分添加的就是TCP协议的首部。而如果采用的是UDP协议,那么H4这个部分添加的就是udp的首部。那具体来说这两种协议的首部的格式定义是不一样的。
那在传输层这个地方,无论使用的是TCP还是udp协议,反正最终这一整块的数据,
都会交给网络层进行下一步的封装。那具体来说就是交给网络层的IP协议。IP协议会把传输层交过来的这一整块数据,封装成一个IP数据报。
那么这就是传输层在协议栈当中的地位,它为上层即应用层提供服务。同时,传输层又需要使用到网络层的IP协议所提供的服务。
在网络层那个部分学过的NAT路由器就引入了传输层端口号的概念。我们说一个IP地址可以标记网络当中的某一台特定的主机。所以网络层可以通过源IP地址和目的IP地址这两个信息去完成IP数据报在主机和主机之间的这种传输。然而,一个IP数据报到达一台主机还不够,最终IP数据报所携带的数据肯定还得交给上面的某一个应用进程。那此时传输层的端口号就会发挥作用,我们可以给主机上面的一个进程绑定一个端口号。并且同一台主机上的不同的进程它们的端口号都是不重复的。那这样的话,我们就可以用主机的IP地址,加上这台主机上某一个特定进程的端口号。用这两个信息去精确的指向网络当中的某一台主机上面的某一个特定的进程。
传输层就是通过端口号来区分数据来自哪个进程以及去往哪个进程的。所以传输层在网络层之上基于源端口号和目的端口号这两个特性实现了端到端的通信,也就是进程到进程之间的通信。
进程、端口号、传输层协议之间的关系
这里需要补充一些和端口号相关的知识。
假设这儿有一台主机a和主机b,它们分别拥有各自的IP地址。假设主机a上面的进程一正在和主机b的进程五进行网络通信。并且这两个进程的通信是使用了TCP协议。紧接着进程二和进程六也在进行通信,它们俩之间的通信也使用TCP协议。进程三和进程七之间也正在进行这个数据通信,只不过他们俩之间采用的是udp协议。进程四和进程八之间也在进行通信,它们俩之间使用的也是udp协议。
这几个数字代表的就是端口号。也就是说进程一绑定的是TCP协议的250这个端口号。而进程二绑定的是TCP协议的777这个端口号。进程三绑定的是udp协议的32端口号。
从这两个图当中,我们会发现,两台主机的端口号是相互独立的。
比如说左边这台主机a它的进程二绑定了777这个端口,右边这台主机的进程七也在使用777这个端口。左边这台主机,它在使用113这个端口,右边这台主机它的进程六也在使用113这个端口。
所以端口号这个东西是每一台主机自己的一个资源。只要确保主机内部端口号别重复就行,但是不同的主机当中端口号重复是可以被允许的。
TCP和UDP的端口号也是相互独立的。像右边主机B的进程五和进程八。可以看到,这两个进程,前者使用的是TCP协议,后者使用的是UDP协议。前者使用的是886端口,后者使用的也是886这个端口。二者使用的端口号是相同的。但是由于他们在传输层对应的协议各不相同。所以这两个看起来相同的端口号其实并不冲突。比如说当进程一和进程五需要通信的时候,它需要指明这样的三个信息。
①首先要指明我们这两个进程的通信采用的是什么协议。按照这个图来说,就是采用TCP协议。
②还需要指明本进程绑定的端口号250,这个是进程一绑定的端口号。绑定了这个端口号,就意味着接下来如果进程五想要给进程一发送数据。那么就往166.1.1.1这个IP地址的TCP协议当中的250这个端口发送数据就行了。所以一个进程要和另一个进程通信的时候,需要给自己绑定一个端口。用于接收对方给我发过来的这个数据。
③第三个要指明的信息就是。我要和对方通信,那么对方的这个IP地址还有端口号是多少?那么在这个图当中。进程五的IP地址是231.2.2.2,端口号是886。
那由于双方采用的是TCP协议,所以进程一给进程五发送的这个数据。会从进程一的TCP到达进程五的TCP,然后再去检查这个端口号886。于是这个数据就可以顺利的送到进程五。
也就是说,虽然UDP这边同样也在使用886端口,但是在传输的过程当中,可以根据现在使用的是TCP还是UDP协议去区分这个数据到底是应该从TCP往886这边去递交还是从UDP往886这边去递交
所以可以看到两个协议的端口号是相互独立的。
在刚才这个过程中,我们梳理了两个进程要通信的时候需要指明哪些信息。
接下来思考这样的一个问题。两个进程之间的通信有可能会传输一系列的数据。那如果每一次发送数据给另一个进程的时候,我都需要在自己的代码当中写明对方的IP地址端口号,并且再写明使用的是什么协议,每一次都重复去写这些东西那么对于写代码的人来说会很麻烦。为了让代码变得简洁,因此在C语言、C++当中定义了这样的一个数据结构,叫做套接字。英文叫socket。那套接字分为TCP套接字和UDP套接字。比如进程一和进程五之间它们要采用TCP协议进行通信。那么在进程一的这个代码当中就可以创建一个TCP套接字。
在这个数据结构当中去指明接下来要和我进行通信的这个进程,它的IP地址等于多少?以及它对应的端口号等于多少?那这样的话,接下来如果两个进程之间需要有多次这种数据传输。在写代码的时候就可以直接用套接字这个数据结构去说明我的这个传输方向是什么就可以了。也就是说,程序员在写代码的时候,就不必每一次都说使用的是什么协议、要跟哪个IP哪个端口通信。不用每次都去重复这些参数。
那这是套接字的作用,本质上它就是一个数据结构,而这个数据结构,它的作用就类似于一个网络指针(可以去这么理解)。它指向了网络当中将要和本进程通信的另一个进程。
这是套接字的概念。
值得一提的是,在套接字这个数据结构当中,除了包含IP地址和端口号,其实还包含了其他的一些重要的信息。那由于本文主要是在学习理论,而不是在学习编程,所以其他的东西我们就暂时不做拓展。有兴趣的同学可以自己去搜一下C语言或者C++的socket套接字在实际写代码的时候应该怎么使用。
总结:通过上文的学习,我们认识到了端口的作用,我们可以用端口号去标识主机上的一个特定的进程。那值得注意的是,每台主机的端口号都是相互独立的。另外,在同一台主机上,TCP和UDP两种协议的端口号也是相互独立的。无论是采用TCP还是UDP协议,在C语言和C++语言当中进行网络编程的时候都会使用到这个套接字这个数据结构。一个套接字包含了IP地址和端口号,这两个重要的信息。那通过这两个信息就可以唯一的去标识网络中的一台主机上面的一个特定的进程。
有了端口这个概念之后,传输层就可以实现端到端的通信,这个功能也就是实现进程到进程之间的通信。
参考资料:25王道计算机网络