复杂的系统需要分层,因为每一层都需要专注于一类事情。各层独立:屏蔽底层实现、透明、实现接口调用。灵活:每一层都可以使用最适合的技术来实现,功能以及暴露的接口的规则没有改变。分解:将复杂问题分解为许多比较小的、界线比较清晰简单的小问题来处理和解决。易于设计,实现和标准化。
1,面向连接的、可靠的(有序、不丢不重,段编号以及确认号:按序发送接收、确认(ACK)、超时重传(ARQ计时+累计确认+发送窗口)、差错控制)、基于字节(有拆分合并)、不保留报文边界、单播;双工通信;拥塞控制(慢开始 、 拥塞避免 、快重传 和 快恢复);流量控制。
2,流量控制:滑动窗口保证接收能及时接收,发送窗口:字节为单位、发送窗口(连续ARQ协议):已发送但未确认(重传)+可以发送未发送(可用窗口)、后沿:不动+前移,前沿:不动(无确认+接收窗大小不变、有确认+接收窗大小变小)+前移,可用窗口=0时超时(滑动均值)重传,零发送窗口窗口探测报文防死锁,发送窗口=min(接收窗口,拥塞窗口),接收<拥塞:接收端能力、接收>拥塞:网络拥塞状况;接收窗口:未有序到达(独立路由)+空白,接受缓存=按序到达+接收窗口,及时读取否则接受窗=0;
3,拥塞控制:防止网络过载、全局过程、无拥塞:增大、拥塞/可能拥塞:减小、慢开始(init=1、报文段确认则加一、传输轮次(窗口发送+确认)后倍增)、拥塞避免(x2,门限、一次往返+1)、快重传(单个数据丢失导致误判超时、超时为拥塞标志(cawd=1,thre=cawd/2、慢开始、通信不通数据、ack无发传输),接收方受到任何数据都立即发送有序ack,3ack时发送端立即发送缺失数据,防止超时 cawd=1)和快恢复(3ack触发、个别丢失、thred=cawd=cawd/2、拥塞避免);
4,三次握手:双方确认自己与对方的发送与接收是正常的。参数协商:双方知道对方的序列号(seq)同时让对方知道自己已经知道了对方的序列号(ack)。
第一次,本机将标识位 SYN 置为 1, seq = x发送给服务端。此时本机状态为SYN-SENT
第二次,服务器收到包之后,将状态切换为SYN-RECEIVED,并将标识位 SYN 和 ACK都置为1, seq = y, ack = x + 1, 并发送给客户端。
第三次,客户端收到包后,将状态切换为ESTABLISHED,并将标识位ACK置为1,seq = x + 1, ack = y + 1, 并发送给服务端。服务端收到包之后,也将状态切换为ESTABLISHED。
两次握手:第一次握手报文在连接释放后达到,B回复第二次握手报文后进入连接态,等待接收数据,A由于并未发送请求报文,所以不处理第二次报文,也无数据发送,而B则一直等待数据,三握手下A对第二个报文发送复位报文、B收到复位报文,不建立连接。
标识位ACK置为1 (ACK=1,ack才有意义,连接建立后ACK=1)表示我已确认收到seq为x的包。而SYN(SYN=1表示为连接请求和连接接受报文)表示这是我第一次随机生成seq的序列x,此后我每次发送的包都会在上一次发送的基础增加。
5,四次握手:全双工
1,客户端第一个「SYN」包丢了。
如果客户端第一个「SYN」包丢了,也就是服务端根本就不知道客户端曾经发过包,那么处理流程主要在客户端。在一定时间范围内,只要没有收到应答的「ACK」包,无论是请求包对方没有收到,还是对方的应答包自己没有收到,均认为是丢包了,会触发超时重传机制。所以此时会进入重传「SYN」包,会尝试三次,间隔时间分别是 5.8s、24s、48s。
2,服务端收到「SYN」并回复的「SYN,ACK」包丢了。
站在客户端的角度,会认为是最开始的那个「SYN」丢了,那么就继续重传,见1。
对服务端而言,如果发送的「SYN,ACK」包丢了,在超时时间内没有收到客户端发来的「ACK」包(第三次握手),也会触发重传,此时客户端处于 SYN_RCVD 状态,会依次等待 3s、6s、12s 后,重新发送「SYN,ACK」包。如果这个重试次数内,仍未收到「ACK」应答包,那么服务端会自动关闭这个连接。
3,客户端最后一次回复「SYN,ACK」的「ACK」包丢了。
如果最后一个「ACK」包丢了,服务端因为收不到「ACK」会走重传机制,而客户端此时进入 ESTABLISHED 状态。多数情况下,客户端进入 ESTABLISHED 状态后,则认为连接已建立,会立即发送数据。但是服务端因为没有收到最后一个「ACK」包,依然处于 SYN-RCVD 状态。若第三个报文中无数据,并且A在B等待期间发送数据传输报文,由于第三个无数据报文不消耗seq,第四个报文拥有和第三个报文一样的seq和ack,对B来说第四个报文就等价于第三个报文,只是该报文带数据而已,连接建立。若第三个报文中无数据无法建立连接。
4,客户端故意不发最后一次「SYN」包。
如果客户端是恶意的,在发送「SYN」包后,并收到「SYN,ACK」后就不回复了,那么服务端此时处于一种半连接的状态,虽然服务端会通过 tcp_synack_retries配置重试的次数,不会无限等待下去,但是这也是有一个时间周期的。如果短时间内存在大量的这种恶意连接,对服务端来说压力就会很大,这就是所谓的 SYN FLOOD 攻击。在接收到第一次握手报文后服务端不分配资源,并对对二个报文计算cookie,并对第三次握手的也计算一个cookie,两个cookie相同才分配资源。
如果一个包发出去,在一定时间内,只要没有收到对端的「ACK」回复,均认为这个包丢了,会触发超时重传机制。而不会关心到底是自己发的包丢了,还是对方的「ACK」丢了。直到触发重传的次数,直接关闭连接。
此时因为客户端没有收到「ACK」应答,会尝试重传之前的「FIN」请求,服务端收到后,又会立即再重传「ACK」。
而此时服务端已经进入 CLOSED-WAIT 状态,开始做断开连接前的准备工作。当准备好之后,会回复「FIN,ACK」(第三次挥手),第三次挥手拥有和第二次挥手报文相同的ack。只要这个消息没丢,客户端可以凭借「FIN,ACK」包中的响应序号,发送第四次挥手报文,直接从 FIN-WAIT-1 状态,进入 TIME-WAIT 状态,开始长达 2MSL 的等待。
服务端在超时后会重传,此时客户端处于 FIN-WAIT-2 状态,会一直等待并响应第三次挥手报文;
客户端在回复「ACK」后,会进入 TIME-WAIT 状态,开始长达 2MSL 的等待,服务端因为没有收到「ACK」的回复,会重试一段时间,收到客户端重发的第四次挥手报文后断开连接,或者直到服务端重试超时后主动断开。
无连接、面向报文:发送端无拆分,只是增加一个 UDP 头标识;接收端无拼接,去掉 UDP 头标识后上传;保留报文边界、应用程序必须选择合适大小的报文。一对多,多对多,多对一的方式;不可靠:无连接、无反馈、没有拥塞控制、无流量控制;实时性好;头部开销小;头部+数据检错;
DNS 解析: 域名树(.->…->单台计算机)、递归查询的过程 ,本地域名服务器 -> 向根域名服务器(. ) -> com顶级域名服务器(.com. )->google.com域名服务器(google.com.) ->… 找到后缓存到本地域名服务器。
缓存机制:浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,域名服务器缓存。
根域名服务器(.)收到请求后->返回这个(.com)顶级DNS服务器的IP->请求者(本地域名服务器)收到这台顶级DNS的服务器IP->向该服务器发起查询->.com服务器就会返回下一级的DNS服务器(google.com)的IP->请求者(本地域名服务器)继续查找,直到服务器找到(www.google.com)的主机。
DNS负载均衡:又叫做DNS重定向,返回一个跟用户最接近的点的IP地址给用户。
TCP 连接:三次握手:请求连接、同意连接、同意连接信号的确认。
发送 HTTP 请求:HTTPS:HTTP + SSL(or TLS),非对称加密,对称加密。HTTP请求报文: 请求行(GET index.html HTTP/1.1), 请求报头(附加信息)和请求正文(POST, PUT等方法需要客户端向服务器传递数据)。
服务器处理请求并返回 HTTP 报文:HTTP响应报文: 状态码, 响应报头和响应报文。缓存:Last-Modify(响应头) + If-Modified-Since(请求头),时间比对后若未修改返回304状态码,浏览器将从缓存中获取资源。反之返回200和资源内容。
浏览器解析渲染页面:WebKit渲染,边解析边渲染,下载资源(图片)。
连接结束:TCP释放连接四次握手、持久连接(keep-alive)
HTTP 无状态协议,需要额外的配置实现状态保存。
cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上,用于告知服务端前后两个请求是否来自同一浏览器。本地保存别人可以分析存放在本地的COOKIE并进行COOKIE欺骗。
session 是基于 cookie 实现的一种认证方式,主要作用就是通过服务端记录用户的状态。服务器端接受客户端请求后,建立一个session,并发送一个http响应到客户端,在响应中包含sessionId,客户以cookie的方式保存sessionId,在客户端发起的第二次请求时,浏览器会自动在请求头中带上cookie,服务器根据cookie找到session恢复数据环境。随着用户的增多,服务端压力增大。分布式下拓展性不强,粘性会话 Sticky Session:尽量让同一个用户的请求落到一台机器上。缺点:如果当前机器下线则用户的信息全部丢失。会话复制 Session Replication:将会话信息复制到所有机器上,无论用户请求落到哪台机器上都能取到之前的会话信息。缺点:复制需要成本,冗余过大,难以保证所有机器上会话信息一致。集中会话 Centralized Session:JDBC、Redis等集中保存信息,机器需要信息时到JDBC,Redis中取。
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
受害者登录a.com,并保留了登录凭证(Cookie)->攻击者引诱受害者访问了b.com->b.com 向 a.com 发送了一个请求:a.com/act=xx->浏览器会默认携带a.com的Cookie->a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求->a.com以受害者的名义执行了act=xx->攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。
解决方法:1,同源检测:CSRF大多来自第三方网站,那么就直接禁止外域(或者不受信任的域名)对我们发起请求。2,CSRF Token:攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。
token:类似于无状态的临时的证书签名,由uid+time+sign[+固定参数]组成,服务端验证浏览器携带的用户名和密码,验证通过后生成用户令牌(token)并返回给浏览器,浏览器再次访问时携带token,服务端校验token并返回相关数据。服务端不用存放 token 数据,用解析 token 的计算时间换取 session 的存储空间,减轻服务器存储压力。安全性高,分布式系统下扩展性强。
JWT:全称是JSON Web Token,用于在空间受限环境下安全传递“声明”,JWT跨语言支持、便于传输、易于扩展。JWT分成三部分,头部(header:声明的类型、声明的加密算法),第二部分是载荷(payload:存放有效信息,一般包含签发者、所面向的用户、接受方、过期时间、签发时间以及唯一身份标识,防篡改,不防泄露),第三部分是签名(signature:主要由头部、载荷以及秘钥组合加密而成)。
缺点:用户状态变化(删除,禁用,注销等)影响到业务而Token仍然有效时,仍然能利用token完成认证,当token过期后需要用户重新登录,用户体验差。
解决办法使用 accessToken (负责后端业务验证)+ refreshToken(负责续签验证)。认证后返回 accessToken + refreshToken,并保存在本地,服务端保存refreshToken,accessToken 失效时间应该设置较短,比如10分钟,refreshToken 失效时间可以长一点,比如 7 天。请求时只用 accessToken,客户端在 accessToken 在失效前主动发起请求用 refreshToken 返回一个新的 accessToken,或者在正常业务请求时判断access-token是否过期,过期了就顺带更新,用户无感提高用户体验。退出时客户端删除accessToken + refreshToken,服务端删除refresh-token。
TCP:一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口。
x1public class Server {
2 public static void main(String[] args) throws IOException {
3 ServerSocket ss = new ServerSocket(6666); // 监听指定端口
4 System.out.println("server is running...");
5 // 无限循环来处理客户端的连接
6 for (;;) {
7 // 每当有新的客户端连接进来后,就返回一个Socket实例,这个Socket实例就是用来和刚连接的客户端进行通信的。由于客户端很多,要实现并发处理,我们就必须为每个新的Socket创建一个新线程来处理.
8 // 如果没有客户端连接进来,accept()方法会阻塞并一直等待
9 Socket sock = ss.accept();
10 System.out.println("connected from " + sock.getRemoteSocketAddress());
11 Thread t = new Handler(sock);
12 t.start();
13 }
14 }
15}
16
17class Handler extends Thread {
18 Socket sock;
19
20 public Handler(Socket sock) {
21 this.sock = sock;
22 }
23
24
25 public void run() {
26 // 因为TCP是一种基于流的协议,因此,Java标准库使用InputStream和OutputStream来封装Socket的数据流
27 try (InputStream input = this.sock.getInputStream()) {
28 try (OutputStream output = this.sock.getOutputStream()) {
29 handle(input, output);
30 }
31 } catch (Exception e) {
32 try {
33 this.sock.close();
34 } catch (IOException ioe) {
35 }
36 System.out.println("client disconnected.");
37 }
38 }
39}
40
41public class Client {
42 public static void main(String[] args) throws IOException {
43 // 接成功,将返回一个Socket实例,用于后续通信。
44 Socket sock = new Socket("localhost", 6666);
45 try (InputStream input = sock.getInputStream()) {
46 try (OutputStream output = sock.getOutputStream()) {
47 handle(input, output);
48 }
49 }
50 sock.close();
51 System.out.println("disconnected.");
52 }
53}
xxxxxxxxxx
131// server
2DatagramSocket ds = new DatagramSocket(6666); // 监听指定端口
3for (;;) { // 无限循环
4 // 数据缓冲区:
5 byte[] buffer = new byte[1024];
6 DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
7 ds.receive(packet); // 收取一个UDP数据包
8 // 收取到的数据存储在buffer中获得数据,packet.getData()获得数据,由packet.getOffset(), packet.getLength()指定起始位置和长度
9 // 发送数据:
10 byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
11 packet.setData(data);
12 ds.send(packet);
13}
xxxxxxxxxx
171// client
2DatagramSocket ds = new DatagramSocket();
3// 后续接收UDP包时,等待时间最多不会超过1秒,否则在没有收到UDP包时,客户端会无限等待下去。这一点和服务器端不一样,服务器端可以无限等待,因为它本来就被设计成长时间运行。
4ds.setSoTimeout(1000);
5// connect()方法不是真连接,它是为了在客户端的DatagramSocket实例中保存服务器端的IP和端口号,确保这个DatagramSocket实例只能往指定的地址和端口发送UDP包,不能往其他地址和端口发送。
6ds.connect(InetAddress.getByName("localhost"), 6666); // 连接指定服务器和端口
7// 发送:
8byte[] data = "Hello".getBytes();
9DatagramPacket packet = new DatagramPacket(data, data.length);
10ds.send(packet);
11// 接收:
12byte[] buffer = new byte[1024];
13packet = new DatagramPacket(buffer, buffer.length);
14ds.receive(packet);
15// packet.getData()获得数据,由packet.getOffset(), packet.getLength()指定起始位置和长度
16// disconnect()也不是真正地断开连接,它只是清除了客户端DatagramSocket实例记录的远程服务器地址和端口号
17ds.disconnect();
路由器实现了不同网络之间的数据转发,交换机实现了特定网络内的数据交换。工作层次不同:交换机主要工作在数据链路层路由器工作在网络层。转发依据不同:交换机转发所依据的对象时:MAC地址。路由转发所依据的对象是:IP地址。主要功能不同:交换机主要用于组建局域网,而路由主要功能是将由交换机组好的局域网相互连接起来,或者接入Internet。
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。https协议需要到ca申请证书。http端口是80,https是443。http直接开始传输数据,https先通过非对称加密协商密钥,然后才是使用对称加密的数据传输。
客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。。
客户端的浏览器建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
Web服务器利用自己的私钥解密出会话密钥。
Web服务器利用会话密钥加密与客户端之间的通信。
协商密钥使用非对称(安全),传输数据使用对称加密(高效)。