首页
计算机网络
二级制
补码
二进制整数都是以补码的形式出现的,正数的补码等于其原码和反码,负数的补码为其反码加上1的结果,这样只需要加法运算器就可以实现加法和减法运算(符号位也参与运算)。
位移运算
符号位均参与移动,注意移动的位数是mod的结果,负数右移时会在最高位补1,无符号右移则补0。
浮点数
浮点数使用科学计数法表示,由符号位、有效数字、指数三部分组成,计算机实际储存的值与真实值可能是不一样的。
- 符号位:最高1位,0表示正数,1表示负数;
- 阶码位:表示的是2的指数,单精度为符号位右侧8位,阶码位储存的是指数对应的移码,目的是把真实值映射到一个正数域。
- 由于规定阶码位全为0表示0,全为1表示无穷大,故单精度浮点数阶码位取值范围为[1,254],对应到能表示的指数范围为[-126,127]。
- 尾数位:剩余的位数用来存储有效数字,表示的是一个无限接近于2的数字1.xyz,保存时省去了第一个1,只保存xyz段。
网络体系结构
五层协议
- 应用层:为特定应用程序提供数据传输服务,如基于TCP的HTTP和FTP以及基于UDP的DNS等。
- 传输层:为进程提供通用数据传输服务。传输控制协议TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。
- 网络层:为主机提供数据传输服务,网络层把传输层传递下来的报文段或者用户数据报封装成分组。根据IP定义网络地址,区分网段,子网内根据ARP进行MAC寻址,子网外进行路由转发数据包。
- 数据链路层:数据链路层把网络层传下来的分组封装成数据帧。
- 物理层:物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
TCP/IP协议簇
它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层,应用层可能会直接使用 IP 层或者网络接口层。
过程
应用层按照既定的协议打包数据,由传输层加上双方通信的端口号,由网络层加上双方的IP地址,由链路层加上双方的MAC地址,并将数据拆分为数据帧,经过多个路由器和网关后,到达目的机器,接收数据时反过来操作。
TCP
报文头
TCP报文是封装在IP报文内的,每个IP头后紧接着是一个TCP头。
- 源端口号和目标端口号:各两字节,与源机器IP和目标机器IP组成的四元组标识唯一一条TCP连接;
- seq(序列号):4字节,表示所发送数据包的第一个字节的序号;
- ack(确认号):4字节,表示期望收到的来自对方的下一个数据包的第一个字节的序号;
- 头部长度:4个bit,表示TCP报文头的长度,已便确认数据包中数据段的开始位置;
- FLAG位:6个bit,SYN用作建立连接的同步信号,ACK用于对数据进行确认(由ack表示),FIN表示要求释放连接;
- 窗口:两个字节,表示本方滑动窗口的大小。
三次握手
- A 向 B 发送连接请求报文,将 SYN 设置为1,选择一个初始的序列号 x;
- B 收到连接请求报文后如果同意建立连接,则向 A 发送连接确认报文,将 SYN 和 ACK 都设置为1,确认号设置为 x+1,同时也选择一个初始的序列号 y;
- A 收到 B 的连接确认报文后,还要向 B 发出确认报文,ACK 设置为1,确认号设置为 y+1,序列号设置为 x+1。
- 原因:为了防止失效的连接请求到达服务器,让服务器错误打开连接,因为网络报文生存的时间往往会超过TCP请求的超时时间。
四次挥手
- A 发送连接释放报文,设置 FIN 为 1;
- B 收到之后发出确认报文,设置 ACK 为1,然后 B 处于 CLOSE_WAIT 状态,B 能向 A 发送数据但是 A 不能向 B 发送数据;
- 当 B 不再需要连接时,发送连接释放报文,设置 FIN 和 ACK 为 1;
- A 收到后发出确认报文,设置 ACK 为1,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接,B 收到 A 的确认报文后释放连接。
- CLOSE_WAIT:这个状态是为了让服务器端发送还未传送完毕的数据;
- TIME-WAIT:一是为了确保来自 A 的确认报文能被 B 接收到,因为一旦 B 在 2 MSL 内没有收到确认报文就会重发 FIN + ACK 报文,此时 A 再次收到后会再次发送确认报文并重新开始计时;二是为了使连接期间产生的请求全部消失,防止其与正常连接的请求数据包混淆。
- 使用netstat命令查看TCP连接状态,如果发现存在大量的TIME-WAIT状态连接,原因可能是大量的高并发短连接,在客户端进行优化,高并发场景下应该尽量的复用TCP连接。
Keep Alive
TCP连接的建立是基于文件描述符的,为了及时回收资源,支持Keep Alive功能,即隔段时间向对方发送心跳,一旦出现异常就会主动关闭连接,回收资源。
可靠传输
使用超时重传机制保证,如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。
滑动窗口和流量控制
发送方和接收方都会在缓冲区上维护一个滑动窗口,只有在窗口内的字节序被确认后才会移动窗口,接收方通过报文头的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小,通过这种方式影响发送方的发送速率,以保证接收方来的及接收数据。
拥塞控制
拥塞控制是为了降低整个网络的拥塞程度,发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
- 慢开始:发送最初执行慢开始,令 cwnd = 1,即只能发送 1 个报文段;当收到确认后,将 cwnd 加倍。
- 拥塞避免:因为慢开始是指数增长,因此设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1;如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。
- 快重传:接收方每次接收到报文段都应该对最后一个已收到的有序报文段进行确认,如果发送方连续收到三个重复确认,即表明下一个报文段丢失,此时执行快重传,立即重传下一个报文段。
- 快恢复:在快重传的场景下,只是丢失了个别报文段而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh。
TTL
位于IP报文投中,是数据包可经过的最多路由器数,初始值由源主机设置后,数据包每经过一个路由器值TTL减一,为0时则被丢弃并发送一个ICMP报文通知源主机,目的是为了防止源主机无休止的发送报文。
MTU & MSS
MTU一般用来说明数据链路层的最大传输单元,MSS指TCP连接建立后双方约定的最大TCP报文长度。
TCP在建立连接时,收发双方根据MTU计算出各自的MSS,通过三次握手互相确认彼此的MSS大小,取较小的MSS值作为双方在TCP层分段的最大payload,这样就避免了IP包分片。
如果底层的MTU是1500byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte。
PMTUD (Path MTU Discovery)
动态探测Path MTU,发出一个试探性的报文,并设置该报文不允许被分片,一直尝试直到确定出路径上的最小MTU。
虽然TCP可以确认MSS,但是两台主机之间的路径上还有路由器和交换机,而它们都有自己的MTU,如果数据帧超过了它们的MTU还是需要进行IP包分片。检查IP包的DF位以判断是否支持分片,如果支持则分片并为后几份IP包添加20字节的IP请求头;如果不支持则丢弃此IP包,同时给源主机发送回复报文并附上自己的MTU值,源主机会更新MTU值并再次尝试,这就是PMTUD。
粘包和拆包
TCP不存在粘包和拆包,只是因为存在发送缓冲区和滑动窗口机制的限制,底层可能会合并发送(使用tcpdump命令可以抓包传送细节),故在应用端为了区分来自不同send命令的数据包,可以在应用数据包前添加当前数据包的长度(Netty有封装好的实现)。
HTTP
- HTTP 1.0:每次请求都需要建立TCP连接
- HTTP1.1:默认支持长连接(Connection: keep-alive)
- HTTP2.0:多路复用、二进制分帧、首部压缩、服务器推送
- HTTP3.0:基于QUIC(quick)协议,可以定义为基于UDP实现HTTP2.0协议,用于解决公网传输中阻塞和丢包的问题。
请求方式
- 在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。
- GET 和 HEAD 是可缓存的,PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
- POST 方法 header 和 body 分开发送是部分浏览器或框架的请求方法,首先发送 header,服务段响应100后才会发送 body,目的是节约网络带宽。
信息安全
CORS协议(Cross-Origin Resource Sharing)
浏览器的跨域保护机制是只有在地址的协议名、域名、端口名均一样的情况下,才允许访问相同的cookie、localStorage或是发送Ajax请求等等。由于前后端分离存在合理的跨域要求,应用比较广泛的是CORS协议(Cross-Origin Resource Sharing),其核心思路是:在HTTP的请求头中设置相应的字段,浏览器在发现HTTP请求的相关字段被设置后,则会正常发起请求,由服务器判断此请求是否是合理的跨域请求。
XSS & CSRF
-
XSS:跨站脚本攻击,指往Html页面中插入恶意脚本,导致用户执行。防范方式主要是:设置Cookie为HTTPOnly,这样脚本就无法调用cookie;或对用户输入数据做过滤和转义,避免脚本代码运行。
-
CSRF:跨站请求伪造,指冒充用户发起请求。HTTP接口设计时需要注意防范不受信任的调用。
SSL
安全套接字层,工作于传输层和应用层之间,为应用提供数据加密传输。
HTTPS
是让 HTTP 先和 SSL 通信,再由 SSL 和 TCP 通信。
- 加密方式:使用非对称加密方式传输对称密钥从而保证安全性,使用对称加密方式进行通信从而保证效率。
- 证书:使用证书对通信方进行认证。CA 是客户端与服务器双方都可信赖的第三方机构,CA 会对服务端申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。
- 完整性保护:SSL 提供报文摘要功能来进行完整性保护,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。
请求流程:
- 浏览器向服务器发送https请求;
- 服务器向CA请求获取证书,然后向浏览器发送证书,附带非对称加密公钥,以及双方都支持的对称加密算法;
- 浏览器验证CA证书,生成对称加密密钥,使用服务器的公钥加密密钥,然后发送给服务器;
- 服务器使用自己的非对称加密私钥解密,得到对称加密密钥;
- 服务器和浏览器使用此加密密钥进行通信。
浏览器请求流程
- 解析URL;
- 封装HTTP请求报文;
- DNS域名解析获取IP地址,一直向上请求,先浏览器缓存然后操作系统缓存然后是本地域名服务器然后是上级域名服务器,获取到后向下传递并缓存;
- 浏览器获取到IP后与对应IP建立TCP连接;
- 浏览器发送http报文,添加TCP首部、IP首部、封装为以太网帧;
- 利用 ARP 协议根据 IP 地址获取作为通信目的地的 MAC 地址后转发给链路层;
- 服务器在链路层接收到报文后向上传递,并去除首部,处理HTTP请求,并以相同的方式发送HTTP响应报文;
- 最后客户端通知服务器断开TCP连接。
IO模型
阻塞和非阻塞:读写没有就绪或者读写没有完成,函数是否要一直等待还是采用轮询; 同步和异步:同步是读写由应用程序完成。异步是读写由操作系统来完成,并通过回调的机制通知应用程序。
BIO
同步阻塞IO,面向流,传统的模型,典型应用如Java的BIO。
应用进程使用recvfrom发起IO系统调用后,会一直阻塞直到内核缓存区的数据准备好,并将数据拷贝至用户空间,阻塞期间不消化CPU资源。
NIO
同步非阻塞IO,面向缓冲区,核心是Buffer(缓冲区)、Channel(通道)和Selector(多路复用器)。
应用进程仍然是使用recvfrom发起IO系统调用,但如果内核缓存区数据未准备好则立即返回错误标识,准备好了则将数据拷贝到用户空间,因此需要应用程序不断发起询问内核数据是否准备好。
多路复用IO
即Reactor模型(主动模式),使用fd(文件句柄)绑定一个socket,典型的如Linux的select、poll和epoll以及Java的NIO。
多个应用进程的IO事件注册到一个复用器(select)上,然后使用一个进程调用该select,select会监听所有注册进来的IO事件,会一直阻塞直到任一个IO的数据在内核缓冲区中可用,select调用进程可以自己或通知注册进程来再次发起recvfrom读取内核缓冲区中准备好的数据。
- select:使用轮询方式,每次都需要把fd集合(固定大小1024)从用户态拷贝到内核态后,才能检查IO事件是否就绪;
- poll:方式与select一样,不过没有最大文件数限制,因为是使用链表结构存储fd;
- epoll:即event poll,虽然连接数有上限,但是很大。在Linux内核中有专门的epoll文件系统,适用于监控大量的但大多数活跃度不高的fd,这样每次只需要将少量的文件句柄从内核态拷贝到用户态。
- epoll_create():用于创建epoll对象,除了会使用红黑树存储注册的socket外,还使用链表保存准备就绪的IO事件,通过内核与用户mmap共享一块内存来实现的;
- epoll_ctl():向epoll对象中注册socket,同时会往内核注册一个回调函数,当socket上数据到达后,将其添加到事件链表中;
- epoll_wait():只需要从事件链表读取准备就绪的句柄即可。
信号驱动IO
应用进程发起一个IO操作,信号处理程序通过系统调用sigaction,往内核注册一个信号处理函数,然后请求即刻返回,当内核数据准备就绪后,就生成对应进程的SIGIO信号,通过信号处理程序通知应用线程可以调用recvfrom来读取数据。
异步IO
Proactor模型(被动模式),属于异步操作,因为只有异步IO不需要应用进程自己调用recvfrom来读取数据,典型如Java的AIO。
应用进程发起一个aio_read请求之后直接返回,如果内核缓存区数据准备好了,内核主动拷贝数据到用户空间,完成后通过aio_read中指定的信号通知到应用进程。
零拷贝和直接内存映射
NIO的零拷贝由transferTo方法实现,transferTo方法将数据从FileChannel对象传送到可写的字节通道(如Socket Channel等),将将数据直接在从一个通道传输到另一个通道,而不需要借助缓冲区。需要借助操作系统,在linux系统中会引起sendfile()系统调用,实现了数据直接从内核的读缓冲区传输到套接字缓冲区,避免了用户态与内核态之间的数据拷贝。
流程:
- transferTo方法调用触发DMA引擎将文件上下文信息拷贝到内核读缓冲区,接着内核将数据从内核缓冲区拷贝到与套接字相关联的缓冲区。
- DMA引擎将数据从内核套接字缓冲区传输到协议引擎(第三次数据拷贝)。
mmap
通过内存映射,将文件直接映射到内核缓存区,这样就免去了拷贝,同时用户空间可以共享内核空间的数据。
sendFile
首先还是DMA拷贝到内核buffer,然后再通过CPU拷贝到socket buffer,最后DMA拷贝到协议栈。但这次的CPU拷贝,拷贝的内容很少,只拷贝内核buffer的长度、偏移量等信息,消耗很低,可以忽略,即就是零拷贝。