14 Jul 2020
计算机网络与操作系统
TCP & UDP
TCP简介
- 面向连接的、可靠的、基于字节流的传输层通信协议
- 将应用层的数据流分割成报文段并发送给目标节点的TCP层
- 数据包都有序号,对方接收到则发送ack,未收到则重传。
- 使用校验和来校验数据在传输过程中是否有误
TCP和UDP的区别:
- 面向连接 vs 无连接
- 可靠性
- 有序性
- 速度
- 量级(头部大小,TCP是24个字节,UDP8个字节)
如何在UDP上实现可靠传输
在UDP协议之上,实现一个带超时的请求回应机制,让业务层负责超时重发,有可能取得比TCP通信更好的效果。但前提是单个请求或回应的包不应太大。
TCP连接如何保证安全可靠的:
TCP的可靠性是通过 1. 顺序编号和确认 + 2. 数据超时重传和数据应答机制来实现的。 提供的可靠性保证:
- 能够处理数据在传输过程中被破坏的问题
- 能够处理接收重复数据的问题
- 能够处理数据丢失及进行有效解决
- 能够处理接收端数据乱序到达问题
TCP 三次握手 四次挥手
三次握手
- SYN=1,seq=x
- SYN=1,ACK=1,seq=y,ack=x+1
- ACK=1,seq=x+1,ack=y+1
第一次握手:建立连接时,客户端发送SYN(seq=x)包到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即ACK+SYN包,此时服务器进入SYN_RECV状态
第三次握手:客户端收到服务器的ACK+SYN包,向服务器发送确认包ACK(ack=y+1),次包发送完毕,客户端和服务器端进入ESTABLISHED状态,完成三次握手。
为什么需要三次握手:为了初始化Sequence Number的初始值。
为什么需要3次握手,而不是2次?
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发 送正常
第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常, 对方发送接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
防止已失效的连接请求报文段突然又传到了服务器,因而产生错误。
首次握手的隐患–SYN超时 – 造成SYN Flood攻击
原因:
- Server收到client的SYN, 回复ACK_SYN的时候未收到ACK确认
- Server不断重试直至超时,Linux默认等待63秒才断开(1+2+4+8+16+32)
防护措施:
- SYN队列满后,通过tcp_tcp_syncookie参数回发SYN Cookie
- 若为正常连接则Client会发回SYN Cookie,直接建立连接
四次挥手
- FIN,seq=x
- ACK=1,seq=y,ack=x+1,服务进入CLOSE_WAIT状态
- FIN=1,ACK=1,seq=z,ack=x+1–客户端收到后要等2MSL后才close
- ACK=1,seq=x+1,ack=z+1)
2MSL(Maxium Segment Lifetime)定时器的作用有2个:
- 防止最后ACK丢失服务器无法关闭连接–确保有足够的时间让对方收到ACK包
- 防止某个连接中的重复报文段可能会出现在下一个连接中。–避免新旧连接混淆
为什么需要四次挥手?
因为TCP是全双工通信的,发送方和接收方都需要FIN报文和ACK报文
- 第一次挥手
因此当主动方发送断开连接的请求(即FIN报文)给被动方时,仅仅代表主动方不会再发送数据报文了,但主动方仍可以接收数据报文。 - 第二次挥手
被动方此时有可能还有相应的数据报文需要发送,因此需要先发送ACK报文,告知主动方“我知道你想断开连接的请求了”。这样主动方便不会因为没有收到应答而继续发送断开连接的请求(即FIN报文)。 - 第三次挥手
被动方在处理完数据报文后,便发送给主动方FIN报文;这样可以保证数据通信正常可靠地完成。发送完FIN报文后,被动方进入LAST_ACK阶段(超时等待)。 - 第四挥手
如果主动方及时发送ACK报文进行连接中断的确认,这时被动方就直接释放连接,进入可用状态。
服务器出现大量CLOSE_WAIT状态的原因:对方关闭socket连接,我方忙于读或写,没有及时关闭连接
相应对措施:
- 检查代码,特别是资源释放的代码
- 检查配置,特别是处理请求的线程配置
服务器上的查看方式:
netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'
输出如下:
TIME_WAIT 1507
CLOSE_WAIT 588
FIN_WAIT2 5
ESTABLISHED 413
TCP的计时器:
- 重传计时器RTO – 与RTT(发送一个数据包到收到对应的ACK,所花费的时间)有关
- 持续计时器 – 避免死锁问题,大小是初始与重传计时器相同,每发送一次又没有应答加倍直到上限60s为止。
- 保活计时器 – 防止连接长时间空闲
- TIME-WAIT计时器 – MSL的常用值是30-60s
HTTP
####状态码:
- 1xx:指示信息–表示请求已接收,继续处理
- 2xx: 成功–表示请求已被成功接收、理解、接受
- 3xx: 重定向–要完成请求必须进行更进一步的操作
- 4xx: 客户端错误–请求有语法错误或请求无法实现
- 5xx: 服务器端错误–服务器未能实现合法的请求
401 vs 403 – 401 Unauthorized是请求未经授权,403 Forbidden是服务器收到请求,但是拒绝提供服务(比如IP被禁)
500 vs 503 – 500 Internal Server Error 是服务器发生不可预期的错误,503 Server Unavaliable 是服务器当前不能处理客户端的请求,一段时间后可能恢复正常(比如连接池占满了)
HTTPS协议
HTTPS协议就是基于SSL的HTTP协议
HTTPS使用与HTTP不同的端⼝(HTTPS80 , HTTPSS443)提供了身份验证与加密通信⽅法,被⼴泛⽤于互联⽹上安全敏感的通信。
- 客户端请求SSL连接,并将⾃己⽀持的加密规则发给⽹站。
- 服务器端将⾃己的身份信息以证书形式发回给客户端。证书⾥面包含了⽹站地址,加密公钥,以及证书的颁发机构。
- 获得证书后,客户要做以下工作验证书合法性,如果证书受信任,客户端会生成一串随机数的密码,并⽤证书提供的公钥进⾏加密。将加密好的随机数发给服务。
- 获得到客户端发的加密了的随机数之后,服务器器⽤⾃己的私钥进⾏解密,得到这个随机数,把这个随机数作为对称加密的密钥。(利用⾮对称加密传输对称加密的密钥)
- 之后服务器与客户之间就可以⽤随机数对各⾃的信息进⾏加密,解密。
GET 与 POST的区别:(我们从3个层面来回答)
- HTTP报文层面:GET 将请求信息放到URL,POST放置在报文体中
- 数据库层面:GET符合幂等性和安全性,POST不符合
- 其他层面:GET可以被缓存、被存储,post不行
cookie和session的区别
cookie简介:
- 是由服务器发给客户端的特殊信息,以文本的形式存放在客户端 Set-Cookie:xxx
- 客户端再次请求时,会把cookie回发
- 服务器接收到后,会解析Cookie生成和客户端相对应的内容
session简介:
- 服务器端的机制,在服务器上保存的信息
- 根据sessionID
- 它的实现方式有两种:
- 使用cookie来实现,Set-Cookie: JSESSIONID=XXXX
- 使用URL回写来实现
什么是WebSockets?
WebSocket是一种计算机通信协议,通过单个TCP连接提供全双工通信信道。
WebSocket是双向的 -使用WebSocket客户端或服务器可以发起消息发送。
WebSocket是全双工的 -客户端和服务器通信是相互独立的。
单个TCP连接 -初始连接使用HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信
Light -与http相比,WebSocket消息数据交换要轻得多。
IO 多路复用
IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出CPU。多路是指网络连接,复用指的是同一个线程。
为什么有IO多路复用机制
没有IO多路复用机制时,有BIO、NIO两种实现方式,但有一些问题。
BIO | NIO | IO多路复用 |
---|---|---|
1. 服务端采用单线程,当accept一个请求后,在recv或send调用阻塞时,将无法accept其他请求,无法并发处理。 2. 服务器端采用多线程,当accept一个请求后,开启线程进行recv,可以完成并发处理,但随着请求数增加需要增加系统线程,而1W个线程真正发生读写事件的线程数不会超过20%,每次开启新线程是一种资源的浪费。 |
服务器端当accept一个请求后,加入fds集合,每次轮询一遍fds集合recv(非阻塞)数据,没有数据则立即返回错误,每次轮询所有的fd,也会浪费CPU。 | 服务器端采用单线程通过select/epoll等系统调用获取fd列表,遍历有事件的fd进行accept/recv/send,使其能支持更多的并发连接请求。 |
IO复用的三种实现方式
比较 | select | poll | epoll |
---|---|---|---|
支持一个进程能打开的最大连接数 | 单个进程所打开的FD是有限制的,通过FD_SETSIZE设置,其大小是32个整数的大小,32位机器上为32*32。 | 与select相比,只是没有fd的限制,其它基本一样。 | epoll只能在Linux下。虽然FD的大小有限制,但是很大,1G内存的机器上可以打开10W左右的连接 |
FD剧增后带来的IO效率问题 | 对socket扫描是线性扫描,采用轮询的方式,效率较低。所以随着FD的增加会造成遍历速度的”线性下降“的性能问题 | 同左 | 由于epoll是根据每个FD上面的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll不会有线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题 |
消息传递方式 | 每次调用select,都需要把fd集合从用户态拷贝到内核态。 | 同左 | 通过内核和用户空间共享一块内存来实现,性能较高 |
数据结构 | bitmap | array | 红黑树 |
最大连接数 | 1024 | 无上限 | 无上限 |
fd拷贝 | 每次调用select拷贝 | 每次调用poll拷贝 | fd首次调用epoll_ctl 拷贝,每次调用epoll_wait 不拷贝 |
工作效率 | 轮询O(n) | 轮询O(n) | 回调O(1) |
- select适合少量活跃连接,一般几千。
- epoll适合大量不太活跃的连接。
epoll两种模式
epoll LT | epoll ET |
---|---|
默认模式 | 高速模式 |
只要这个fd还有数据可读,每次epoll_wait都会返回它的事件,提醒用户程序去操作 | 它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读完,或者遇到EAGAIN错误。 |
操作系统命令
- 切忌vim直接打开大日志文件,因为会直接加载到内存的
Til next time,
at 13:14