从输入网址到浏览器返回内容(二),TCP/IP篇
在从输入网址到浏览器返回内容(一),服务器处理篇谈了Web服务器、应用服务器、浏览器在整个过程中都做了什么,本章主要谈一下浏览器与服务器建立连接过程中的TCP,它是HTTP的基础。
第一章 基础概念
1.1 为什么需要封装
我们要寄信,除了信的内容,还需要信封邮票等,信封的主要作用是告诉邮局我的目的地址和寄信地址等,这样才能保证信能正确到达目的地。
我们邮寄信件,有平信,有挂号信,根据不同的应用场景选择不同的邮寄方式。所以所谓封装就是在不同的协议层将要传递的数据包装成不同的格式,加上附加的信息,保证能正确传输。
回到协议里面,HTTP协议是我们要发的数据,以太网头部、IP头部、TCP头部都是信封。
下面我们可以看HTTP请求传递的过程了。
发送过程:
首先浏览器发出一个HTTP请求,在传输层(TCP协议)进行分割,并加上序号和端口号,
然后通过网络层增加MAC地址发给链路层。
接收过程
在链路层接收到数据,一层一层的解封装,一直传到应用层。
1.2 为什么有端口号
学习TCP/IP协议我们首先会学习数据包的结构,但是没有人告诉我们为什么有目的IP、源IP,目的端口、源端口这集中概念。
我们以寄信的流程来做类比,HTTP协议处于应用层,就类似于我们要传递的数据,也就是信件。标准的信件格式是要在信封上写“收信人地址”和“寄信人地址”,因此引入了目的IP和源IP地址。
但是有目的IP只能保证信可以传递到某个学校或者某个小区(某一台PC上),但是无法确定到底传给哪个具体的人。所以可以在信件上添加收信人姓名和寄信人姓名(由此引入端口号)
所以目的IP可以保证数据包传到具体的PC上,但是PC上运行着多个应用,如果进行区分呢?就使用端口号,比如浏览器的默认端口号就是80
1.3 为什么有IP层?
作用一:屏蔽链路层的不同
其实以太网有MAC地址,完全可以用来寻址,但是却有天生的缺点:
- MAC 太长,48bit,用于路由寻址的时效低。
- 世界上不是每个环境都用以太网来建立网络,还可以有其他的网络系统,有其他的寻址方式。(最关键)
以太网天生的弱点导致它必须向TCP/IP合作,而且只能将MAC地址隐藏,对外通通使用IP地址。而APR协议的作用就是将其他协议的地址映射到IP地址上。
作用二:适配上下层
IP层另外的作用是适配上下层:给链路层和传输层提供适配。
链路层有MTU的概念:链路的最大传输单元,即每帧所允许的最大字节数。
链路层就类似司机和交通规则,对货车的载重、大小有要求,如果货物太大,就必须分割,这个动作是由IP层程序来做,也就是IP根据链路的MTU来分割货物,然后贴上源目的IP地址、顺序号。
路由器每次选择的路径不一样,所以到达目的地以后可能乱序了,此时可以使用顺序号。这个顺序号是根据分割处相对起点的距离,根据这个号,等所有货物到达了以后,对方的IP程序可以将这个号码组装起来。
每个货物块都携带IP头,但是只有第一块携带TCP头部 。因为传输层头部是在应用数据之前的,所以IP在分割的时候,一定会把头部分到第一块中。
总之,IP最大的作用是寻址和路由,以及适配链路层的MTU。但是以太网是个面向无连接的网络, 不保障数据一定会传送到对方,这就是传输层(TCP协议)需要存在的原因
1.4 为什么需要TCP协议
上面提到了以太网是面向连接的网络,不保证数据一定会传送到对方,而IP层其实也只是做了寻址、路由、适配的工作,为了把信安全的送到目的地,就引入了TCP协议。
- TCP协议的作用是保证上层数据能传输到目的地。类似货运公司的押运员,不管通过什么渠道(直达还是下一跳),不管物理链路的类型,只要送到即可。
- 如果出现错误,需要重新发送。每件货物到了目的地必须找收件人签字(TCP的ACK应答包)或者一批货物到了以后一次性签收。(滑动窗口)
- 最后回公司登记
1.5 TCP协议和UDP有什么区别
TCP和UDP的功能
- TCP功能:维护复杂的状态机,保障发送方发出的数据包都会传送到接收方。出错的话,则反馈信息。所以TCP是面向连接的协议。
- UDP功能:TCP/IP对以太网的透传。没有传输保障功能的亚层。除了UDP可以提供比以太网更方便的调用方式,其他没有本质区别,UDP是面向无连接的协议。
TCP协议就如同两个人打电话,首先会先有个交互的过程,通过双方各自一个“你好”确保对方能听到自己在说话了。
UDP协议就是如同广播,反正声音已经播放出去了,能不能听到就是自己的事情了。
第二章 TCP协议
下面我们来介绍一下TCP协议的流程。
之前说过网络上传输是没有连接的,TCP协议也是没有,不过TCP协议可以建立一个“虚”连接,也就是在双方维护一个连接状态,让它看上去好像有连接一样。而且
连接的状态信息并不会在路上保存; 相反, 连接的状态信息是在两端维持
两个人要开始对话之间需要互问一下“你好”,那么两端要建立通信应如何开始呢?通过三次握手
2.1 三次握手
三次握手的目的:主要是为了验证两端的发信/收信能力没问题, 这样就证明连接是通的, 可以正式发信了
我们以深圳向成都发信来进行举例:
- 第一次握手:深圳发信,成都收到了,此时成都就会明白,深圳的发信能力和自己(成都)的收信能力没有问题。
- 第二次握手:成都发信,深圳收到了,此时深圳也会明白:成都的发信和自己(深圳)的收信都是OK的。综合上次的结果,说明深圳、成都的发信、收信能力都没问题。但是此时成都还不知道自己的发信能力如何,所以需要下一次握手
- 第三次握手:深圳向成都再次发信,因为深圳已经确定了双方的收发都没问题,所以如果此时成都还收到这个报文,说明双方都没问题。
2.2 正式发包
TCP只是押送员,而决定走哪条路由他所在的车自己决定。车是没有意识,他只需要沿着路走即可,每个中转站(路由器)知道怎么走,所以由中转站来调度到达的车即可。
那么押送员TCP需要干:
- 如果货物太多,分成小包裹,一个一个的运
- 对小包裹进行编号,这样到了目的以后组装起来就行能。
- 如果中途某个包裹走丢了,就重发一个。
那么怎么确定包裹是否到了成都了?
用一个沙漏(时钟),如果漏完了,成都方面还没给我们确认,就算是丢了,重发一个。
先发送1~3号包,一段时间后收到了成都返回来的信,里面写者:1号包裹收到了。那么可以把第4号包裹发出去。也就是说每次保证有三个包裹发出去并且没有确认
接下来,成都方面返回的车带来了2~4号包裹的确认。于是深圳把5~7发送出去,并重新计时。
5号包裹的确认很快就回来了,我们又把8号发出去,现在已经发出去、但是没有收到确认的还是只有6~8三个包。
现在6~8号包裹都发出去了,但是6号的确认迟迟不来,当沙漏漏完,重发
为什么一下发了3个包裹,而不是发一个等着确认一个?因为如果发一个等一个的确认,太慢了,所以可以先发三个,一边发包裹,一边收确认,这样会快点,就是滑动窗口协议
如果6,7号包裹的确认还没收到,但是8号包裹的确认收到了。说明6,7号的确认包裹在路上丢了,但是只要成都方面说收到了8号包裹,暗含的意思就是6,7都收到了,不然也不会发送8号确认的。
第三章 Socket
上面我们说到IP层就是把数据分组从一个主机跨越千山万水发到另一个主机,并且一点都不靠谱,丢包、重复、失序是非常正常的。那么TCP就是在两个主机的进程之间通过失败重传来实现可靠性的传输。
那么建立一个TCP连接是非常复杂的,不能让每个人都重复的去做3 次握手、累积确认的开发,他们是属于操作系统内核的部分,应用程序应该只需要调用某个API接口就可以使用了。
所以TCP/IP协议向应用程序呈现的接口就是Socket接口,即插座接口。TCP/IP想让应用程序更方便的使用网络,就像插头插入插座一样。
socket 可以理解为 (客户端IP, 客户端Port, 服务器端IP, 服务器端Port)。
之前也提过,为什么要引入端口号,因为使用IP只能到达主机,但是要区分主机上是那个进程需要这个数据包,就需要用端口号。
- 一般来说, 服务器端都是被动访问的, 所以大家需要知道它提供服务的端口号, 例如80, 443等, 就是所谓知名端口号;
- 而客户端访问服务器的时候,自己的端口号可以随机生成一个, 只要不和别的应用冲突即可。
3.1 Socket编程模型
对客户端来说:
- socket() :创建一个socket,不需要输入IP和端口,因为系统可以自动获得IP和自动分配端口。它需要返回一个Socket标识符。
- connect():然后向服务器发起连接:三次握手
- send(),receive():连接上以后就可以发送,接收数据了
对服务器端来说:
对服务器端来说要复杂很多,因为
- 服务器是被动的,所以启动后,需要监听客户端发起的连接。
- 服务器需要应付很多客户端发起的连接,所以一定得把各个客户端Socket区分开
那么
- 监听客户端:只需要启动一个死循环,不断的监听是否有状态改变即可。
- 把Socket区分开:所以对每个客户端发起的连接都需要用一个新的socket来表示。
所以服务器需要:
- socket():为了要监听,所以创建的socket描述符listenfd,它就起了一个大门的作用,可以用它来生产一个独一无二的socket描述符,用来区分不同客户端的连接
- bind():声明我要占用这个端口,别人可别占用了。
- listen():开始监听。
- 通过一个循环:
- accept:与客户端的connet配合,完成3次握手。因为服务器要区分开各个客户端,所以需要为不同客户端生成不同的socket,即connfd
o 进行send(),receive(),它们都是基于accept()生成的connfd来进行的。
第四章 参考
· 知乎