TCP/IP 协议栈
什么是TCP:
TCP 是 面向连接的、可靠的、基于字节流的传输层通信协议。
什么是TCP连接:
简单来说就是, 用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。
三次握手
需要了解三次握手的过程,以及TCP状态迁移图。
三次握手过程(Client 主动与 Server发起连接):
- client端,发送SYN同步标志。
告诉server想要与其建立TCP连接,此时client的TCP状态为SYNC_SENT
- server端,回复ACK,并发送SYN标志,TCP状态为
SYNC_RCVD
。
server端在收到client端的同步标志SYN后,确认client发送能力OK。
同时发起SYN,表明可以建立连接。 - client端,收到sevrer端的SYN,回复ACK。
client端收到server端的SYN,以及ACK后,确认server端的发送能力以及接收能力OK。(Client:TCP状态 ESTABLISHED)
server端在收到客户端的ACK后,确认其client的接收能力OK。(Server:TCP状态 ESTABLISHED)
这里有个问题,为什么不能是2次握手呢?
比较片面的理解:TCP是双向的,既然是双向的那么就必须确保通信的双方的收发能力都OK。
假设没有最后的一次ACK答复,那么server端并不知道client端的接收能力是否OK,所有需要3次握手,确保双方的收发能力都OK。
- 首要原因是为了防止旧的重复连接初始化造成混乱.
在网络拥堵的情况下,客户端连续发送了两个建立连接的SYN报文。
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:
- 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;
- 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;
- 需要通过3次握手来同步双方的初始化序列号
当客户端发送初始化序列号的时候,服务端会回复ACK。
那么当服务端发送初始化序列号时,自然客户端也需要回复服务端ACK。
这样才能保证双方的序列号的同步。 - 避免资源浪费
倘若只有2次握手,当我们参数超时重发的时候,可能建立多个连接,浪费资源
问题小结:TCP 建立连接时,通过三次握手 能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
一次三次握手抓包图:
三次握手图解:
四次挥手
需要了解四次挥手的过程,以及TCP状态迁移图。
理解四次挥手每一次挥手后两端处于什么状态。
四次挥手过程(Client 主动关闭,Server被动关闭):
- client 发送FIN标志,并进入
FIN_WAIT_1
状态。 - server端收到客户端的FIN后,回复ACK,并进入
CLOSE_WAIT状态
,client端收到ACK后,进入FIN_WAIT_2
状态(半关闭状态,可接收数据,不可发送数据)。 - 当server端数据处理完毕时,发送FIN给客户端,并进入
LAST_ACK
状态。 - client端收到FIN后,发送ACK,进入
TIME_WAIT
状态(等待2MSL后关闭),server端收到ACK后关闭。
图解四次挥手:
问:
- 为什么需要等待2MSL时间
为了确保被动关闭方能够关闭,当最后一个ACK发出后,由于主动方并不知道被动方是否收到,当被动方发出FIN后但没有收到主动方的ACK,会触发FIN重传,所以等待2MSL,以便于再次接收FIN。
TCP 就设计出了这么一个机制,经过 2MSL 这个时间, 足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的 - 为什么需要四次挥手而不是三次
在收到Client端的FIN后,服务端回复ACK后,仅代表客户端半关闭,此时还可以接收数据,倘若服务端在收到FIN后还需要进行一些处理,还需要给客户端发送数据,这时如果只有3次握手的话,服务端在回复ACK的同时就发送了FIN标志,这样的话服务端没法在客户端想要关闭的时候再处理数据。
TCP与UDP的适用场景
实际上就是考察TCP与UDP的区别。
区别:
- TCP是面向流的,UDP是面向数据报的。
- TCP是可靠的有序的,UDP是不可靠的。
- TCP通信前需要建立连接,UDP不需要建立连接。
其他问题:
- TCP是可靠的,为什么是可靠的?
- UDP可是实现数据的有序吗?
常见的网络模型
select、poll、epoll
- 如果网络出错的话,select会检测的到吗?
不能,用来检测socke连接断开,当对端关闭时,select会有可读事件,但read返回0。 - send函数返回100,表明这100的数据已经到对方了吗。
不是,表明send已经将数据从应用层数据发送到本地缓冲区,什么时候把数据再发出去时间上是操作系统的行为,或者说是网络协议栈的行为。 TCP_NODELAY
,用于是否开启Nagle算法,默认情况下,操作系统是不会将一个很小的数据包立刻发出去的,会放在缓冲区,当到达一定的大小
的时候才一起发出去,使用TCP_NODELAY
可以禁用该算法,将数据包立即发出去。
Nagle 算法 该算法要求一个TCP连接上最多只能有一个未被确认的小分组,在该小分组的确认到来之前,不能发送其他小分组。
- 用select 可实现异步connect
poll,和select 类似。
epoll(最高效的网络模型):
- 在监测socket 是否可读下,存在两种模式 ET、LT
默认情况下,epoll采用的是LT。
LT:只要socket上有数据可读,就会一直触发。
ET:只有到socket上的数据从无到有的情况下,才会触发,也就是说,在ET模式下,如果你这个scoket上的数据你没有读完,
那么当这个socket即便是再有数据来也不会触发可读事件,也就是说,在该模式下我们必须循环的去读数据,直到socket上的数据读完。 - 那么是否需要监测socket的可写,也存在这两种模式吗?
理论上是有这两种模式存在的,但是我们一般不监测socket的可写,因为只要双方在正常的收发数据,那么socket就是一直可写的。
一般情况下,只有当我们调用send或者write返回-1(不代表出错,也可能是TCP窗口太小了)时,才去监测socket的可写事件,并且一旦发送完成后
就需要取消对该socket的可写事件的监听。
如何将socket设置为非阻塞的:
Linux:
- 创建socket时,可以设置为非阻塞的。
- 调用fcntl,可设置为非阻塞的。
- 可用accept4,设置为非阻塞的。
优雅关闭socket:
正常情况下,我们在关闭连接的情况下,我们不知道最后一个数据包是否被发出去。
- shutdown:
可用shutdown,实现半关闭。 - socket选项:
SO_LINGER
等待数据发完后,再关闭socket。
错误码
系统API分为可重试的,和不可重试的,在调用API的过程中可能被信号中断。
EINTR
SIGPIPE、EPIPE
为什么需要屏蔽这两个信号:
当一个连接已经关闭了,再对连接调用send 或者write时,操作系统会发送这个信号。
如果不对这两个信号处理,那么就会导致进程退出。
gethostbyname:
函数默认是阻塞的,并且当函数出错时,没办法通过lasterror获取错误码,必须用herrpr()函数去获取错误码。
定时器的实现(实际上考察数据结构):
- 服务器端为什么需要定时器?
服务器端可能产生大量的死链(在3次握手或者4次挥手过程中,对端关机啊什么的,处于TIME_WAIT 或CLOSE_WAIT
),需要处理。
链接保活机制,心跳包,服务器端需要定时的处理没有发心跳包的链接。
还有一些定时任务需要处理。 - libevent,libuv(中的计时器)。
TCP如何解决丢包粘包问题:
首先TCP是可靠的,不存在丢包的问题。
粘包:由于TCP是流的协议,所以发送100字节的数据,收到数据的情况可能很多种。
- 通过特定的结束符
- 包头中添加包的长度(最广泛的使用)
- 规定每个包的长度。
断线重连机制的设计:
- 客户端发现自己断开,然后重连。
- 分布式服务中的下游服务重连上游服务。
什么时间重连是核心:
- 固定时间重连
- 使用依次时间增加的方式(断线后立即重连,还是连不上的话就 等待2s 4s 8s ……),但需要监听网络变化,一旦网络连通后,立即重连。
HTTP协议
HTTP请求头 和 请求Body 中间通过 \r\n
分割
GET /index.php HTTP/1.1\r\n
Host: www.hootina.org\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n
User-Agent: Mozilla/5.0\r\n
\r\n
POST /index.php HTTP/1.1\r\n
Host: www.hootina.org\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n
content-length: 8
User-Agent: Mozilla/5.0\r\n
\r\n
abcdefgh
GET、POST的区别:
GET的参数放在网址后面,POST的参数放在包体。
GET有长度的限制
keep-alive:只是建议服务器不要断开这个链接,但服务器不一定要接受你的建议。