1.TCP头格式有哪些?
图解TCP头部格式
详情
源端口和目的端口
端口的作用是什么?
端口的作用是在网络中唯一表示一台主机中的一个进程
序列号
什么是序列号?
用来给传输的字节标号的
比如要传10个字节 那么给第一个字节标号为1001 那么第十个字节为 1010
为什么要有序列号(seq)?
为了解决乱序问题 我们给传输的字节每个都有个编号 那么就可以解决传输过程中的乱序问题
确认序列号
什么是确认序列号?
也是字节的编号,但是他是期待下一个发送报文首个字节的编号
为什么要有确认序列号(ack)?
为了解决丢包问题
比如我包1的seq是1001 传输10个字节 那么我期待接收到的ack应该是1011,但是如果你收到的ack是1005那么说明在传输过程中发生了丢包,需要重传
首部长度
是整个TCP首部的长度
包括20字节的固定长度和其他可选项
他有4位可以表示 0~15 单位是4B
窗口大小
主要是做流量控制的
标志位
SYN
握手请求标志位
ACK
确认应答有效位
FIN
断开连接标志位
RST
强制断开连接标志位
2.TCP工作在哪一层?
首先回答他TCP工作在哪一层?
TCP工作在TCP/IP模型或者OSI七层模型中的传输层
其次是OSI七层模型具体有什么?
从底层到顶层分别是
物理层
数据链路层
网络层
传输层
会话层
表示层
应用层
然后是TCP/IP四层模型具体有什么?
从底层到顶层分别是
网络接口层
把数据链路层和物理层合为一个网络接口层
网络层
传输层
应用层
会话层、表示层和应用层合为一个应用层
3.网络层和传输层有什么区别?
网络层
网络层主要是通过逻辑寻址、路由选择,将数据从源主机传到目标主机,但是在这过程中,你的数据都丢了还是错了不归我管
传输层
传输层主要是做数据的可靠性传输
4.什么是流式传输和包式传输?
流式传输
我认为TCP的流失传输就和水池的注水和取水一样,我可以一次注水,然后多次把我刚才注的水取走,我也可以多次注水,然后一次把我注的水取走。
流失传输是以字节流的形式传输,在TCP中也中同样的道理,在发送方建立一个缓冲区不断的发送,在接收到建立一个缓冲区不断接收,我们不用去考虑我们分多少次发送的,也不用去考虑分多少次接收的。在socket编程中,比如我们write了100个字节,我们可以分多次去read,也可以一次读完,这就是我理解的流式传输
包式传输
包式传输是以一个个数据包的形式传输,你发送的时候有几个数据包,读的时候必须按一个包一个包的读
5.什么是TCP?
TCP是面向连接的、可靠的、流式传输的传输层通信协议
TCP必须是建立一对一的连接 不能是多对一
6.什么是TCP连接?
用于保证可靠性和流量控制维护的某些状态信息
TCP连接包括什么?
socket
ip和端口号
seq序列号
用来解决乱序问题
winsize
做流量控制
7.如何确定一个TCP连接?
用一个四元组来确定,具体包括
(源IP,源端口, 目的IP,目的端口 )
源IP目的IP的作用是告诉IP协议,数据由哪个主机发给另外一个主机
源端口和目的端口的作用是告诉TCP协议,数据由主机的某一个进程发给另外一个主机的某个进程
8.TCP和UDP的区别有哪些?分别的应用场景有哪些?
UDP头部格式
从连接方面来看
TCP是面向连接的,传输数据之前需要三次握手建立连接,不想传数据的时候需要四次挥手来断开连接
而UDP是不需要连接的,直接就可以传输数据
从可靠性上来看
TCP是可靠的传输层协议,为了实现可靠性他又面向连接、流量控制、拥塞控制、超时重传等诸多机制,可以保证数据不重、不乱、不丢的安全到达接收方
而UDP是不可靠的传输层协议,他对丢包、乱序等错误不会处理
从首部开销上来看
TCP的首部很复杂,有20个固定的选项 还可以添加额外的选项
而UDP的首部很简单,只有8个字节(源端口、目的端口、协议长度、校验位)
从传输方式上来看
TCP是流式传输,可以具体给面试官说说什么是流失传输
UDP是包式传输,同上
从分片上来看
如果TCP传输数据的大小大于MSS,则会在传输层进行分片,目标主机收到后,也会同样在传输层组装TCP数据包
而UDP在传输层不会进行分片,是在IP层进行分片操作
TCP和UDP没有觉得的好坏就看使用场景
TCP使用场景
在应用层中的HTTP协议和HTTPS协议的传输层都是用TCP封装的
UDP使用场景
视频的时候有的时候很卡 但是没有什么关系 再一说一遍就好了 他对可靠性的要求每那么高 但是对数据的传输速率的要求很高,所以使用UDP协议
包含量较少的通信 :DNS、SNMP等
9.为什么TCP有首部长度而UDP没有?
因为TCP有可选项而UDP没有
10.TCP是如何保证可靠性的?
a.通过面向连接机制
b.通过超时重传机制
c.通过流量控制机制
d.通过拥塞控制机制
11.TCP和UDP可以使用同一个端口吗?
答案:可以的
因为在OS内核中,TCP和UDP是俩个完全独立的软件模块
12.如何理解TCP是面向字节流的传输协议?
TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种不同的传输层协议,它们在设计时考虑了不同的应用场景和要求。下面是为什么TCP要用流式传输而UDP要用包式传输的原因:
TCP为什么要用流式传输
- 可靠性:TCP是一种可靠的传输协议,它需要保证数据的可靠传输。流式传输可以提供更好的可靠性,因为它可以将数据分成小的单位(字节或帧),并连续地发送到接收端。如果某个单位丢失或损坏,TCP可以通过重传机制来恢复丢失的数据。
- 顺序性:TCP需要保证数据的顺序性,即数据包按照发送顺序到达接收端。流式传输可以提供更好的顺序性,因为它可以将数据包按照顺序发送到接收端。
- 流量控制:TCP需要实现流量控制,以防止网络拥塞。流式传输可以提供更好的流量控制,因为它可以根据网络状况调整发送速率。
UDP为什么要用包式传输
- 速度:UDP是一种无连接的传输协议,它需要提供快速的数据传输。包式传输可以提供更快的数据传输,因为它可以将数据分成固定大小的包,并独立地发送到接收端。
- 无连接性:UDP不需要建立连接,因此不需要保证数据的可靠传输。包式传输可以提供更好的无连接性,因为它可以将数据包独立地发送到接收端,不需要等待确认。
- 实时性:UDP通常用于实时应用,如视频和音频传输。包式传输可以提供更好的实时性,因为它可以将数据包快速地发送到接收端,不需要等待确认。
总结
TCP和UDP的设计目标和应用场景不同,因此它们选择了不同的传输方式。TCP需要保证数据的可靠传输和顺序性,因此选择了流式传输。UDP需要提供快速的数据传输和无连接性,因此选择了包式传输。
13.TCP如何解决沾包问题?
出现沾包问题的原因是不知道一个消息的边界在哪,如果直到了边界就不会产生这样的问题
三种解决方法
固定消息的长度
给特殊的字符边界
\r\n
自定义消息结构
14.TCP三次握手的过程是怎么样的?
首先TCP三次握手就是通过三次数据包的交换来完成的,具体过程如下
1.最开始客户端和服务器都处于CLOSE状态
2.然后服务器通过调用socket函数创建一个主动的套接字文件,然后调用bind函数给套接字文件绑定IP和端口号,然后调用listen函数,把主动套接字转为被动套接字,服务器进入监听状态LISTEN状态
3.客户端这边也是先创建socket套接字文件,然后调用connect函数,服务端会想服务器发送一个SYN报文,具体内容如下
首先把SYN握手请求标志位置1
然后给客户端初始化一个Client_seq序列号,该序列号是一个随机值,假设Client_seq = x
然后客户端进入SYN-SEND状态
4.服务器这边接收到SYN报文,然后会发送一个ACK报文,具体内容如下:
首先把SYN握手请求标志位和ACK确认应答有效位置1
然后给服务器初始化一个Server_seq序列号,该序列号是一个随机值,假设Server_seq = y
最后返回一个确认序列号ack,等于服务器的Client_seq + 1 ,ack = x + 1
然后服务器进入SYN-RECEIVE状态
5.客户端接收到ACK报文之后,会发送一个ACK报文,具体内容如下:
首先把ACK置1
然后给发送服务器的序列号置位 x+ 1,确认序列号位y + 1
客户端进入ESTABLISHED状态
6.服务器在收到ACK报文之后,进入ESTABLISHED状态
三次握手的过程中,前俩个报文时不可以传输数据的,但是最后一个报文可以传输数据
当双方都处于ESTABLISHED状态的时候连接就已经建立完成了,客户端和服务器就可以发消息了
15.Linux系统中如何查看TCP状态?
netstat -napt
-a: 列出所有端口
-at: 列出所有TCP端口
-au:列出所有UDP端口
-p:显示正在使用socket的程序识别码的程序名称
-n:显示IP地址和端口号的s
16.为什么是三次握手而不是俩次或者四次?
a.三次握手能交换双方的初始序列号,而俩次握手不能
首先是说为什么三次握手可以交换双方初始序列号?
因为每一次给对方发送初始序列号之后,对方都会回应一个ACK,保证对方已经接收到了该初始序列号
然后是说为什么俩次握手不可以交换双方初始序列号?
俩次握手的过程中只回复一个ACK 不能确保交换的安全性
b.三次握手可以防止建立无效的连接,而两次握手不能
首先说一下为什么会发送无效的连接?
主要原因是客户端发生了宕机,在宕机之前发送了一个旧的SYN报文,客户端回复正常状态之后会重新发送一个新的SYN报文,该报文和旧的不是一个值。旧的报文比新的先到,如果不是三次握手,就有可能建立一个无效的连接(如何无效,就是客户端之变直到该连接时历史性无效连接,但是服务器这边不知道,导致服务器资源的浪费)
三次握手为什么可以避免无效连接的建立?
客户端发送一个SYN_OLD报文(oldseq = 100),但是发生网络拥塞,客户端这边突然宕机,等到宕机结束之后,客户端会重新发送一个新的SYN_NEW报文(newseq = 110),但是旧的报文比新的先到达,然后返回了一个SYN+ACK报文,确认应答序列号是111,通过上下文对比不是101,客户端会发送一个RST报文(RST强制断开连接标志位置1),阻止了该无效的连接。之后新SYN报文到达服务器,开始正确的三次握手。
俩次握手为什么不行?
造成了客户端知道此次连接时无效连接,但是由于只有俩次握手无法通知服务器,服务器这边白白的浪费了资源
c.三次握手可以防止重复性连接的建立,而俩次握手不能
由于发生了网络拥塞,导致触发了超时重传,会产生俩次或者多次重复的连接,三次握手可以避免,而俩次不可以
四次挥手有点浪费资源了 但是理论上可以没问题
17.为什么每次建立连接时,初始化序列号都要求不一样那?
防止建立无效的连接(历史性连接)
18.既然IP层会分片,为什么TCP层还需要MSS那?
19.第一次握手丢失了,会发生什么?
会发生超时重传
20.第二次握手丢失了,会发送什么?
因为第二次握手比较特殊,它包含俩个内容,第一个内容是对客户端向服务器发送握手请求的回复,第二个内容是服务器向客户端发送握手请求。所以如果第二次握手丢失,客户端这边会想是不是我的SYN丢了,服务器之变可能回想是不是我服务器的SYN丢了,所以会触发客户端的超时重传或者服务器这边的超时重传
21.第三次握手丢失了,会发生什么?
第三次挥手是发送ACK报文,具体内容是对第二次握手中服务器向客户端发送握手请求的回应,如果第三次丢了,可能会触发服务器这边的重试重传,切记第三次握手没有超时重传
22.什么是SYN攻击?如何避免?
SYN攻击就是通过不断的向服务器发送SYN报文,从而造成服务器中半连接队列的溢出。
避免?
增大TCP的半连接队列
开启cookie
23.TCP四次挥手的过程是怎么样的?
总体上来说TCP四次握手就是通过四次数据包的发送来断开连接的,具体过程如下
a.如果客户端想要断开连接的话,会发送一个FIN报文,该报文具体内容有把FIN标志位置1,然后客户端进入FIN_WAIT1状态
b.服务器接收到客户端的FIN报文后,会发送一个ACK报文,然后服务器进入CLOSE_WAIT状态
c.客户端接收到ACK报文后,会进入FIN_WAIT2状态
d.等待服务器处理完数据之后,服务器会发送FIN报文,之后服务器进入LAST_ACK状态
e.客户端接收到FIN报文后,会发送一个ACK报文,然后客户端进入TIME_WAIT状态
f.服务器接收到ACK报文之后,会进入CLOSE状态,服务器完成连接的关闭
g.客户端在等待2MSL时间之后,自动进入CLOSE状态,至此客户端也完成了连接的关闭
24.为什么挥手需要四次?
因为TCP是一个全双工的通信协议,必须双方都同意断开连接之后才能真的断开连接。
当客户端给服务器发送已给FIN报文,然后接收到ACK报文之后,客户端会关闭发送消息的功能,但是接收功能还在,服务器这边可以接着向客户端发送消息。当服务器发送FIN报文并接收到ACK报文之后,才能证明服务器这边不想发送消息了,客户端这边也不想发送消息了。这个时候才算真的断开连接
所以第二次挥手的ACK和第三次挥手的FIN不能合并
25.close函数发起FIN报文和shutdown函数发送FIN报文的区别有哪些?
触发客户端发送FIN报文的情况有两种,第一种是调用close函数,第二种是调用shutdown函数
调用close函数关闭,会把客户端的收发能力都关闭,当客户端接收到ACK报文之后,不会死等服务器的FIN报文,达到一定之间之后自动进入CLOSE状态
而调用shutdown函数关闭,只是把他的发送能力关闭了,当客户端接收到ACK报文之后,进入FIN_WAIT2状态会四等服务器发来的FIN报文,如果服务器一直流氓行为不主动发送FIN报文,则客户端会一直处于FIN_WAIT状态
26.第一次挥手丢失了会发生什么?
会触发FIN的超时重传
27.第二次挥手丢失了会发生什么?
ACK是没有超时重传的,但是会触发第一次挥手的超时重传
28.第三次挥手丢失了会发生什么?
会触发第三次挥手FIN的超时重传
29.第四次挥手丢失了会发生什么?
会触发第三次挥手的超时重传
30.为什么TIME_WAIT等待时间是2MSL?
首先清楚MSL是什么?
MSL是任意一个报文在网络中最大生存时间
从客户端发送ACK报文开始计时,因为客户端发送ACK,然后触发第三次挥手的重试重传发送一个FIN,这俩个报文加起来的最大生存时间是2MSL,如果在这个事件内还没有接到ACK,直接CLOSE掉
31.为什么需要TIME_WAIT状态?
保证四次挥手的可靠完成
防止旧的报文影响新的连接
假设A于B刚完成四次挥手,立即又想继续建立连接,那么旧的连接中可能存在一些旧的报文,这些报文于新的报文之间又相同的四元组,可能无法区分,所以要等待一段时间,让这些旧的报文全部消失之后,再建立新的连接
32.TIME_WIAT过多有什么危害?
他需要等待2MSL的时候 这个时间内肯定会占用系统资源和端口号
33.服务器出现大量TIME_WAIT状态的原因有哪些?
只有主动断开连接的哪一方才会出现TIME_WAIT状态,所以出现的原因可能是服务器主要断开了大量的连接
34.服务器出现大量CLOSE_WAIT状态的原因有哪些?
CLOSE_WAIT状态存在于被动断开连接的一方,服务器大量出现该状态,说明服务器没有发送FIN报文,也就是说没有调用CLOSE函数关闭连接
35.如果已经建立了连接,但是客户端突然出现故障了怎么办?
如何服务器一直没有于客户端通信,那么服务器则无法察觉客户端的宕机。那么服务器就会一直不会关闭,占用资源。TCP提供了一种保活机制,当一段时间内没有数据传输的时候,TCP会发送一个探测报文,来检查连接是否有效,如果多次探测报文都没有效,服务器会自动关闭
36.如果已经建立了连接,但是服务器进程崩溃了会发生什么?
仍能继续完成四次挥手断开连接,因为四次挥手的过程不需要进程的参与,时由内核完成的
37.拔掉网线后,原来的TCP还存在吗?
存在
TCP存在内核中的一个结构体里面
38.TCP连接,一端断电(宕机)和进程崩溃(crash)有什么区别?
keepalive选项
他是TCP应对客户端宕机的保活机制,具体内容如下
如果客户端突然宕机,并且服务器长时间没有给客户端发消息的话,服务器是无法知道客户端宕机的,那么服务器就回一直占有资源,我们引入了一种保活机制,如果一段时间之内客户端服务器之间没有通信的话,TCP会向客户端发送一个探测报文,如果多次探测没有响应的话,服务器会自动关闭,这就是keepalive选项
进程崩溃
我之前自己模拟过 使用kill -9 杀死客户端进程,模拟进程崩溃,杀死的一瞬间,客户端会向服务器发送FIN报文,完成四次挥手,因为四次挥手的过程是在内核中完成的
客户端宕机
就客户端宕机而言,而且没有数据的交互,服务器这边无法确定客户端是否宕机。且没有开启保活机制,服务器会一直处于ESTABLISHED状态。所以一方处于ESTABLISHED状态,并不代表于另一方连接正常
39.针对套接字如何进行Socket编程?
a.最开始客户端和服务器都创建一个socket套接字文件
b.服务器这边
先调用bind函数给socket绑定IP和端口号
然后调用listen函数,将主动套接字转化为被动套接字,服务器处于监听状态,可以接收客户端的握手请求了
最后调用accept函数,等待三次握手的建立完成后返回一个已连接成功的的socket
c.客户端这边
调用connect函数,向服务器的地址和端口发起三次握手请求
d.建立连接成功,三次握手完成
e.客户端这边调用write函数往socket里面写数据,服务器这边调用read函数从socket里面读数据,也可以反正来,因为TCP是全双工的通讯协议
f.当有一个想断开连接的时候,调用close函数,另一方在read的时候会读到一个EOF,待处理完数据之后,另一方就会调用close函数,表示连接关闭
服务器这边又俩个socket,一个是最开始调用socket函数创建出来的socket,他是专门负责监听的socket
还有一个socket是在accpet函数调用之后,返回的socket,是一个已经完成三次握手的socket
40.listen函数的第二个参数backlog的意义?
首先先看源码
int listen(int sockfd, int backlog);
DESCRIPTION
listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket
that will be used to accept incoming connection requests using accept(2).
The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or
SOCK_SEQPACKET.
The backlog argument defines the maximum length to which the queue of pending connections for
sockfd may grow. If a connection request arrives when the queue is full, the client may
receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports
retransmission, the request may be ignored so that a later reattempt at connection succeeds.
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
他与两个队列的大小有关
半连接队列
在服务器收到SYN报文之后,会把一个四元组(源ip,源端口,目的ip,目的端口)放到半连接队列里面,此时的服务器处于SYN_RECEIVE状态
全连接队列
当客户端的ACK到达的时候会把该四元组取出来放到全连接队列里面,此时的服务器处于ESTABLISHED状态,正等待着accpet取出
所以通常认为backlog是acept队列的长度,但是上限值是内核的参数somaxconn的大小,也就是说accept队列长度 = min(backlog, somaxconn)
41.accpet发生在三次握手的哪一步?
三次握手是发生在listen之后accpet之前,accept的返回值是一个已经连接成功的socket
42.客户端调用close函数,断开的流程是什么?
当客户端调用close函数的时候会发送一个FIN报文,服务器接收到FIN报文之后会在接收方缓冲区中插入一个文件结束标识符EOF,然后服务器返回一个ACK报文。服务器接着处理没有处理完的数据,直到读到EOF文件结束标识符,服务器会调用close函数,然后发送一个FIN报文,客户端回一个ACK报文。
43.没有accept能建立TCP连接吗?
可以,accpet并不影响三次握手的完成,他的主要作用是从全连接队列里面取出一个已经建立完成的socket。
44.没有listen能建立TCP连接吗?
没有listen无法建立哪种典型的客户端于服务器的连接,但是可以建立自连接。和一个客户端与另外一个客户端的连接
45.重传机制
为什么要有重传机制?
为了防止数据包的丢失
TCP提供了哪些重传机制?
超时重传
在发送端发送数据的那一刻开始计时,如果time > RTO(最大超时时间,并且ROT > RTT来回一趟所需要的时间)还没有收到ACK就会触发超时重传,重发消息,改RTO为2RTO,再超时4RTO,8RTO,16RTO,OS回提供一个最大超时传输次数。达到上线了,就不会再超时重传了,系统回认定服务器挂掉了
快速重传
接收了三次重复的ACK,肯定是出问题了,但是问题不太大,因为服务器的ACK我收到了,可能只是个别包的丢失,触发快速重传机制
SACK
D-SACK
46.超时重传
47.快速重传
但是于此同时产生了一个问题,就是我是从1010开始出的问题,我是重传1010那,还是都重传那,站在上帝视角我们直到只要重传1010和1020就可以了,但是客户端不知道哪个丢了,这就用到SACK了,SACK交选择性确认
48.什么是SACK?
49.什么是Duplicate SACK?
D-SACK主要是使用SACK来告诉发送发哪些数据被重复发送了
50.引入窗口概念的原因是什么?
主要是为了提高数据传输的效率,如果没有窗口的话,数据传输的形式应该是等待上一个ACK应答之后,才能发送下一个消息。这样的数据的传输效率很慢,引入了窗口的概念就无需确认应答,只要发送的消息的大小小于当前窗口大小就可以。
51.窗口的大小由哪一方决定?
窗口是什么?
窗口大小实际上就是缓冲区剩余的内存大小,在接收方发送的ACK报文中有窗口大小的字段。发送方通过ACK报文获取到这个winsize窗口大小,然后再想发送数据的时候就会比较看发送的数据是否小于窗口大小,如果小于则可以发送,如果大于则不能发送
52.发送方的滑动窗口?
53.接收方的滑动窗口?
54.什么是零窗口问题?
55.接收窗口和发送窗口的大小是相等的吗?
不是完全相等,是约等于
56.操作系统缓冲区和滑动窗口的关系是怎样的?
例子:由于操作系统资源紧张,临时减少了接收方缓冲区的大小
这就造成了1051(50)在接收方缓冲区内存的溢出,为了防止这种现象的发生,TCP规定是不允许同时减少缓存的同时又收缩窗口大小的
57.什么是窗口关闭?
窗口关闭其实就是零窗口问题产生的死等现象
如何解决?
当又一方出现零窗口问题的时候,会启动一个计时器,如果计时器超时,就会发出一个探测报文(其实就类似于保活机制),然后该探测报文发出之后,观察回应的ACK看winsize大小是否是零,如果是零,就会重新启动计时器,如果不是零,则零窗口问题就被解决了,因为接收方窗口被重新更新了
58.什么是糊涂窗口综合征?
59.流量控制和拥塞控制的区别是什么?
流量控制
流量控制是防止发送方发送的数据过多或者是太快导致接收方缓冲区溢出
拥塞控制
拥塞控制是防止发送方发送消息过多或者是太快导致网络出现拥塞,可能会导致包的丢失和延迟,又可能触发TCP的超时重传,从而加网络拥塞,形成一个恶性循环
TCP作为一个可靠的协议,提供了拥塞控制,当网络发生拥塞的时候会减少发送数据。
如果完全不管网络拥塞会造成怎么现象?
过程模拟:网络拥塞,导致数据包传递出现延迟或者丢包现象,触发重传,重传的数据包继续拥塞,最后可能发送方的数据填满整个网络。
60.四个窗口的区别是什么?
发送窗口
swnd
min(cwnd, awnd)
接收窗口
awnd
接收方的缓冲区剩余的内存大小
拥塞窗口
cwnd
网络上不会发生拥塞所能承受的最大数据量
61.什么是拥塞窗口?和发送窗口有什么关系?
拥塞窗口指的是在不发生网络拥塞情况下,发送方能过向网络上发送的最大数据量。它会根据网络的拥塞程度动态变化。
发送窗口需要考虑两点,第一点是发送的数据量不会造成网络的拥塞,第二点是发送的数据量不会使接收方的缓冲区发生溢出,所以发送窗口、接收窗口、拥塞窗口之间的关系如下
swnd = min(awnd, cwnd),即发送窗口的值应该是接收窗口和拥塞窗口俩者取的最小值
62.拥塞窗口cwnd的变化规律是什么?
遵循加性增、乘性减的原则
解释:如果网络上没有发生拥塞,那么cwnd拥塞窗口的值会一点点的增加,如果发生了拥塞,cwnd会折半的减少
63.如何知道网络上出现了拥塞?
接收方在规定的时间内没有收到ACK报文,触发了超时重传
64.拥塞控制有哪些算法?
慢启动算法
拥塞避免算法
拥塞发生算法
快速恢复算法
65.什么是慢启动算法?
在TCP刚建立连接之后,首先得有一个慢启动的过程,就是一点点的数据包的大小,去试探网络
规律:每一轮,发送试探的cwnd的大小都会乘以2,指数型增长
等到达到了慢启动的阈值,就会启动拥塞避免算法
66.什么是拥塞避免算法?
规律:每一轮cwnd增加1,呈线性增长
直到发生了网络拥塞,出现了丢包现象,也就是发生了重传(超时重传或者是快速重传),那么接下来进入拥塞发生算法
67.拥塞发生时什么?
出现了重传现象,可能时超时重传也有可能时快速重传
68.发生超时重传的拥塞发生算法是什么样?
把慢启动的阈值更新为之前的一半
cwnd重置为1
69.发生快速重传的拥塞发生算法连同快速恢复算法一起使用是什么样?
快速重传是连续收到三次重复的ACK值,既然它还能收到ACK说明它网络拥塞还没那么严重,所以采用的拥塞发生算法肯定与超时重传的不一样。
把慢启动的阈值更新为原来的一半
cwnd的值是慢启动阈值+ 3
70.HTTP是什么?
HTTP是超文本传输协议的简称,是一种无状态的应用层协议,它的传输层协议用的是TCP,网络层协议用的是IP协议。它可以传送的任意的媒体类型。
请求报文
响应报文
71.什么是URL?
URL全称叫统一资源定位符,用于定位查找某个资源。以以下的一个URL为例
http://www.baidu.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name
下面是URL的组成部分
协议域(//)
http: 表示使用的是http协议,后面用//做分隔符
域名(:)
www.baidu.com 这是一个域名 它可以映射一个ip地址
端口(/)
:8080,如果不写端口的话http协议会默认一个端口
在使用https协议的URL中,如果没有端口号,那么使用的是默认的443端口
虚拟目录(?)
/news/
参数部分(#)
boardID=4&ID=24618&page=1
锚点
name
72.HTTP常见的状态码有哪些?
1**类(中间态状态码)
表示中间状态的状态码
2**类(成功状态码)
表示服务器成功处理客户端请求的状态码,创建的状态码有
[200, OK]:表示一个笼统的处理成功的状态码,对于非HEAD的请求,一般服务器的响应报文里面会有响应body
[204, No Content]:也是表示成功的状态码,但是响应一般不会携带响应的body
[206, Partial Content]:也是表示成功的状态码,但是响应返回的body只是资源的一部分,不是全部
3**类(重定向状态码)
表示客户端请求的该资源已经发生了变动,需要客户端重新传URL,即重定向
[301, Moved Permanently]:永久重定向
[302 Found]:临时重定向
301和302的响应头里面指明后续跳转的URL写,到Location字段里面
4**类(客户端错误)
[403, Forbidden]:服务器禁止访问资源,你的权限不够
[404, Not Found]:表示请求段的资源在服务器中找不到
5**类(服务器错误)
[501, Not Implemented]:有可能是客户端的版本号过高,即将开业,敬请期待
[502, Bad Gateway]:表示服务器作为网关或者代理时返回的错误码,后端服务器运行正常
[503, Service Unavailable]:表示服务器当前忙
73.单线程服务器中有多个阻塞IO会造成什么影响?
它只能满足一个客户端连接一个服务器的情况,对于多个客户端连接服务器这种情况不能满足。
在TCP服务器中,必须调用俩个阻塞IO,一个是accept取出一个已经完成连接的socket,另外已给是read
下面画图图例,多个阻塞IO对服务器造成的影响
74.如何实现多线程解O复用?
根据73我们可以得出只要一个线程或者进程只有一个阻塞IO的话就可以实现多个客户端的服务器
而TCPServer中阻塞的IO有俩个,一个时accpet,一个是read,我们把它们俩个放在不同的线程中即可。(主线程放accpet,自线程放read)
具体代码如下:
// 实现单线程IO复用
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
//服务器
//1创建套接字 socket
//2给套接字绑定一个IP和端口 bind
//3等待有客户端和服务器建立连接 listen accpet
#define SER_IP "192.168.22.139"
#define SER_PORT 8001
void* thread(void* arg){
int cfd = (int)arg;
int readret = 0;
char buf[1024];
while(1){
readret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, readret);
write(cfd, buf, readret);
}
return NULL;
}
int main(int argc, char* argv[])
{
struct sockaddr_in seraddr;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);//网络字节序的端口号
seraddr.sin_addr.s_addr = INADDR_ANY;//网络字节序的整形的IP地址
int ret = bind(lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
//设置监听状态
listen(lfd, 64);
pthread_t tid;
struct sockaddr_in clientaddr;
socklen_t addrlen = sizeof(clientaddr);
char dst[64];
while(1){
int cfd = accept(lfd, (struct sockaddr*)&clientaddr, &addrlen);
printf("new client connection IP: %s, PORT: %d\n", inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, dst, sizeof(dst)), ntohs(clientaddr.sin_port));
pthread_create(&tid, NULL, thread, (void*)cfd);
}
return 0;
}
75.如何实现单线程IO复用?
select
最主要的特点就是使用fd_set位图实现了监听文件描述符的功能
select中的位图是一个结构体数组
轮询监听
跨平台
代码
// 实现单线程IO复用
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/select.h>
//服务器
//1创建套接字 socket
//2给套接字绑定一个IP和端口 bind
//3等待有客户端和服务器建立连接 listen accpet
#define SER_IP "192.168.22.139"
#define SER_PORT 8001
void* thread(void* arg){
int cfd = (int)arg;
int readret = 0;
char buf[1024];
while(1){
readret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, readret);
write(cfd, buf, readret);
}
return NULL;
}
int main(int argc, char* argv[])
{
struct sockaddr_in seraddr;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
printf("lfd = %d\n", lfd);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT); // 网络字节序的端口号
seraddr.sin_addr.s_addr = INADDR_ANY; // 网络字节序的整形的IP地址
int ret = bind(lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret == -1){
perror("bind error");
exit(1);
}
//设置监听状态
listen(lfd, 64);
struct sockaddr_in cliaddr;
// 目前只有一个lfd需要监听
int selectret = 0;
int maxi = lfd;
int readret;
char buf[1024];
int i;
fd_set rset, aset; // aset表示所有监听文件描述符的集合 rset表示触发了事件的文件描述符的集合
FD_ZERO(&aset); // 读文件描述符的位图初始化为0
FD_SET(lfd, &aset); // 位图监听lfd
while(1){
rset = aset;
selectret = select(maxi + 1, &rset, NULL, NULL, NULL);
if(selectret < 0){
perror("select error");
exit(1);
}
printf("selectret = %d\n", selectret);
// 处理触发了的事件
// 先判断lfd是否触发
if(FD_ISSET(lfd, &rset) == 1){ // 监听accpet事件
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
FD_SET(cfd, &aset);
if(cfd > maxi){
maxi = cfd;
}
}
for (i = lfd + 1; i <= maxi ; i++) {
if(FD_ISSET(i, &rset) == 1){ // 监听read事件
readret = read(i, buf, sizeof(buf));
if(readret < 0){
perror("read error");
exit(1);
}
if(readret == 0){
printf("客户端断开连接\n");
FD_CLR(i, &aset);
close(i);
}
write(STDOUT_FILENO, buf, readret);
write(i, buf, readret);
}
}
}
return 0;
}
poll
poll中的位图是一个链表
只在类unix系统中有
epoll
首先epoll_create函数会在内核区维护一段空间,初始化一个epoll对象,空间里面有来部分内容,第一个部分红黑树,第二部分是双向链表
代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/epoll.h>
//服务器
//1创建套接字
//2给套接字安排一个IP和端口
//3等待有客户端和服务器建立连接
#define SER_IP "192.168.0.184"
#define SER_PORT 8000
int main(int argc, char* argv[])
{
//初始化监听套接字
struct sockaddr_in seraddr;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);//网络字节序的端口号
//int dst;
//inet_pton(AF_INET, SER_IP, &dst);
seraddr.sin_addr.s_addr = INADDR_ANY;//网络字节序的整形的IP地址
int ret = bind(lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret == -1){
perror("bind error");
exit(1);
}
//设置监听状态
listen(lfd, 64);
struct sockaddr_in cliaddr;
socklen_t addrlen = sizeof(cliaddr);
char dst[64];
char buf[4];
int readret;
int epollret;
int i;
//目前只有一个lfd需要监听
//创建一个epoll对象
struct epoll_event event;
struct epoll_event events[1025];
int epfd = epoll_create(64);
event.events = EPOLLIN;
event.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);
while(1){
epollret = epoll_wait(epfd, events, 1025, -1);
printf("调用一次epoll_wait\n");
if(epollret < 0){
perror("epoll_wait error");
exit(1);
}
for(i = 0; i < epollret; i++){
if(events[i].events & EPOLLIN){
if(events[i].data.fd == lfd){
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &addrlen);
printf("new client connection IP: %s, PORT:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, dst, sizeof(dst)), ntohs(cliaddr.sin_port));
event.events = EPOLLIN | EPOLLET;
event.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
}
else{
readret = read(events[i].data.fd, buf, sizeof(buf));
if(readret < 0){
perror("read error");
exit(1);
}
else if(readret == 0){
printf("客户端断开连接\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
}
write(STDOUT_FILENO, buf, readret);
write(events[i].data.fd, buf, readret);
}
}
}
}
return 0;
}
76.select实现单线程IO复用的优缺点有哪些?
缺点
最多只能监听1024个文件描述符
因为位图的大小是128B,1024位,为一个位置监听一个fd,总共可以监听1024个文件描述符
内核空间,用户空间产生了大量的数据拷贝
用户到内核拷贝128,内核到用户128,但是它是一直轮询的,会大量的拷贝
轮询监听,在活跃用户量比较少的时候,性能比较低
举例,比如我初始化位图的时候监听了3~1000的文件描述符,但是实际上只有4 5 6 三个事件被触发了,但是我需要从到到尾遍历一遍,所以在活跃用户量比较少的时候,性能较低
优点
跨平台
window能用 Linux也能用
77.epoll实现单线程IO复用的有缺有哪些?
优点
它监听事件的上限只取决于那块内核空间的大小,不想select一样只能监听1024个文件描述符
没有冗余的数据拷贝,select会有大量冗余的数据拷贝
因为epoll是基于事件通知机制的监听,当事件发生的时候,会把该事件从红黑树拷贝一份放到双向链表中,没有多余的拷贝
epoll基于事件通知机制的监听,及时活跃用户很少,也可以保证效率
因为它遍历的是已给双向链表,不像select一样遍历真个数组
缺点
不跨平台
用户少的时候不太合适,因为内核中维护的数据结构过大
红黑树、双向链表
78.GET请求和POST请求的区别有什么?
从功能上来看
GET请求
根据RFC规范,GET请求是用来获取服务器中的资源,对服务器的操作是只读的,它的放在请求报文的URL中,但是我请求的参数是有限制的,不是URL有限制,而是浏览器对URL的长度有限制。
举例,在我的项目中,获取商城配置用的就是GET请求
POST请求
根据RFC规范,POST请求是用来向服务器提交数据的,对服务器而言一般是可读可写的,它的请求参数放在报文的请求body中,不限制传输大小。
举例,项目中,注册和登录就是一个典型的POST请求,还有商城购买
从安全性和幂等性来看
什么是安全性?
服务器的安全性指的就是发来的请求不会破坏服务器的资源
什么是幂等性?
指的是执行多次重复的操作,结果是相同的
具体例子,比如我先在要发送一个商城购买请求,但是由于某些原因,我的用户点击了一次没有响应,它一顿连点,点了10多次,这个时候幂等性就很重要,如果没有幂等性,我用户就想买一个双倍经验卡,但是最后的结果是它买了10多个。
由于GET请求时只读的,是安全的(不会破坏服务器资源),是幂等的(多次重复请求结果相同)
但是POST请求是可读可写的,是不安全的,不是幂等的
79.GET请求可以携带body吗?
GET请求一般情况下参数放在URL中,是不会携带body的,但是并没有明确的规定,只要你客户端和服务器约定好了,可以携带那就没什么问题,POST也一样,一般POST携带的参数都放在body中,但是你放在URL?后面也没什么问题,但是不要传的太多,因为浏览器对URL有限长
80.HTTP为什么要使用缓存?
为了减少重复性的请求
对于一些经常使用的HTTP请求,比如在我都项目中,需要经常获取商城配置,那么就可以把获取商城缓存到本地,下次再拉商城配置的时候,直接在本地获取就可以了。HTTP的响应头里面就有存储有关缓存相关的字段。
HTTP中的缓存分为协商缓存和强制缓存,具体解释看81 82
81.什么是强制缓存?
服务器在发送响应报文的时候回携带一个缓存过期事件的字段,当再次发送相同请求的时候,客户端这边就会比较看当前事件time是否小于缓存事件,如果小于,则说明该缓存没有过期,直接在本地获取该数据,就不用去到服务器获取了。但是如果大于,则说明该数据已经过期了,就需要用到强制缓存,具体看82。
Cache-Control和Exprie都可以表示缓存过期时间,前者是相对时间,后者是绝对时间,一般优先使用Cache-Control
82.什么是协商缓存
当缓存中的数据过期的时候,就要发送个请求去看看服务器里面的数据是否被修改过,有俩种方式去判断,第一种是lastmodifiy 表示最后修改时间,第二种方式是Etag唯一标识符,我们去对比看if-Modified-since的值和lastmodify是否相等,或者比较if-None-match的值是否和Etag相等,如果相等说明这段时间内没有服务器没有修改该资源,缓存仍然有效,更新Cache-Control,然后返回304状态码。反之说明缓存被修改,直接返回要请求的资源,200状态码
83.为什么Etag的优先级比Lastmodified高?
比如你设置走后修改时间位19点49,但是在这1s之内,资源被修改了,用这种lastmodified的方式,客户端下次使用协商缓存的时候,会认为缓存没有被修改,产生错误
84.HTTP/1.1的优缺点有哪些?
优点
简单易于学习
灵活易于扩展
他是应用层的协议,我们用户可以根据自己的需求去修改
应用广泛
缺点
明文传输,不安全
明文传输可以使HTTP协议结构上更简单,但是同样也很不安全,容易被抓包,泄漏信息
无状态
好处
服务器不用去记住HTTP的状态,可以减轻服务器的压力
坏处
服务器在进行关联性操作的时候很麻烦,比我项目中,每次拉商城配置,发送邮件都操作之前都要先去验证登录
为了解决HTTP这种无状态的缺点,采用了cookie技术,就是用cookie来记录客户端的状态,然后写入请求和响应报文中。
客户端第一次请求服务器,服务器会给客户端一个cookie,自己也留一个,下次再请求的时候,有cookie,服务器就可以记起来客户端的状态了
85.HTTP/1.1的性能如何?
会产生对头阻塞问题
在最早的http1.0,采用的是短连接方式,具体过程是先TCP三次握手,然后一个请求一个响应,TCP四次挥手,接着再握手,请求响应,再挥手,总结来说就是必须一个请求一个响应
在http1.1之后,采用的是长连接的方式,不用发送请求之后必须等待响应才能发送下一个响应了。但是无论是http1.0还是http1.1都没有解决对头堵塞的问题
什么是对头阻塞问题?
就是如果请求1的响应没发送给客户端,则客户端无法发送请求2