问:为什么OSI模型有7层而因特网只有5层?
《自顶向下》中有这样一段:
表示层的作用是使通信和应用程序能够解释交换数据的含义。这些服务包括数据压缩,数据加密以及数据描述。会话层提供了数据交换定界和同步功能,包括了建立检查点和恢复方案。……这些层次提供的服务不重要吗?如果一个应用程序需要这些服务之一,将怎样呢?对这两个问题的回答是相同的:这留给应用程序开发者处理,应用程序开发者决定一个服务是否重要,如果该服务重要,应用程序开发者就应该在应用程序中构建该功能。
这一段就很好地回答了这个问题。
请求报文
GET /dir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
user-agent: Mozilla'
Accept-language: fr
空行
data data data ...(如果有)
常用Method
问:get和post有什么区别?
我认为,get和post最根本的区别是语义的区别 ,其他的所有区别都源于语义。
上文说过,get的语义是“根据某些关键字获取信息”,而post是“提交信息”。
因此出现了第一个区别:
而很多区别又是这第一个区别导致的,如网上经常提到的
还有一个网上经常提到的区别:
HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果。
从语义的角度理解,查询(get)某个资源很多次结果当然应该相同,而提交(post)某个资源很多次,服务器收到了多份资源,自然会产生不同的结果。
因此,get和post的最主要区别在于语义,其他的区别只是HTTP协议根据语义做出的优化调整。
响应报文
HTTP/1.1 200 OK
DATE: TUE, 09...
SERVER: Apache/2.2.3(CentOS)
Last-Modified:TUE, 09...
Content-Length: 6821
Content-Type: text/html
空行
data data data ...
状态码
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
问:状态码302,303,307之间有什么区别?
302是HTTP/1.0的状态码,对这个状态码的描述是:如果客户端发出 非幂等请求 后,收到服务端的302状态码,那么就不能自动的向新URI发送重复请求,除非得到用户的确认。然而在实际操作中,浏览器默认原本的非幂等请求已经执行完成,往往直接向新的url发一个幂等的 GET 请求,不会经过用户的确认。
303和307是HTTP/1.1的状态码,对302做了细分。303不需要经过用户确认,走302的实际操作流程,307则是走原本302描述的“合法”流程。
注意,在HTTP/1.1中,302还是走的“非法”流程。总结一下就是:
302文档描述 = 307文档描述 = 307实际执行 = 询问客户是否向新的url发出相同的非幂等请求
302实际执行 = 303文档描述 = 303实际执行 = 直接将原本的非幂等请求变为幂等的GET请求
Connection: Keep-Alive
。HTTP协议是一个无状态协议,无状态协议是指协议对于事务处理没有记忆能力。这意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。解决方案有两个:Session和Cookie。
Cookie: Cookie实际上是一小段文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把报文连同Cookie一同提交给服务器。服务器检查Cookie,以此来辨认用户状态。
Session: 当某个客户第一次访问服务器是,服务器端给他分配一个sessionID,服务器端维护一张sessionID—>用户状态的映射表,当客户再次访问该服务器时,报文中会包含该sessionID,服务器根据sessionID查映射表来获取客户状态。当session过期,服务器从映射表中删除对应的session。
两者的区别:
DNS协议是一个基于分布式数据库,将域名解析为相应的IP地址的协议,主要理解DNS解析的过程。
FTP是文件传输协议,数据端口20,控制端口21。
SMTP是简单邮件传输协议,大致过程就是发送代理–>发送方服务器–>接收方服务器–>接收代理。
和HTTP的区别:
1.SMTP是推协议,HTTP是拉协议
2.SMTP必须是7bit Ascall码,HTTP不一定
3.SMTP所有对象在一个报文中,HTTP一个对象一个报文。
前面提到,会话层提供了数据交换定界和同步功能,包括了建立检查点和恢复方案。一般认为HTTPS的SSL或TLS协议在会话层。
数字签名: 通过提供可鉴别的数字信息验证自身身份的一种方式。一套数字签名通常定义两种互补的运算,一个用于 签名,另一个用于 验证。签名的目的是要能够 唯一证明发送方的身份 ,防止中间人攻击、CSRF 跨域身份伪造等。
对称加密: 在对称加密算法中,使用的密钥只有一个,发送和接收双方都使用这个密钥对数据进行加密和解密。对称加密算法要求加密和解密方事先都必须知道加密的密钥。常见的对称加密算法主要有 D E S 、 3 D E S 、 A E S DES、3DES、AES DES、3DES、AES 等。
非对称加密: 在非对称加密算法中,使用两个密钥,一个是公开的的密钥,称为 公钥,一个是私有的的密钥,称为 私钥。如果用公钥加密,则可用私钥解密;如果用私钥加密,则要用公钥解密。常见的非对称加密算法主要有 R S A 、 D S A RSA、DSA RSA、DSA 等。
提供不同主机进程间的逻辑通信。
协议 | 情景 | 改变 | 实现 |
---|---|---|---|
r d t 1.0 rdt\ 1.0 rdt 1.0 | / | 发送方发出包后便结束通信 | 假设通信建立在完全可靠的网络信道之上 |
r d t 2.0 rdt\ 2.0 rdt 2.0 | 考虑数据包的比特差错 | 新增校验和机制 | 接收方通过返回ACK或NAK来表示自己是否收到了正确的包,发送方收到NAK重发 |
r d t 2.1 rdt\ 2.1 rdt 2.1 | 考虑ACK\NAK的比特差错 | 对数据包和ACK/NAK新增版本号(0,1) | 通过检查ACK/NAK的版本号判断其正确性 |
r d t 2.2 rdt\ 2.2 rdt 2.2 | / | 取消NAK | 只需要通过ACK的版本号就能判断接收方是否正确收到数据包 |
r d t 3.0 rdt\ 3.0 rdt 3.0 | 考虑信道上的丢包 | 新增计时器 | 发送方在计时器限定的时间内未收到ACK便重发 |
r d t rdt rdt 都是停等协议,即等到上一个包被正确接收后才会发送下一个包,效率低,因此又产生了两种流水线协议:回退N步 和 选择重传。
回退N步 ( G o − B a c k − N ) (Go-Back-N) (Go−Back−N)
选择重传 ( s e l e c t i v e r e p e a t ) (selective\ repeat) (selective repeat)
TCP的可靠数据传输
TCP的可靠数据传输可以理解为 G B N GBN GBN 和 S R SR SR 的混合体。
发送方:SYN=1,seq=X,进入SYN_SENT状态
接收方:SYN=1,ACK=1,seq=Y,ack=X+1,进入SYN_RECVD状态
发送方:SYN=0,ACK=1,seq=X+1,ack=Y+1,双方进入ESTABLISHED状态
问:为什么有了SYN还要有ACK?
参考TCP报文段头结构:
可以看到,在图中第四行中存在6bit的标识位,其中A就是ACK标识位,其含义是第三行的确认序号是否有效。S就是SYN标识位,标识其是否是一次建立连接的请求。因此SYN和ACK表示两种不同的涵义。
问:为什么需要三次握手?
原因有以下两点:
1.假设客户端第一次发的请求包因某种原因在通讯中滞留(如在路由器中排队),导致客户端发了第二次请求,第二次请求的连接关闭后。服务器收到了之前滞留的包,就会建立第二次连接,进而浪费资源。
2.前面在可靠数据传输中提到,要在存在比特差错的信道上进行可靠传输,至少需要两次数据传输,即数据包和ACK。握手是客户端向服务器端建立连接,服务器端向客户端建立连接的两次过程,因此理论上需要进行四次传输,而TCP在连接建立时,服务器端机智地将数据包和ack合并为一次,因此是三次握手。
问:接收方出现大量SYN_RECVD状态如何解决?
在正常情况下,SYN_RECVD状态的持续时间都很短,不可能出现大量SYN_RECVD的情况。当出现大量SYN_RECVD状态时,多半是收到了SYN攻击。
SYN攻击 :SYN攻击是指在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。
Linux下可用如下命令查看SYN_RECVD状态的数量:
netstat -n -p TCP | grep SYN_RECV
一般解决方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等,但是不能完全防范syn攻击。
以下是一组优化参数。
net.ipv4.tcp_syn_retries = 2
#服务器在等待不到终端的确认消息时,syn消息报文会重新发送,需要根据网络情况尽量减少,如果网络不好的情况下终端可能会出现连不上服务器的情况】
net.ipv4.tcp_synack_retries = 2
#服务器在等待不到终端的确认消息时,synack消息报文会重新发送,需要根据网络情况尽量减少,情况和上面一样
net.ipv4.tcp_syncookies = 1
#该功能可以防止部分SYN攻击
net.ipv4.tcp_max_syn_backlog = 1024
#tcp_max_syn_backlog 是SYN队列的长度,加大SYN队列长度可以容纳更多等待连接的网络连接数
发送方:FIN=1,seq=X,ack=Z,进入FIN_WAIT1状态
接收方:ACK=X+1,seq=Z,进入CLOSE_WAIT状态
接收方:FIN=1,seq=Y,ack=X+1,进入LAST_ACK状态
发送方:ack=Y+1,seq=X+1 进入TIME_WAIT状态,等2MSL后关闭
特殊情况:
发送方,接收方(同时):FIN=1 进入CLOSING状态
发送方,接收方:ACK 进入TIME_WAIT状态
问:为什么需要四次挥手?
前面提到,握手只需要三次是因为服务器端将数据包和ack的发送合并为一次。而在挥手时,服务器端则不能这样做,因为客户端发送FIN只是说明客户端已经把消息发完了,服务器端返回ACK后,还要继续把自己要发的其他东西发完,才会发FIN,因此挥手需要四次。
问:为什么TIME_WAIT状态要等2MSL(报文段最长生存时间)才关闭?
原因有以下两点:
1.前面说到,可能有数据包会滞留在网络中,要确保连接关闭时该连接中所有的发送的数据包都已到达或丢弃,因此要等待一段时间。
2.如果最后一个ACK丢失,服务器在等待1MSL后会将FIN重发,最长间隔即为2MSL,因此要等待2MSL以便能收到重发的FIN。
问:接收方出现大量TIME_WAIT状态如何解决?
出现大量TIME_WAIT状态主要因为TCP连接太多,因此优化的思路主要是
以下是一组优化参数。
net.ipv4.tcp_syncookies = 1
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout
#修改系默认的 TIMEOUT 时间
net.ipv4.tcp_keepalive_time = 1200
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 1024 65000
#表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_tw_buckets = 5000
#表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。
问:接收方出现大量CLOSE_WAIT状态如何解决?
CLOSE_WAIT状态是留给接收方把余下的一些包发完,因此一般很短时间内就应该结束了。因此大量CLOSE_WAIT状态出现一般是因为程序本身的问题,或者是发送剩余数据包时出现了问题,或者是忘记了关闭连接(发FIN)。
前面在可靠数据传输部分提到过,TCP的可靠数据传输会给每个数据包维护一个定时器,那么超时的时间如何计算呢?
估计往返时间: E s t i m a t e d R T T = ( 1 − α ) ∗ E s t i m a t e d R T T + α ∗ S a m p e l R T T EstimatedRTT = (1-α)*EstimatedRTT + α*SampelRTT EstimatedRTT=(1−α)∗EstimatedRTT+α∗SampelRTT
往返时间偏离量: D e v R T T = ( 1 − β ) ∗ D e v R T T + β ∗ ∣ E s t i m a t e d R T T − S a m p e l R T T ) DevRTT = (1-β)*DevRTT + β*|EstimatedRTT - SampelRTT) DevRTT=(1−β)∗DevRTT+β∗∣EstimatedRTT−SampelRTT)
则超时间隔: T i m e O u t I n t e r v a l = E s t i m a t e d R T T + 4 ∗ D e v R T T TimeOutInterval = EstimatedRTT + 4*DevRTT TimeOutInterval=EstimatedRTT+4∗DevRTT
TCP超时间隔加倍机制:
TCP每次发生超时重传后,会把下一次的超时间隔设置为原先的两倍。
当接收到上层数据或收到新的ACK时,才会再次用SampleRTT计算超时间隔。
假设接收方缓存区大小为 R c v B u f f e r RcvBuffer RcvBuffer,上层读取的最后一个字节编号为 L a s t B y t e R e a d LastByteRead LastByteRead,从网络中收到的最后一个字节的编号为 L a s t B y t e R c v d LastByteRcvd LastByteRcvd。TCP不允许缓存溢出,所以有: L a s t B y t e R c v d − L a s t B y t e R e a d ≤ R c v B u f f e r LastByteRcvd - LastByteRead ≤ RcvBuffer LastByteRcvd−LastByteRead≤RcvBuffer
因此窗口大小: r w n d = R c v B u f f e r − ( L a s t B y t e R c v d − L a s t B y t e R e a d ) rwnd = RcvBuffer - (LastByteRcvd - LastByteRead) rwnd=RcvBuffer−(LastByteRcvd−LastByteRead)
发送方窗口大小要小于等于接收方窗口大小: L a s t B y t e S e n t − L a s t B y t e A c k e d ≤ r w n d LastByteSent - LastByteAcked ≤ rwnd LastByteSent−LastByteAcked≤rwnd
拥塞控制说白了就是拥塞窗口大小在作死的边缘疯狂试探,以适应网络情况。记拥塞窗口为 c w n d cwnd cwnd,阈值 s s t h r e s h ssthresh ssthresh ,算法流程大概如下:
综合流量控制来看,有:
L a s t B y t e S e n t − L a s t B y t e A c k e d ≤ m i n { r w n d , c w n d } LastByteSent - LastByteAcked ≤ min\{rwnd,cwnd\} LastByteSent−LastByteAcked≤min{rwnd,cwnd}
问:TCP和UDP的区别?
TCP面向链接,UDP不面向连接。
TCP重视传输的可靠性,UDP重视传输的及时性。
TCP只能一对一,UDP可以一对多。
TCP提供流量控制和拥塞控制,UDP不提供。
TCP在接受方窗口缓存报文,对上层按序交付。UDP不按序。
TCP头部20字节,UDP头部8字节。