0%

[toc]

第一题:全排列问题,字符串处理麻烦#

1087. 花括号展开 - 力扣(LeetCode)

1661355892528

很常见的搜索全排列题

但是处理这个字符串折腾了我好久,看我下面这个也太麻烦了

1
2
3
4
5
6
7
List<char[]> lists = new ArrayList<>();
String[] split = ss.split(",");
char[] cs = new char[split.length];
for (int j = 0; j < split.length;j++){
cs[j] = split[j].charAt(0);
}
lists.add(cs);

看看答案怎么做的,好像其他人都是逐个判断的。

1661356015380

也是,频繁substring还是很耗时间的,不建议

我的答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public String[] expand(String s) {
List<char[]> lists = new ArrayList<>();
for (int i = 0; i < s.length();i++) {
if (s.charAt(i) != '{') {
lists.add(new char[]{s.charAt(i)});
} else {
int end = s.indexOf("}", i);
String ss = s.substring(i+1, end);
// todo:看下答案这里怎么取的比较好
String[] split = ss.split(",");
char[] cs = new char[split.length];
for (int j = 0; j < split.length;j++){
cs[j] = split[j].charAt(0);
}
lists.add(cs);
i = end;
}
}
List<String> res = new ArrayList<>();
dfs(lists, 0, new StringBuilder(), res);
Collections.sort(res);
return res.toArray(new String[0]);
}

void dfs(List<char[]> lists, int index, StringBuilder sb, List<String> res) {
if (index == lists.size()) {
res.add(sb.toString());
return;
}

for (char c : lists.get(index)) {
sb.append(c);
dfs(lists, index+1, sb, res);
sb.deleteCharAt(sb.length()-1);
}
}
}

第二题:简单动态规划#

2244. 完成所有任务需要的最少轮数 - 力扣(LeetCode)

1661356069871

一眼想到动态规划,但就是要注意题意得先排序,还要注意溢出的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int minimumRounds(int[] tasks) {
long[] dp = new long[tasks.length];
Arrays.sort(tasks);
for (int i = 0; i < dp.length;i++) {
int task = tasks[i];
dp[i] = Integer.MAX_VALUE;
for (int j = i-1;j>=i-2 && j >=0;j--) {
if (tasks[j] != task) {
break;
}
dp[i] = Math.min(dp[i], 1 + (j-1>=0?dp[j-1]:0));
}
}
return dp[tasks.length-1] >=Integer.MAX_VALUE ? -1 : (int)dp[tasks.length-1];
}
}

第三题:通过数据范围决定用动态规划而不是搜索#

剑指 Offer II 104. 排列的数目 - 力扣(LeetCode)

1661356141861

看起来以为要搜索,结果一看数据范围:

1661356155356

target最大1000,且都是正数,那么可以直接基于总和做动态规划了,因为不会有超过target的情况出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target+1];
dp[0] = 1;
for (int sum = 1; sum <= target;sum++ ) {
for (int num : nums) {
if (sum - num < 0) {
continue;
}
dp[sum] += dp[sum - num];
}
}
return dp[target];
}
}

[toc]

互联网发展阶段#

  1. 分组交换网ARPANET——
  2. 美国国家基金会NSF——三级网络模型: 主干网、地区、校园/企业往
  3. 多层次 ISP 结构
    ISP: 互联网服务提供商, 申请ip租给用户
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ja7k64lh-1613409272592)(en-resource://database/1830:1)]
    IXP: 互联网交换点—— 可让2个网络直接相连,交换分组,用局域网互联。

分层模型#

OSI七层模型#

应用层
表示层(数据信息变化)
会话层(建立进程间会话)
运输层
网络层
链路层
物理层

TCP/IP四层模型#

应用层(FTP\SMTP)
运输层(TCP\UDP)
网络层(IP、ICMP、IGMP)
网络接口层(MAC, 设备驱动,接口卡,以太网,令牌环)

  • 对应关系
    OSI中的应用层、会话层、表示层对应TCP/IP中的应用层。
    OSI中的数据链路层、物理层对应TCP/IP中网络接口层。

教科书五层模型#

应用层——数据
运输层——数据报
网络层——分组/包
链路层——数据帧
物理层——比特,电流,电缆

一些其他概念#

  • 封装:从应用程序发出的数据,到链路层的过程种, 数据会不断变长,添加各种首部信息

  • 分用:从链路传到应用程序时,会慢慢去掉首部

  • 服务区别:
    重复型服务: 来一个连接,处理一个, 处理完再接收下一个(UDP)
    并发型服务: 来一个则生成一个服务器做处理,可同时处理多个(TCP)

  • Internet: 通过TCP/IP通信的主机集合

  • internet: 只所有的通信住机

[toc]

因为http是非常重要的东西,restful经常问道, 所以单独抽出一章节讲解。

这个最好看图解HTTP笔记里的内容。


HTTP常见问题和概念#

Q: Http为什么是无连接 和无状态的?#

A:

  • 一.无连接:每一个访问都是无连接,服务器挨个处理访问队列里的访问,处理完一个就关闭连接,这事儿就完了,然后处理下一个新的无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接
  • 二.无状态:
  1. 协议对于事务处理没有记忆能力
  2. 对同一个url请求没有上下文关系
  3. 每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
  4. 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器

Q: 1.1.2 那么HTTP的持久连接又是怎么回事?#

A:
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
实现长连接需要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
TCP短连接长连接都由客户端发起,而TCP长连接的保活功能主要为服务器应用提供。
如果客户端已经消失而连接未断开,则会使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,此时服务器将永远等待客户端的数据。保活功能就是试图在服务端器端检测到这种半开放的连接。也因为短连接、长连接的实现在HTTP之外,与HTTP无关,从HTTP自身来看,HTTP依然是无连接的


Q: 浏览器从发送到接收,发生了什么?#

A:
24d799de5436b49c1f29cf4c3b40aa7ef3baf1ac

HTTP状态码和重定向#


Q: HTTP状态码有哪些?#

A:
状态码分类表
90f8198400ce1f1b8f031e861471dbf36fd154c0

各类别常见状态码:

  • 2xx (3种)

200 OK:表示从客户端发送给服务器的请求被正常处理并返回;

204 No Content:表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回);

206 Patial Content:表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求,响应报文中包含由Content-Range指定范围的实体内容。

  • 3xx (5种)

301 Moved Permanently:永久性重定向,表示请求的资源被分配了新的URL,之后应使用更改的URL;

302 Found:临时性重定向,表示请求的资源被分配了新的URL,希望本次访问使用新的URL;

301与302的区别:前者是永久移动,后者是临时移动(之后可能还会更改URL)

303 See Other:表示请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源;
302与303的区别:后者明确表示客户端应当采用GET方式获取资源

304 Not Modified:表示客户端发送附带条件(是指采用GET方法的请求报文中包含if-Match、If-Modified-Since、If-None-Match、If-Range、If-Unmodified-Since中任一首部)的请求时,服务器端允许访问资源,但是请求为满足条件的情况下返回改状态码;

307 Temporary Redirect:临时重定向,与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET;(不同浏览器可能会出现不同的情况);

  • 4xx (4种)

400 Bad Request:表示请求报文中存在语法错误;

401 Unauthorized:未经许可,需要通过HTTP认证;

403 Forbidden:服务器拒绝该次访问(访问权限出现问题)

404 Not Found:表示服务器上无法找到请求的资源,除此之外,也可以在服务器拒绝请求但不想给拒绝原因时使用;

  • 5xx (2种)

500 Inter Server Error:表示服务器在执行请求时发生了错误,也有可能是web应用存在的bug或某些临时的错误时;

503 Server Unavailable:表示服务器暂时处于超负载或正在进行停机维护,无法处理请求;


Q: HTTP的重定向是什么?#

在 HTTP 协议中,重定向操作由服务器通过发送特殊的响应(即 redirects)而触发。HTTP 协议的重定向响应的状态码为 3xx 。

浏览器在接收到重定向响应的时候,会采用该响应提供的新的 URL ,并立即进行加载;大多数情况下,除了会有一小部分性能损失之外,重定向操作对于用户来说是不可见的。
HTTP 的重定向


Q: HTTP的Forward和Redirect 有什么区别?#

A:
直接转发方式(Forward),客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于一个每个信息资源是共享的。

间接转发方式(Redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。

直接转发就相当于:“A找B借钱,B说没有,B去找C借,借到借不到都会把消息传递给A”;

间接转发就相当于:“A找B借钱,B说没有,让A去找C借”。

代码实际应用

HTTPS安全#


Q: 讲一下HTTPS双向认证的过程?#

A:

  1. 服务端发送证书, 证书中包含网站信息、CA签名、网站公钥
  2. 客户端校验CA签名,确认是CA所签且来源方正是该网站。
  3. 客户端生成会话密钥
  4. 客户端用网站公钥对会话密钥进行加密,生成非对称的会话密钥密文
  5. 客户端发送会话密钥密文给服务端
  6. 服务端用私钥解密, 得到明文会话密钥。
  7. 二者使用会话密钥进行加密传输

故事+图文,一次性解决你对HTTPS认证过程的所有疑惑


Q: MD5 在双向认证过程中有什么用?#

A:
对网址申请信息进行摘要, 避免签名内容过长。同时也避免了逆向推导原网址信息的过程。


Q: 什么是证书链?#

A:

  • 一个证书或证书链的拆封操作,是为了
    从中获得一个公钥。可示为X1p?X1<>,这为一个中缀操作,其左操作数为一个认证机构的公钥,
    右操作数则为该认证机构所颁发的一个证书。如果能正确解开,输出结果为用户的公钥

  • 证书链验证的要求是,路径中每个证书从最终实体到根证书
    都是有效的,并且每个证书都要正确地对应发行该证书的权威可信任性CA。操作表达式为 Ap?A<>B<>,
    指出该操作使用A的公钥,从B的证书中获得B的公钥Bp,然后再通过 Bp来解封C的证书。操作的最终结果

说人话就是下游证书解出来是上游父证书的密文公钥,然后继续解

理解证书和证书链

HTTP语义——POST和GET#


Q: post和get的区别是什么?#

A:

  • GET参数包含在url中, post参数还包含在requestBody中。 所以不建议用get参数传递敏感信息。
  • get最大提交url为2k,即放在url里的参数有限制。 post的requestBody则比get大很多。
  • get一次只生成1个数据包(header和data一起发,返回200), post会生成2个(先发header,响应100,再发data,返回200)。
  • get请求可以被浏览器主动缓存, post则不会,除非浏览器中主动设置缓存post
  • 浏览器回退的时候,get没影响,但是post会再次提交数据,可能导致重复提交。
    GET和POST两种基本请求方法的详细区别
  • get只接收ASCII码,post则可以在body中传输多种编码内容。
  • post比get更安全, 因为参数没有直接暴露在url里。

Q: 上面提到了get缓存, 浏览器怎么确定缓存是否要更新?#

A:
首先, 请求有个Cache-Control, 里面可以指定最大缓存时间。


Q: 如果缓存超期了, 但服务端资源确实没有变化,如何避免客户端重发请求?#

A:
服务端的响应中,携带last-modified时间给客户端。
客户端缓存超期时,把last_modified这个时间传递过去, 服务端校验如果发现时间一致,则不做业务处理,直接返回304, 让客户端直接继续用本地缓存即可。


Q: 上面提到的, 浏览器get缓存,有什么办法避免缓存?#

A: 给get请求的url中加个时间戳。或者设置no-cache指令
关于浏览器缓存的其他解答


Q: 除了POST和GET请求, 还有哪些HTTP方法?#

A:

  • HEAD:类似get请求, 返回的响应里不包含body,只给响应报头
  • PUT:发送的数据专门用于覆盖某数据
  • DELETE:请求服务器删除指定资源或者页面
  • TRACE: 回显服务起收到的请求, 用于测试或者诊断
  • CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
  • OPTIONS: 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性

Q: 我可以在get里加body么?#

A:
完全可以加,没有问题, GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。 例如某些框架可能针对get和post做过封装来限制一些语义。


Q: post一定就比get安全么?#

A:
如果用了HTTPS, 则post比get安全, 否则是一样的的,因为传输时都是明文传输。


HTTP额外特性#

Q: HTTP的keep-alive和 TCP的keep-alive有什么区别?#

A:
区别一:

  • HTTP 的 Keep-Alive,是由应用层(用户态) 实现的,称为 HTTP 长连接;
  • TCP 的 Keepalive,是由 TCP 层(内核态) 实现的,称为 TCP 保活机制;

区别二:

  • HTTP 的 Keep-Alive 就是实现了这个功能,可以使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,避免了连接建立和释放的开销,这个方法称为 HTTP 长连接
  • TCP 的 Keepalive 这东西其实就是 TCP 的保活机制
    如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文

区别三:


Q: 什么是HTTP 流水线技术?#

A:
所谓的 HTTP 流水线,是客户端可以先一次性发送多个请求,而在发送过程中不需先等待服务器的回应,可以减少整体的响应时间。

举例来说,客户端需要请求两个资源。以前的做法是,在同一个 TCP 连接里面,先发送 A 请求,然后等待服务器做出回应,收到后再发出 B 请求。HTTP 流水线机制则允许客户端同时发出 A 请求和 B 请求。
3f834d8022663b7ee0ce1602133f09dcf0f8c5cf


Q: HTTP请求头里有什么东西?#

A:

  • Host: www.study.com // 请求的地址域名和端口,不包括协议
  • Connection: keep-alive    // 连接类型,持续连接
  • Upgrade-Insecure-Requests:1 // http 自动升级到https,防止跨域问题但是域名端口都不同的不会提升
  • User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36 //浏览器的用户代理信息
  • Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 //浏览器支持的请求类型
  • Accept-Encoding: gzip, deflate, sdch //浏览器能处理的压缩代码
  • Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 //浏览器当前设置语言
    post新加的请求头:
  • Content-Length: 29 //请求参数长度
  • Cache-Control: max-age=0 //强制要求服务器返回最新的文件内容,也就是不走缓存,返回的200
  • Origin: http://www.study.com //请求来源地址,包括协议
  • Referer: http://www.study.com/day02/01-login.html //原始的url,不带锚点,比方说在谷歌打开百度,feferer显示的是谷歌的url

Q: cookie有什么用?#

A:
HTTP协议作为无状态协议,对于HTTP协议而言,无状态同样指每次request请求之前是相互独立的,当前请求并不会记录它的上一次请求信息。那么问题来了,既然无状态,那完成一套完整的业务逻辑,发送多次请求的情况数不胜数,使用http如何将上下文请求进行关联呢?机智的人类通过优化,找到了一种简单的方式记录http协议的请求信息.

  1. 浏览器发送request请求到服务器,服务器除了返回请求的response之外,还给请求分配一个唯一标识ID,协同response一并返回给浏览器。
  2. 同时服务器在本地创建一个MAP结构,专门以key-value(请求ID-会话内容)形式将每个request进行存储
  3. 此时浏览器的request已经被赋予了一个ID,第二次访问时,服务器先从request中查找该ID,根据ID查找维护会话的content内容,该内容中记录了上一次request的信息状态。
  4. 根据查找出的request信息生成基于这些信息的response内容,再次返回给浏览器。如果有需要会再次更新会话内容,为下一次请求提供准备。

根据这个会话ID,以建立多次请求-响应模式的关联数据传递, 这就是cookie和session对无状态的http协议的强大作用。
服务端生成这个全局的唯一标识,传递给客户端用于唯一标记这次请求,也就是cookie;而服务器创建的那个map结构就是session。


Q: cookie中会包含哪些内容?#

A:

  • Domain:域,表示当前cookie所属于哪个域或子域下面。
    对于服务器返回的Set-Cookie中,如果没有指定Domain的值,那么其Domain的值是默认为当前所提交的http的请求所对应的主域名的。比如访问 http://www.example.com,返回一个cookie,没有指名domain值,那么其为值为默认的www.example.com
  • Path:表示cookie的所属路径。
  • Expire time/Max-age:表示了cookie的有效期。expire的值,是一个时间,过了这个时间,该cookie就失效了。或者是用max-age指定当前cookie是在多长时间之后而失效。如果服务器返回的一个cookie,没有指定其expire time,那么表明此cookie有效期只是当前的session,即是session cookie,当前session会话结束后,就过期了。对应的,当关闭(浏览器中)该页面的时候,此cookie就应该被浏览器所删除了。
  • secure:表示该cookie只能用https传输。一般用于包含认证信息的cookie,要求传输此cookie的时候,必须用https传输。
  • httponly:表示此cookie必须用于http或https传输。这意味着,浏览器脚本,比如javascript中,是不允许访问操作此cookie的。
    1f9ba53e2ef279db7a874a0a4ab4b0fcf312ddd8
    Http协议中Cookie详细介绍

Q: cookie和session的区别?#

A:
会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。
常用的会话跟踪技术是Cookie与Session。

  1. Cookie通过在客户端记录信息确定用户身份,Cookie具有不可跨域名性
    Session通过在服务器端记录信息确定用户身份。

  2. Session需要使用Cookie作为识别标志

  3. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。

  4. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie

Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

cookie和session的详解与区别


Q: 客户端浏览器将Cookie功能禁用, 那session可以怎么办?#

A:
URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态


Q: 服务端如何涉及高可用的session特性?#

A:
保证session一致性的架构设计常见方法:

  • session同步法:多台web-server相互同步数据(代价高,无法水平扩容)
  • 客户端存储法:session存到用户本地cookie中(带宽变大,不安全)
  • 反向代理hash一致性:四层hash和七层hash都可以做,保证一个用户的请求落在一台web-server上
  • 后端统一存储:存到redis集群或者数据库,web-server重启和扩容,session也不会丢失

建议:
web层、service层无状态是大规模分布式系统设计原则之一,session属于状态,不宜放在web层
让专业的软件做专业的事情,web-server存session?还是让cache去做这样的事情吧

session一致性架构设计实践


Q: token又是什么?#

A:
Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。

Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。

使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
Token 、Cookie和Session的区别

[toc]

1 DNS#

  • 全称Domain Name System, 域名系统。
  • 传输层使用UDP协议,端口为53

Q: DNS为什么要使用UDP协议呢?
A:
DNS这个东西通信模式相当固定,报文基本上一个包搞定。大多数情况一问一答就结束了。
你三次握手来回3个包呢,人家一来一回就已经结速了,这种时候搞什么TCP?有必要中间连续穿好几次数据保证可靠性么?

1.1 域名#

  • 域名由 “标号.标号.标号”组成
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rk1b92vm-1613753140851)(en-resource://database/2048:1)]

  • 标号由字母和“-”组成。

  • 标号不超过63个字符

  • 总域名(标号之和)不能超过255

  • 分为根、顶级、二级、三级
    在这里插入图片描述

  • 顶级域名分类:

    1. 国家顶级域名nTLD: 例如cn\us
    2. 通用顶级域名gTLD:例如com\net\gov
    3. 基础建设顶级域(arpa)
      4.测试顶级域
  • 域名和ip可以是多对多的关系, 即1个域名可以对应多个ip(分布式多活), 1个ip也可以被绑定成不同的域名。

1.2 域名服务器#

  • 每个域名和ip的对应关系都会存在域名服务器中。
  • DNS采用分区的方式,解决域名服务器过多的问题,每个域名服务器管理一个区。
  • 分布式部署,C/S模式
    在这里插入图片描述

1.3 域名查找机制#

1.3.1 迭代查找(最常用)#

在这里插入图片描述

  1. 查找域名时,会先根据缓存,再根据本机host文件
  2. 当本地都没有,就会启动dns域名查找机制。
  3. 迭代查找记住就是会以本地DNS服务器为关键节点, 先去根域名寻求顶级域名服务器的ip
  4. 再去顶级域名却寻二级域的位置, 最后再去权威DNS服务器查找最终位置。
  • 注意像根域名服务器的位置一般也会做缓存,所以1、2、3这几步一般也是不用重复去做的,很可能查询一个陌生的域名时,直接在本地DNS那边定位到了4那一步。

1.3.2 递归查找(很少)#

递归其实就是从根域名如果找不到, 则让根域名发起请求向顶级域名查询。
即发起请求的对象变了,这种显然会占用根域名服务器的客户端资源,明显不好。

2 FTP文件传送协议#

基于TCP传输,可靠性高。

2.1 文件上传过程#

客户端发送PASV命令
在这里插入图片描述

可以看到FTP服务器会给每个连接建立相应独立的控制进程和数据进程。

2.2 文件下载过程#

其实就是发送了PORT命令:
PORT h1,h2,h3,h4,p1,p2 (h1-h4是IP地址,p1-p2是端口号)
在这里插入图片描述

可以看到下载过程是FTP服务器向对方发起连接


  • 注意上面发起会话时,会有身份认证的过程,同时也可以配置用户权限。
  • FTP不适合在2个计算机直接进行共享读写文件(因为会进行资源占用), 只适合单次的上传和下载。

3 TFTP 简单文件传送协议#

  • 使用UDP传输
  • 支持文件传输但是无交互命令
  • 应用层报文按序编号
  • 熟知端口为69
  • 代码轻量,程序占用小
  • 传输过程类似于停止-等待协议:

单帧滑动-停止等待协议

  • 一次只发一帧,1次只收一帧,并确认占有信道后才继续
  • 发送端:收到ack确认后,才会发下一个,每个都有个定时器
  • 接收端:序号不匹配递增的话都会丢弃。

3.1 P2P文件传输#

FTP和TFTP,都可能是有人把文件存在一个集中的服务器上,大家去下载上传。

而P2P则是建立了主机和主机间的直接通信, 不需要依赖中间服务器

  • 类似于别人直接从你电脑的目录上下载文件而不是从百度网盘里下载

4 SMTP电子邮件协议#

4.1 组件构成#

4.1.1 用户代理UA(user agent)#

其实就是邮件客户端,类似于foxmail、outlook、qq邮箱等exe程序,

4.1.2 邮件服务器#

  • 功能: 发送和接收邮件,报告邮件的传送情况
  • 具备C/S工作模式: 发送邮件时是一个客户端,接收邮件时是一个服务端
  • 不同邮件提供商有自己的服务器,但是他们之间可以互相通信。

4.1.3 协议#

使用SMTP接收协议和POP读取服务完成整套邮件系统协议。

4.2 通信步骤#

4.2.1 连接建立#

  • 用户使用UA发送邮件到自己UA所属的邮件服务器
  • 服务器定期扫描,发现有新的未发送邮件
  • 向目的端服务器试图建立连接

4.2.2 邮件传送#

  1. 发送端发送MAIL命令
  2. 接收端回答“250 OK”,即准备好接收
  3. 发送端发送多个"RCPT+ 收件人"的命令, 以向对方确认是否可以接收
  4. 接收端逐一应答“250 ok”或者“550 no such user”
  5. 获得ok应答后,使用DATA命令发送邮件内容
  6. 接收端回复 354 xxx end xxx., 告知邮件接收结束

4.2.3 连接释放#

发送端发送QUIT命令
接收端返回221同意释放连接

4.2.4 POP推送给客户端UA#

注意前面3步只是完成了从发送者到 接收者所属邮件服务器的步骤,还没有到接收者本地的邮箱应用。
接收者客户端会通过POP协议定期去拉取邮件。

[toc]

mysql服务器响应原理dc40bbcdffcfae8017e64a957a15a800a640516e#


事务ACID#

  • A是 atomicity原子性, 事务内的行为一次性执行完,要么就回退
  • C是consistency一致性 有a+b=c的限制条件,然后a变化的同时,b也必须跟着变化
  • I是isolation隔离性 事务隔离,即事务的中间执行过程,对另外一个事务不可见。
  • D是durability持久性 提交i成功后,修改不会改变,也会被记录。

不同的存储引擎支持不同的事务处理
不支持事务的话,性能会高,但是可靠性就差。

3种读问题#

  • 脏读:数据被更新了,但是还没提交, 然后另一个事务读到了更新后的数据,结果事务回滚了,导致读的数据其实是脏数据,
  • 不可重复读: 1个事务要读2次数据(注意是单条数据),结果第一次读和第二次读数据不一致了。
    换个说法:事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。(主要是update触发)
  • 幻读: 1个事务按照某搜索条件读了2次 数据,发现2次的记录数不一致,可能多了或者少了记录(注意是多条记录的情况, 不可重复读只针对单条数据的内容变化)(主要是insert、delete触发)
    换个说法,就是你发现数据莫名奇妙多了一条:
    第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行

4种隔离级别#

简易记法: RU\RC\RR\SE
第一个R是read, U是uncommit, C是uncommit, R是repeated, SE是顺序

  • Read Uncommited 最低隔离级别。 写的时候可以被读取。 3个读问题都无法避免。
    如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据
    注意,RU级别,是有写锁的,并不是什么都没做
  • Read Committed 大部分数据库的默认隔离级别。 只有在事务提交后,另一个事务才能看到同一笔数据更新后的结果。

允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行,会对该写锁一直保持直到到事务提交。

区别就是写的时候,未提交的事务会禁止,相当于写的过程会让读不可见。

能解决脏读, RC隔离级别保证了对读取到的记录加锁(记录锁)

  • Repeateable Read 保证同一笔数据在事务中,必须是相同的,不会让他变化。
    能避免不可重复度和脏读, “可能”避免幻读
    RR隔离级别保证对读取到的记录加锁(记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入(间隙锁)。

  • Serializable 所有事务都必须依次顺序执行。 都能解决
    所有的读操作都是当前读,读加读锁(S锁),写加写锁(X锁)。在该隔离级别下,读写冲突,因此并发性能急剧下降,在MySQL/InnoDB中不建议使用。

2486676b1dd91f04b79a37922663c2aada317781

数据库3种完整性约束#

  • 域完整性(值约束):域完整性是保证数据库字段取值的合理性
  • 实体完整性(主键约束):指关系的主关键字不能重复也不能取“空值"。
  • 参照完整性(外键约束): 主关键字与外部关键字引用的约束条件。

4种范式#

  1. 第一范式比较简单,属性不可拆分。电话号码一个字段可以分为手机号码和座机号码两个字段。
  2. 第二范式不难理解,非主属性对候选键完全依赖,不能存在部分依赖。 即主键唯一时,能确定这个非主属性值。
    候选键只有一个主属性时则一定符合第二范式。
    候选键包含多个主属性时,可能出现不符合第二范式的情况,
  3. 第三范式去除冗余,非主属性只能存在一个表中,不应该存在多个表中,要去除无意义的数据冗余。
  4. BC范式则不应存在关键字决定关键字的情况。也就是在关联关系表中,一个表有多个属性构成复合的候选键,主属性直接不应该有互相依赖。工号和身份证号是相互依赖。
  5. 第四范式,对于候选键只能存在不超过1个多值属性。要求把同一表内的多对多关系删除。

简易版:

  • 第一范式:不可拆
  • 第二范式:多个主键时,不能只用到1个主键。
  • 第三范式:非主键属性不能冗余,最好集中到一个表
  • 第四范式:不常问, 好象是1个主键只能对应一个属性值,不要多对多,到时候就说我也不太理解,很少用到吧。

存储引擎比较#

存储引擎的比较InnoDB#

这是mysql的默认事务型引擎

用于处理短期事务(不常回滚的),但本质上还是事务型的存储引擎
数据存储在tablespace中(一堆又innoDB管理的文件,我们看不懂的那种文件)
使用MVCC支持高并发,实现4个隔离级别, 有间隙锁
使用聚簇索引

MyISAM#

MYSQL5.1之前,是默认的存储引擎
不支持事务和行级锁!也就是说不支持修复!
表存在2个文件中: 数据文件和索引文件
只能针对表加锁,不能针对行
有修复操作,但是用于修复表的错误(而不是回滚这种操作)
支持全文索引
如果表导入数据后不再更新,可以用MyISAM压缩表, 而且解压开销不大。

其他引擎#

Archive: 只支持insert和select、压缩写、支持行级锁、不是事务引擎
blackhole: 会丢弃所有插入的数据,但是会记录日志。(一般用于数据备份、日志审计)
csv:把csv文件当作mysql表。不支持索引
Federated: 是访问mysql的一个代理引擎。本质还是连接mysql。默认禁用
memory引擎: 希望快速访问不存盘, 重启后丢失也无所谓,可以i用这个,类似于H2内存数据库。
用作缓存非常不错。
NDB集群引擎: 分布式mysql集群

Q: 不同场景下的存储引擎选择#

A:

  • 日志记录: 要求插入快,可使用myisam
  • 读多写少: InnoDB, 避免崩溃恢复问题。
  • 订单处理: 事务时必须的,innoDB
  • 电子公告牌:多数应用只有几个表保存数据,核心表的压力很大
  • 大数据量: 数据仓库, infobright、NDB或者hive哈哈

Q: mysql5.7和8.0的区别#

A:

  • 新增INVISIBLE和VISIBLE语法, 以支持隐藏索引的功能,用于调试
  • MySQL 8 开始,数据库的缺省编码将改为 utf8mb4
  • 支持用命令 SET PERSIST进行配置的修改,而不是登录后台修改。
  • 新增窗口函数,支持先生成一个排序的窗口表,再做rank
    4678c23a55172ed1d7db48f6a8f6765f97e66e74
  • 更安全,支持SQL角色设置和权限设置

更详细的:MySQL5.7.X用的好好的,为什么要用MySQL8.0

[toc]

1 传输层和网络层的区别:#

1.1 负责对象的不同#

  • 传输层负责建立进程间的通信(即只关心进程A和进程B的消息传递,不考虑底下的东西)
  • 网络层负责建立主机间的通信(即只关心主机A怎么发给主机B)

同理链路层就是物理上相邻的2个机器如何通信,物理层是电路上的通信

1.2 校验上的不同#

  • 传输层会对数据和首部一起做校验
  • 网络层只对首部做校验,数据报部分不会关心。

2 端口号#

端口号是传输层重要的概念。

2.1 端口号的意义#

① 为进程适应不同操作系统提供一个复用和分用的软件

复用:发送方不同进程都用同一种协议传输,目的端口号承担了这个工作
分用:接收方把IP报文组装交付时,通过端口号确认交付给谁

② 端口是进程的重要交互地址。
即由端口确定交给主机上的哪个进程。
③ 只具有本地意义,只对本机有效

2.2 端口号范围#

端口号共16位,因此最大为65535

  • 服务器端口号(做listen用的端口号)有2类:

    1. 熟知端口号:0~1023

    熟知端口号有以下:
    HTTP:80
    HTTPS:443
    DNS:53
    FTP:21
    TFTP:69
    SNMP:161
    TELNET:23
    SMTP:25

    1. 注册端口号: 1024~49151,也叫系统端口号。有许多服务绑定于这些端口。
    2. 动态和/或私有端口:49152~65535,理论上,不应为监听服务分配这些端口,一般是用于客户端的短暂端口号。
  • UDP和TCP有各自的专属端口号,因此不会重复和干扰。

3 TCP#

TCP全称
Transmission Control Protocol, 即传输控制协议。

基于套接字传输
套接字:IP+端口号

tcp的可靠性如下:

  1. 数据被分隔成很多份合适的块来发送
  2. 有超时重传机制
  3. 会校验首部和数据内容。
  4. 会对收到的数据报文做排序,按顺序组装上交
    5.会丢弃重复的数据
    6.提供流量控制,发送主机不会一下子发送太快
    7.每次传输都有序号和ack应答,全双工。

3.1 TCP首部#

位数 含义 详情
16 源端口 因此端口号最多16位
16 目的端口
32 报文序号 该报文的序号
32 确认号 期望收到的下一个报文序号。如果确认号位N,说明N-1都已经收到
4 数据偏移 数据部分的起始位置,可以理解为首部长度。单位是4字节。
6 保留位 没用,全部置成0
1 URG紧急指针标志 是否存在紧急数据
1 ACK标志 是否是确认报文
1 PSH推送标志 是否需要尽快上交进程
1 RST复位标志 是否需要重建连接
1 SYN同步建立连接标志 是不是连接建立期间的报文
1 FIN终止标志 是否是终止连接期间的报文
16 窗口 收到ack前对方可发送回来的数据量
16 校验和 校验首部+数据+伪首部(伪首部指携带了ip的首部)
16 紧急指针位置 紧急数据放在末尾,需要给出紧急数据的长度,便可推断位置
0-320 可变选项 一些TCP选项,例如 最大报文长度、时间戳等。最长40字节
? 填充位 保证首部长度为4字节整数倍

从上面可以看到:

  1. TCP报文中不包含ip信息,只包含端口信息。
  2. 校验和中使用到了伪首部来做校验,即实际上是有ip信息被处理后包含在校验和中了。
  3. TCP首部最小长度为20字节,最大长度为60字节,取决于可变选项。
  4. 首部长度必定是4字节整数倍,不足会填充,因为数据偏移里的单位设置是4字节。

Q: 讲一下TCP报文都有啥,可以不按顺序讲#

A:

  1. 首先是首部长度(数据偏移),确定读到哪
  2. 重要的源端口 目的端口
  3. TCP 可靠性机制关键的一些数字:
    报文号、ACK号、窗口、 校验和
  4. 建连接、关连接用的标记
    一堆标记(SYNC、FIN、RST之类的)
  5. 其他无关紧要的紧急指针位置、可变选项,填充位

3.2 滑动窗口#

在TCP的发送端和接收端,有一个窗口的概念,直接用图片的方式简单明了回忆一下:

3.2.1发送端窗口#

在这里插入图片描述


3.2.2 接收端窗口#

在这里插入图片描述

这里可以看到接收端一次性接收的缓存是有限的,所以进程出现问题迟迟没有接收数据,那么会在ack里告知还能发多少份。
这个叫通告窗口,即告知接收端还能收几份数据(TCP报文里的窗口位就是这个)

3.2.3 坚持定时器#

如果接收端的接收缓存用完,导致返回的ack报文里提示窗口为0,则发送端无法发送数据,此时会启动坚持定时器:
每隔5s发送1个字节的小报文,来查看对方窗口响应。当窗口不再为0,则结束坚持定时器

3.2.4 糊涂窗口#

上面提到的坚持定时器机制里,导致了每次只发一小点数据。
避免措施:
1. 接收方设定一个最小窗口阈值,不通告小窗口
2. 发送方设定一个最小窗口阈值,每次发送满一定长度的报文
3. 发送手头所有数据切不接收ack

3.3 TCP连接流程#

TCP通过三次握手建立连接,四次挥手结束连接

3.3.1 握手和挥手流程#

把这个图牢记于心就不会有问题:
在这里插入图片描述

CLOSED状态:建立连接前的初始状态即关闭状态在建立连接前,先从CLOSED状态变成LISTEN状态(监听状态,表示可以传信号了)

  1. 当客户首先发送SYN后,客户变成SYN_SENT状态。
  2. 当服务器接收到SYN后,服务器变成了SYN_REVD状态。
  3. 当服务器传给客户一个SYN和ACK后,变成了ESTABLISHED状态(表述开始进行数据传输)
  4. 当服务器接受到客户传来的ACK后,也变成ESTABLISHED状态。
  5. 当客户发送FIN(主动关闭)后,客户变成了FIN_WAIT1状态。服务器收到FIN后,执行被动关6. 闭,服务器变成了CLOSEWAIT状态。
  6. 服务器先发送ACK,客户收到ACK后变成FIN_WAIT2状态。
  7. 过了一段时间,服务器才发送FIN,这时候服务器变成LAST_ACK状态客户收到FIN后,变成了TIME_WAIT状态,同时发送ACK,进行2MSL等待。之后2者一起变为CLOSED状态

为什么建立要3次握手?#

建立握手3次原因:一方接收到syn报文后,需向对方回应一个ack。三次握手中,第一个是sync报文,第二个是ack、sync报文合在一起,第三个ack报文。这样就都回应了ack,需要3次。
关键在于最后一次需要一个对 接收端sync的ack响应。


为什么结束连接要4次挥手?#

TCP中发送端发送fin后,就会将自己关闭。
但是接收端一方接收到fin报文后,数据可能还没发送完成。
所以需要先发完ack,再发fin,所以这里会多一次挥手。 最后ack是对fin的确认


能否挥手3次#

。收到第一个fin报文后,它可能刚好没有数据要传输了,fin和ack报文一起回应,对方再回应ack,总共三次,挥手完毕。实际中抓报文,有很多这样的情况。


四次挥手中的TIME_WAIT状态作用?#

A:

1.若最后一步客户发出的ACK丢失了,那么服务器将重发FIN,所以必须维持TIME_WAIT状态到可能的第二次重发FIN的时间。

2.为了避免在终止连接后再次重新建立新连接时,收到之前那次连接“迷路”的分组,要维持一个TIME_WAIT状态以便吸收掉迷路的分组。


两边同时相互建立连接会发生什么?#

2边同时发送SYNC
2边同时收到后, 就会发现自己在还没收到SYNC-ACK的情况下收到了新的SYNC,说明发生了"同时打开"的情况。
此时他会直接发送ack,并且不再等待sync-ack响应了,直接进入ESTABLISHED状态。所以此时仅需2次握手(当然整体上看是4次,一边各2次)

两边同时关闭连接会发生什么#

当2者发生同时关闭,即同时发出FIN时,会进入CLOSING状态,收到相互的ACK后进入TIME_WAIT状态. 即此时需2次挥手(整体上看是4次)

3.3.4 交互数据#

在这里插入图片描述

每一个交互按键都会产生一个数据分组,每次从客户传到服务器的是一个字节的按键。而Rlogin需要远程系统回显客户键入的字符,这样就会产生4个报文段:
(1)来自客户的交互按键
(2)来自服务器的按键确认
(3)来自服务器的按键回显
(4)来自客户的按键回显确认

  • nagle算法#

    当数据交互很快时, 可能会有很多小分组。
    开启nagle后,会把小分组做合并一起发送。


Q: 什么是TCP粘包?#

A:
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包
从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾
出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。


Q: 造成粘包的原因是什么?#

A:
发送方原因: 开启了nagle算法
接收方原因:如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包


Q:什么时候需要处理粘包现象?#

A:
如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
如果多个分组毫不相干,甚至是并列关系,导致应用层取出时无法识别各份数据,那么这个时候就一定要处理粘包现象了


Q: 如何处理粘包现象?#

A:

  1. 发送方
    对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。
  2. 接收方
    接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。
  3. 应用层
    解决办法:循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?
  • 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行,但是选择开始符和结束符时一定要确保每条数据的内部不包含开始符和结束
  • 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置

Q:UDP会不会产生粘包问题呢?#

A:
UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。

3.3.5 异常情况#

  • 异常情况时,会把报文里的复位表示RST置为1。有3种情况会发送复位报文
    ①端口不存在
    ②进程异常终止
    ③客户端异常退出, 服务器没有收到任何fin,此时称为TCP半打开状态。如果TCP配置了心跳,则可以检测

  • 半关闭
    TCP的半关连接是指:TCP连接只有一方发送了FIN,另一方没有发出FIN包,仍然可以在一个方向上正常发送数据。

  • 半连接
    三次握手中,主动发起握手的一方不发最后一次ACK,使得服务器端阻塞在SYN_RECV状态半连接攻击(SYN攻击):会耗尽服务器资源,使得真正的请求无法建立连接。

3.4 拥塞避免机制#

这块概念很多很乱,我按问题整理了一下,一步步来
TCP的拥塞避免等机制对于初学者来说还是比较复杂的,工作中如果开发时偏应用层,那么大部分时候就会摸不到这个机制,感受也就没那么深了。
但如果你的开发过程涉及数据传输,一直在重传、超时之类的方案里有困惑的话,不妨重新学一学可靠性最精致的TCP协议。

所以这里我抛去死记硬背的那堆概念,用10个连续的问题来学习这个机制,注意看的时候先自己思考一下如果是自己,会怎么设计,再去看实际的TCP设计,来理解它的精妙之处。


Q: 建立连接后,每次发送的报文数量是固定的吗?即每次都发1条或者10条?#

A:
不是。
建立连接后,会先只发1条, 然后发2条,接着再发4条,逐步增加。
这个过程叫 “慢启动”
这个1、2、4递增的数量被称之为 拥塞窗口 cwnd

可以理解为TCP希望刚开始,可以大胆点,不断加数量。但为了保险期间还是从1条还是倍增。


Q: 慢启动过程中,那么发送数量(拥塞窗口)什么时候不再倍增?是无限倍增吗?#

A:
不会无限倍增。
当到达慢启动门限ssthreshold时,会变成每次都增加1条。
这个过程叫拥塞避免过程, 也有叫他拥塞避免算法的

可以理解为tcp感觉到有风险了,于是开始慢慢地、小心翼翼地1条1条地添加发送条数。


Q:那么,当进入拥塞避免,每次+1时,什么时候才会不再继续加?#

A:
随着每次发送的数量越发越多, 最终会超出带宽限制,于是就会有某条报文发生超时。
有可能是发的中途丢了, 亦或者是返回的数据全阻塞住了,一条都回不来。

当发送端检测到发生超时时,就会让 慢启动门限ssthreshold = 当前拥塞窗口cwnd/2
接着cwnd 重新置为1,从新开始 慢启动算法。

这样的好处在于可以检测到每次发送的上限,动态调整发送窗口。
上面的过程叫做 超时重传
注意发生超时重传时, cwnd会重置成1。


Q: 上面提到了超时, 那么TCP客户端是怎么判断报文发送超时的呢?#

A:
每次发送数据包的时候, 都会有一个相应的计时器,一旦超过 RTO(超时时间) 而没有收到 ACK, TCP就会重发该数据包。
没收到 ACK 的数据包都会存在重传缓冲区里,等到 ACK 后,就从缓冲区里删除。


Q:上面提到的超时时间RTO是怎么来的?万一设得太大可能导致很迟才能反应过来, 设得太小则可能导致每条都超时#

A:
通过“每次报文的往返时间样本”和“之前样本的偏差值”动态计算出来的。

  • RTT : 报文往返时间(指从发送到收到ack的时间)。每个报文发出后都有个定时器,收到后都会计算出一个RTT样本

  • RTTs: 加权平均往返时间,类似于一个估算的往返时间,实时在变。
    RTTs = (1-a) * RTTs + a * RTT最新样本
    即每次得到RTT样本后, ?都会使用a这个占比去更新RTTs。

  • RTTd: ?RTT偏差加权平均值(就是用来计算超时时间应该比RTT多多少)
    RTTd = (1 - b) * RTTd + b*RTTs - RTT最新样本
    即每次会用新的RTTs以b的占比去更新一下RTTd,并减去RTT样本

  • RTO : 超时重传时间
    等于平均往返时间 加上 4倍偏差值
    RTO = RTTs + 4*RTTd?

Q: 如果发生重传,却还是没有收到ack,那么最新的RTT样本应该怎么算?即你都收不到最新的ack了, RTT难道取超时时间吗?#

A:
会使用karn算法: 发生重传时,不更新这次的RTT样。选用后面收到的ack
修正karn: 为了避免发生重传后,实际RTT都变慢了,导致一下子所有请求都超时, 会在发生重传时,把RTO假大1倍。


Q: 上面提到的ACK超时判断会不会太久了? 假如只是发的时候丢了中间部分报文而已, 但大部分报文ACK还能正常返回,也要一直等超时吗?#

A:
如果能正常接收其他报文的ACK, 只是中间的部分报文丢了, 则有另一个办法。

接收端有一个冗余确认机制:

  1. 发送端A 发送 1、2、3、4、5四条
  2. 但是B接收端只收到1、2、4、5,而3因为网络拥塞丢了。
  3. 于是B会发送ack=3而不是ack=5 给A。 这就是冗余确认机制,只发送缺失那部分的ack,后面的4和5都不管。
  4. A收到ack=3后, 继续发送3、4、5、6、7, 结果3还是丢了。
  5. 于是B又发送ack=3。

当A发现连续3次收到了ack=3时,就会觉察到不对劲,我都发3次了你还是说没收到,可你又能正常返回其他ACK给我,是不是我发的太多了?

上面这个判断3次的重传算法叫“快重传”。

于是A会马上进入 “快速恢复”。
和之前类似,慢启动门限ssthreshold = 当前拥塞窗口cwnd/2
但是!! 新的拥塞窗口cwnd会设置成ssthreshold/2, 而不是1。
而且不会走慢启动倍增的那种,而是走拥塞避免, 逐步+1的那种。
image.png


Q: 前面“超时重传”的时候,是变成从1开始慢启动, 为什么这个“快重传”却是从ssthreshold/2开始,并且走拥塞避免? 为什么会有这个区别?#

A:
因为前面发生超时重传时, 是比较严重的情况, 超时时间内一个ACK都没收到。就好像来回数据都凭空消失了。

而快速重传发生时, 还是能收到部分ack的, 只是丢失了部分数据, 说明拥塞没那么严重,于是可以大胆一点将cwnd削减到1/4, 而不是直接从1开始。

到了这里,基本就能理清楚超时重传和快重传的区别了,重点是理解这2个区别是怎么来的。后面再补几个问题,避免你和其他概念搞混,但不会说得太深,具体需要你自己去扩展学习了。

Q: 为啥发送地多了,数据就会部分丢失?这个是怎么个原理?#

A:
路由器有缓存,IP分组接收过多时就会耗尽空间,丢弃数据。详细可以看路由器的数据转发原理。


Q: TCP除了上面的重传定时器, 好象还有个坚持定时器?区别是啥?#

A:
坚持定时器和超时、网络拥塞没有关系, 和通告窗口即对端的接收能力有关。
简单来说, 就是对方的传输层缓冲区(接收端窗口)满了,告诉你别发了,我吃不下了,于是返回通告窗口为0。
但你想知道啥时候可以发,于是就启动一个坚持定时器,每隔5s发送1个字节的小报文,小小地试探下。当通告窗口不为0了,就重新开始发。

4 UDP#

网络层的多播和广播机制,需要依赖传输层的UDP。

4.1 TCP和UDP的区别:#

  • TCP有连接, UDP无连接
  • TCP可靠, UDP不可靠,发出去不管了。也没有拥塞控制等机制。 不过UDP会做数据正确性校验。
  • UDP会一次性交付一个完整报文,不会做拆分,TCP可能会有小的分组。
  • UDP首部比较简单, 只有源端口、目的端口、报文长度、校验和、填充位。

4.2 UDP的一些特点#

  • 每次调用程序里多播的接口时,都会产生1个UDP消息,没有那种可以复用的UDP连接。

  • UDP数据报的最大长度,和应用程序可读写的数据报最大长度有关,和TCP/IP内核有关。
    当数据报长度大于程序可读写长度,会引发 数据截断。所以udp数据的长度必须要控制好,毕竟他无法根据MTU做分片。

  • 怎么确认MTU多大?
    可以用taceroute命令检测MTU, 本质上是把TCP报文设置成不分片,然后逐步增大,直到发生了ICMP不可达的报错。

  • 如果一次性发送了6个UDP数据报, 并且在链路层有6次ARP请求, 接收端收到6个UDP后,只会发送一个ARP响应。

  • UDP一般用于本地小范围通信, 所以差错其实相比TCP还小一点。

4.3 UDP的应用#

TFTP(小文件传输)
DNS(域名解析)
SNMP(简单网络管理协议)
IGMP
BOOTP(无盘系统引导)
RTP(实时传输协议)
多媒体应用


Q: UDP可以实现可靠传输吗?#

A:
可以实现,但必须在应用层做改造。

  1. 添加seq/ack机制,确保数据发送到对端
  2. 添加发送和接收缓冲区,主要是用户超时重传。
  3. 添加超时重传机制。
  • 详细说明:送端发送数据时,生成一个随机seq=x,然后每一片按照数据大小分配seq。数据到达接收端后接收端放入缓存,并发送一个ack=x的包,表示对方已经收到了数据。发送端收到了ack包后,删除缓冲区对应的数据。时间到后,定时任务检查是否需要重传数据。

  • 目前有多种开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT

[toc]

1.IP地址#

1.1 分类表示法:#

分类表示法已经不常用了。

  • A类地址:
    格式为
    1[7位网络号][24位主机号]
    网络号全0指本网络
    网络号全1用于环回地址(127.0.0.1)
    主机号全0时指本住机所在网络
    全1时指本网络所有主机(广播地址)
    因此A类地址实际可选范围为1.x.x.x ~ 126.x.x.x

  • B类地址
    格式为
    10[14位网络号][16位主机号]
    网络号不可全0,但可以全1
    范围为128.x.x.x~191.x.x.x

  • C类地址
    格式为
    110[21位网络号][8位主机号]
    网络号不可全0
    范围为192.x.x.x~223.x.x.x

  • D类地址(多播地址)
    格式为
    1110[28位多播地址]
    范围为224.x.x.x~239.x.x.x
    因此看到224以上的ip要注意

  • E类地址
    格式为11110[保留]
    用于实验用,因此看到240以上的认定不是正常节点ip

1.1.2 分类表示地址的其他说明#

  • 网络号全0,但主机号非全0的某个ip就是指本网络的某个主机

  • 网络号不为全1,但主机号为1的ip,则指某个网络的广播地址

  • 全0,指本网络的本主机

  • 全1,指本网络的广播地址

  • 环回地址,指127.0.0.1,在同一台主机上进行网络传输

  • 私有地址,指不会参与路由器转发的地址,, 只会参与本局域网,发给本局域网的交换机:
    A类: 10.0.0.0-10.25.255.255
    B类: 172.16.0.0-172.31.0.0
    C类: 192.168.0.0-192.168.255.255

1.2 无分类编址CIDR#

Classless Inter-Domain Routing 无类型域间选路

  • CIDR将路由集中起来,使一个IP地址代表主要骨干提供商服务的几千个IP地址,从而减轻Internet路由器的负担。
  • 该编址用于子网划分,子网号和上面提到的网络号是不同的。
  • IP地址 ::= {<网络号>, <子网号>, <主机号>}

CIDR有三种编址方式:

  • 128.14.35.7/20 , 完整ip加子网位数
  • 10.0.0.0/10 -> 10/10, 可省略末尾的0
  • 00010100*, 即用星号代替子网后的主机号

对于CIDR编址
子网号的全0和全1没有特殊含义,但不可设置成全0或者全1。
主机号的全0指本网络, 全1指广播。(网络号仍然遵从ABCD地址的规则)

  • 子网掩码:
    值1的位置指该ip中该位置是网络号和子网号区域
    值0的位置指该ip中该位置是主机号区域。
    例子:111111100000000…, 那么前面8个1就是网络号+子网号,后面都代表了主机号
  • 路由寻址时,一般先比较网络号,再比较子网号,再比较主机。
    子网掩码可以简化先比网络再比子网的过程。

2 IP数据报文格式#

IP报文的首部至少有20个字节(160位),首部如下:

位数 含义 详情
4 版本 IPV4或者IPV6
4 首部长度 单位是字(4字节
8 区分服务 设置服务的时延、吞吐量、可靠性,一般不用
16 IP报文总长度 单位字节
16 报文标识 用于分片后的同报文组装。相同报文的不同分片,该值相同
3 分片标志 判断是否可分片或者是否是分配最后一个
13 片偏移 用于按顺序组装同报文的分片
8 生存时间TTL 该报文最大跳数,每经过一次转发就减一
8 协议类型 ICMP/IGMP/TCP/UDP
16 首部校验和 用于和首部做校验,看首部是否正确
32 源IP地址
32 目的IP地址
任选项 很少被使用,最多40字节

上面可以看到IP报文的以下限制:

  1. 首部长度字段可以看出首部长度最多可以位60字节,所以任选项最多40字节
  2. 报文总长度最大为65535, 但是由于MTU的限制(链路层防冲突机制导致的),一般都要做分片, 分片后就会用上分片标识和片偏移了。

Q: IP报文里有什么?可以不按顺序或者字节来讲一讲#

A:

  • 首先要知道报文多长, 首部长度+报文长度
  • 为了校验首部,还需要校验首部和
  • 很重要的源ip 目的ip
    那么如何确定ip类型?这就需要 ipv版本,来确认是ip4还是ip6。
  • ip支持分片,那么就需要
    分片id、是否是最后分片标记、分片偏移
  • 协议类型(icmp、igmp)
  • TTL生存
  • 其他任选项(40字节)

3.路由概念#

  • 路由器可分隔广播域,指的是不同网络号的地址,路由器不会转发广播报文

Hub集线器在同一个冲突域通信无法分割;交换机在同一个广播域通信,可分割冲突域;路由器实现不同广播域间通信,可分隔广播域。

  • IP报文在传输中不会被改变,但是链路层报文的mac地址会不断变化。
  • 当2个主机在不同的子网时,必须要借助路由才能通信

3.1 路由表#

假设某个路由器在N1网络,他的路由表如下:

目的网络 下一跳地址 解释 到目的网络距离
N1 0.0.0.0 假设目的网络就是路由器所在网络,说明可以直接交付给本网络的主机了,不用再转发,所以地址为全0 0
N2 R2 如果为N2,则会发给R2路由 4
0.0.0.0 R1默认路由 如果路由表找不到目的网络,则会默认转给R1处理,0.0.0.0是默认转发网络的标识
特定IP地址 R3 这种特定地址的选择是管理员配置的 3
  • 特定IP地址的子网掩码为全1,所以一般都是x.x.x.x/32
  • DNS服务器一般会配置在路由表中的特定IP地址
  • 未知网络在路由表里的目的网络被写为0.0.0.0, 如果么有,则就是未设置默认路由
  • 路由器不会转发私有地址。
  • 距离指的是跨越路由器的数量,而不是实际长度单位

3.2 路由网络匹配#

如果路由表中的目的网络由很多,怎么确定IP和路由表中的目的网络是匹配的?
使用 最长前缀匹配, 即前缀匹配得最多的就是目的网络。
优化算法可用二叉线索树来确认最长前缀。


Q: 为什么要用二叉线索树来 判断最长匹配前缀? 字典树不可以吗?
A:

3.3 ARP解析#

全称Address Resolution Protocol,地址解析协议。

从主机发给路由, 或者路由发给路由时,底层还是得封装一层mac地址然后往下交给交换机。
那么ip和mac地址的对应关系, 是怎么得知的?
答案就是ARP协议

本质就是当mac缓存表里没有ip和mac的对应关系时, 主机或者路由会广播ARP报文, 对应ip方向的交换机会把报文发送回来,这时候就直到mac地址和ip的对饮关系了。

  • arp -a可以检查ARP告诉缓存
  • ARP缓存有超时时间
  • 目的主机不存在时,会反复发送,有个超期期限的存在。
  • 主机发送ARP查找自己的Mac地址称为“免费ARP"
  • 发送给某1主机的arp请求被中间路由器接收了,则称为“ARP代理”, 发送者不管不管你是中间路由还是目的ip主机,只知道这个ip需要发给这个mac。

3.4 RARP逆地址解析协议#

由mac地址反取ip。
因为ip不存在,无法直接转给给路由。所以会比ARP难。
过程:
1)将源设备和目标设备的MAC地址字段都设为发送者的MAC地址和IP地址,发送主机发送一个本地的RARP广播,能够到达网络上的所有设备,在此广播包中,声明自己的MAC地址并且请求任何收到此请求的RARP服务器分配一个IP地址;
? 2)本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址;
? 3)如果存在,RARP服务器就给源主机发送一个响应数据包并将此IP地址提供给对方主机使用;如果不存在,RARP服务器对此不做任何的响应;
? 4) 源主机收到从RARP服务器的响应信息,就利用得到的IP地址进行通讯;如果一直没有收到RARP服务器的响应信息,表示初始化失败。

4 ICMP协议#

全称Internet control message protocl,网络控制报文协议
他会包装在IP的数据报文中,并把首部的协议类型改成ICMP那个数字。

首部总共8个字节,分别为
2字节的ICMP类型
2字节的ICMP报文代码(类似错误码)
4字节的校验和
后面就是数据部分了。

常见的2种用途:

  1. 发送网络层之间的差错报告,例如:
    • 源点抑制——发送网络拥塞
    • 终点不可达——无法找到对应ip交付地点
    • 时间超时——报文种的TTL降为0,或者分片一直没收集完
    • 参数错误——首部中字段有错
    • 路由改变(重定向)—— 主机把数据发给了路由器R2,但是路由器R2发现主机自己本来就可以直达了,于是发给主机该消息,告诉他你要更新路由表了。

差错报告有以下其他特点:
* ICMP自身出错时,不会再发ICMP差错报文
* 如果是报文分配后发生错误,则只会发1次,而不会每个分片发一次
* 不针对多播,不针对127.0.0.1、0.0.0.0等特殊的地址发送差错报文,不可广播(避免广播风暴)

  1. 发送一些询问报文,例如:
    • 回送请求和应答——例如ping命令就是借助ICMP
    • 超时报文——traceroute就是用这个,把TTL从1慢慢增加,发好多份,通过TTL为0时的差错报告,定位跟踪路上有哪些路由
    • 时间戳请求——同步时间

5 DHCP协议#

全称Dynamic Host Configuration Protocol, 动态主机配置协议

当某个局域网内新增了一台主机,这个主机的ip是怎么生成的呢?这就会用到DHCP协议

主机所在网内会有一台DCHP服务器。
当新主机加入时,发生如下之事:

  1. 主机先“广播”自己,告诉大家“我来了,谁给我一个IP地址”(他一开始不知道DHCP在哪)
  2. DCHP服务器收到后,会分配一个IP地址,但因为不知道发给谁,所以也只能“广播”,告诉大家“我这有个ip,刚才谁要的,自己来领一下”
  3. 主机收到DHCP广播的报文后,就能知道自己的ip和dhcp服务器位置了。于是给DCHP服务器发送请求,告诉他“我收到了你发来的ip了”
  4. DCHP收到后,确认了他的信息,并加入到DHCP本地的数据库中,后面分配新ip时就会排除掉这个ip了。

有以下几个注意点:

  1. 如果有多个主机同时应答了DHCP的广播, 则会选择最先到达的做分配。
  2. 分配的ip是临时的

DCHP可以认为是基于UDP的应用层协议,但本质是为了寻求新主机的动态ip地址

6.路由表的最优下一跳地址如何计算?#

可以理解为 在一个复杂的拓扑图下, 怎么选择最优的一个路由做目的地址的下一跳。
有2种方式:

6.1 RIP协议#

全称Routing Information Protocol,路由信息协议
是一种动态路由信息协议。

  • 路由只会和相邻的其他路由交换信息。

  • 交换的是路由表的信息,关键在于目的网络和距离

  • 之前路由表里知道了表里会存储 到目的网络的距离即跨越路由数量,那么只要拿到周边所有路由的距离表, 看下哪个方向最小, 然后把下一跳地址选为最小的那个路由方向即可。

  • 使用UDP广播,把自己的路由报文发给周边的其他路由。

  • 当路径不可达时,会导致2个路由之间不断叠加该目的地址的距离,直到16时,会被设置成不可达。

所以RIP本质也是基于UDP的应用层协议,但是目的是为了网络层的最优路由选取。

6.2 OSPF协议#

open shortest path first,开放最短路径优先协议
指路由器里有全网的拓扑结构,使用最短路算法计算最优路由
因此路由会把自己的连接情况通过OSPF协议发给所有其他路由,以建立拓扑图。
这个是属于IP层的协议,不借助UDP。


RIP和OSPF是自治网络系统AS里的选路措施。
AS里的选路措施被称作IGP(内部网关协议)
1个AS里只会有一种选路措施。

而跨自治系统的协议叫EGP(外部网关协议)
通常使用BGP协议


6.3 BGP协议#

Border Gateway Protocol边界网关协议

  • 每个AS都知道自己为了到达网络N,需要经过哪些AS(相当于知道以AS为节点的拓扑图)
  • 每个AS都有一个BGP发言人,会与其他BGP网络之间交换自身的AS拓扑信息,从而构建全局连通图
  • 使用TCP 179端口工作

Q: RIP协议下路由表什么时候更新?#

A:

  • 正常情况下,路由器会基于更新计时器每30s将路由表发送给邻居路由器,而触发更新是立刻发送路由更新信息
  • 触发更新就是当检测到网络拓扑发生变动时,路由器会立即发送一个更新信息给邻居路由器,并依次产生触发更新通知它们的邻居路由器,此过程就叫触发更新

Q: 路由中毒是什么?#

A:
路由中毒是指在路由信息在路由表中失效时,先将度量值变为无穷大的数,而不是马上从路由表中删掉这条路由信息。 然后再将中毒路由信息发布出去,当相邻的路由器收到该中毒路由就可以通过其度量值是16,说明该路由是无效的。

??因为RIP协议中的度量值其实就是跳数,而RIP协议的跳数最大是15,大于15的目的地被认为是不可达,所以当其度量值为16,就表示这是一个无效路由,这就是所谓的路由中毒,这个数字在限制了网络大小的同时也防止了一个叫做“记数到无穷大”的问题。


Q: 收到中毒路由的路由器会怎么做?#

A:
收到中毒路由信息的相邻的路由器会发送一个毒性逆转的信息,表示已经收到中毒路由信息。

??那么为什么收到中毒路由的路由器为什么要回复一个毒性逆转的信息?这是因为如果不回复的话,那么发送中毒路由的路由器就会一直以广播的形式发送中毒路由,直到相邻的路由器收到并回复一个毒性逆转的信息。

7 多播#

UDP的时候会用到多播

7.1 IGMP协议#

internet group message protol, 网络组管理协议
负责收集和解释一个网络中的组成员信息
IGMP协议应用于路由器

  • 某主机加入新的多播组时,发送报文,并转发多播的关系给其他相邻主机或者路由
  • 会周期性探寻,确认自身这个主机是否还在多播组内
  • 无法直到总成员数
  • IGMP属于网络层的协议

7.2 MOSPF多播路由选择协议#

多播开放最短通路优先(Multicast Open Shortest PathFirst,MOSPF)协议是OSPF协议的扩展
使用多播链路状态路由选择来创建源点基准树。
这个协议需要一个新的链路状态更新分组,把主机的单播地址和组地址或主机负责的地址联系起来,这个分组就称为组成员关系LSA。
此外,这个数可以保存在高速缓存中,以便以后有同样源点/组地址对的分组可以使用它。
多播的其他更详细概念见链接

8 其他网络层概念#

8.1 VPN#

需要建立专用通道
当专用A试图向专用B通信时,会先加密,再通过加密隧道发到对方内网,具体报文内容不会和互联网直接接触。

8.2 NAT#

内外网转换用的一个东西, 公网ip和内网ip互转。

8.3 移动IP#

ip从子网A变道子网B。
在本网时,按TCP通信
要漫游到外网时, 注册一个转交地址
本地代理接收地址,开启隧道
数据发送到外网
在外网时,使用代理ip发送数据
回到本地时,会注册并转交之前的地址

9 常见网络层命令#

  • ifconfig 可显示本机的IP地址
  • netstat -r可显示路由表
  • tcpdump 可显示硬件地址
  • ping 测试另一个主机是否可达
  • traceroute 利用ICMP跟踪途径的所有路由
  • route 命令可查看和修改路由表
  • gated可查看IGP(内部网关协议)和EGP(外部网关协

[toc]


limit 1 offset 1 不存在却要求返回null时, 可以用子查询,或者IFNULL(临时表结果或字段, NULL)
子查询是select结果作为字段,例如 select (select xx) 而不是 select xxx from (select xxx)。 当select中的字段结果不存在,会自动返回NULL


如果排名问题要去重,记得加 distinct


CREATE FUNCTION中
可以set N:=N-1: 进行变量更新。 不能在return中进行更新

1
2
3
4
5
6
7
8
9
10
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
set N := N-1;
RETURN (
# Write your MySQL query statement below.
select (select distinct salary from Employee
order by salary desc
limit 1 offset N)
);
END

要做排名时给出序号时,有两种方式
简单方式:
利用子查询字段,注意子查询字段的特点等同于摘出一个字段,再做一次额外查询,得到唯一的一个结果。

1
2
3
4
5
6
7
8
# Write your MySQL query statement below

select a.score, (
# 拿a.score重新重新去检索一次表,得到count(distinct b.score)+1,得到自己的排名
select count(distinct b.score)+1 from Scores b where a.score<b.score
) as `rank`

from Scores a order by a.score desc

但效率太慢。
高速的序号方法:
使用dense_rank() over(order by xxx) 可以得到序号,1 1 2 3 3 4,不会跳序号
其他序号方式:
row_number() over(order by xxx) 就是简单的12345这种,不考虑重复
rank() over(order by xxx) 如果重复了,1 1 3 4 4 6

1
2
3
select  score, 
(dense_rank() over(order by score desc) ) as `rank`
from Scores

寻找3个连续出现的数字,如果id是连续的,则可以自联结,即自己和自己join, 构造where a.id = b.id-1 and b.id = c.id-1 这样的情况。非常好理解

1
2
3
4
5
6
select distinct a.num as ConsecutiveNums from 
logs a,logs b, logs c
where
a.id = b.id-1
and b.id = c.id-1
and a.num = b.num and b.num = c.num

善用自join联结解决一些表内成员互相比较的复杂问题。

1
2
3
4
5
6
# 查询收入超过他经理的员工名字。
select
a.name as Employee
from
Employee a , Employee b
where b.id = a.managerId and a.salary > b.salary

group分组后, 可以用having进行组内聚合情况的过滤, 剔除不想要的组

1
2
3
4
5
# 查找 Person 表中所有重复的电子邮箱。
select email from person
group by email
having
count(email) > 1;

表a在 表b中不存在记录, 用left join + where xxx is null 来处理

1
2
3
select c.name as Customers 
from Customers c left join Orders o
on c.id = o.CustomerId where o.id is null

in 的用法 where a in(1,2,3,4)
或者 where a in (select aaa from xxx)

in也支持二元组形式
where (a,b) in (select aaa,bbb from xxx)

每组最大值的人选的情况,可以用join + in(分组结果)的方法

1
2
3
4
5
6
7
8
9
10
11
12
# 查找每个部门中薪资最高的员工,先join,再用in获取符合组+最大值的行
select
b.name as Department , a.name as Employee , a.salary as Salary
from Employee a
join
Department b
on a.departmentId = b.id
and (a.departmentId, a.salary) in (
select departmentId, max(salary)
from Employee
group by departmentId
)

找某薪水是组内前几位的情况, 擅用自查询, 即join后, 拿某条记录在where中遍历一遍, 然后根据count(distinct xxx)来确认自己的排名

1
2
3
4
5
6
7
8
9
10
# 找出每个部门获得前三高工资的所有员工
select
b.name as Department , a.name as Employee , a.salary as Salary
from Employee a
join
Department b
on a.departmentId = b.id
# 比自己这行记录salary大, 且在同一组内的数量小于3,则满足前3
and (select count(distinct e.salary) from Employee e where a.DepartmentId = e.DepartmentId and a.salary < e.salary
) < 3

  • 分组后, SUM(IF(x=y,1,0) 可以统计组内x=y的个数
    那么SUM(IF(x=y,1,0) ) / COUNT(x) 就能得到组内x=y的比率
  • ROUND(xxx, 2) 可以保留两位小数。
  • between的用法: between ‘2013-10-01’ AND ‘2013-10-03’
1
2
3
4
5
6
7
8
9
10
11
12
# 写一段 SQL 语句查出?"2013-10-01"?至?"2013-10-03"?期间非禁止用户(乘客和司机都必须未被禁止)的取消率。非禁止用户即 banned 为 No 的用户,禁止用户即 banned 为 Yes 的用户。

select request_at as DAY, ROUND(
SUM(IF(status = 'completed', 0, 1))/COUNT(status), 2
) as `Cancellation Rate`
from
Trips t, Users u1, Users u2
where (t.client_id = u1.users_id
and t.driver_id = u2.users_id)
and u1.banned = 'No' and u2.banned = 'No'
and request_at between '2013-10-01' AND '2013-10-03'
group by request_at

求3个或3个以上连续id的记录,直接自联结,然后用3个or条件判断
t1.id t2.id t3.id
t2.id t1.id t3.id
t2.id t3.id t1.id
然后取每种连接表的t1即可

注意distinct 可以用于星号
distinct s1.*
也可用于多列 distict s1.a,s1.b,s1.completed

1
2
3
4
5
6
7
8
9
10
11
12
13
# 找出每行的人数大于或等于 100 且 id 连续的三行或更多行记录。
# 返回按 visit_date 升序排列的结果表
select
distinct s1.id,s1.visit_date , s1.people
from
Stadium s1, Stadium s2,Stadium s3
where s1.people >= 100 and s2.people >=100 and s3.people >= 100
and (
(s1.id = s2.id-1 and s2.id = s3.id-1 ) or
(s2.id = s1.id-1 and s1.id = s3.id-1 ) or
(s2.id = s3.id-1 and s3.id = s1.id-1 )
)
order by s1.id

[toc]

SHOW命令#

显示当前所有的可用数据库#

SHOW DATABASE;


显示当前数据库内可用表#

SHOW TABLES ;


显示某个表中的所有属性信息#

SHOW COLUMNS FORM 表名

SHOW STATUS 显示广泛的服务器状态
SHOW CREATE DATABASE/TABLE 显示创建库或表的SQL语句是啥
SHOW GRANTS 显示授权用户
SHOW ERRORS SHOW WARNINGS 显示服务器错误或警告消息


检索命令#

检索单个列#

SELECT prod_name
FROM products


检索多个列#

SELECT id,name,price
FROM products


检索所有列#

SELECT *
FROM products


检索出不重复的行#

SELECT DISTINCT id
FROM products


返回某些行#

SELECT name
FROM products
LIMIT begin,len //即开始行,和行数
注意: 行从0开始, 故LIMIT1,返回的是第二行


排序命令#

按单列排序#

SELECT name,age
FROM products
ORDER BY age
找出年龄最小的? 加个LIMIT 1即可


按多个列排序#

先按price排,再按name排
SELECT id,price,name
FROM products
ORDER BY price, name


降序排序#

按price降序排,再按name升序排
SELECT id,price,name
FROM products
ORDER BY price DEC, name


条件查询#

找出price=2的人#

SELECT name, price
FROM products
WHERE price = 2
注意: ORDER BY 排序语句应该放在WHERE的后面

SQL做where字符匹配时,不区分大小写。


不匹配检查: 给出id不是1003的人制造的产品#

SELECT id,name
FROM products
WHERE id <> 1003; //或者 id != 1003


范围值检查:#

SELECT name,price
FROM products
WHERE price BETWEEN 5 AND 10


空值检查#

WHERE price IS NULL


多个过滤条件(AND是且, OR是或)#

WHERE id=1003 AND price <10 AND age < 18


AND和OR的组合#

WHERE id=1002 OR id=1003 AND price>10
根据SQL的规则, AND优先于OR,变成了
WHERE id=1002 OR (id=1003 AND price>10)
所以最好在OR和AND组合时,加上括号
WHERE (id=1002 OR id=1003) AND price>10


取特定值,即id=1002、1003、1004的值皆可#

WHERE id IN (1002,1003,1004)


否定条件 NOT 条件, 则取不满足这个条件的行#

WHERE id NOT IN (1002,1003,1004)


通配符#

配合字符串匹配和LIKE使用#

百分号通配符,查找以jet开头的任何名字
WHERE name LIKE ‘jet%’
包含有anvil的名字
WHERE name LIKE ‘%anvil%’
查找以s起头,e结尾的所有名字
WHERE name LIKE ‘s%e’


下划线通配符, 该符号只能代表1个任意字符#

不能匹配0个字符,即该处必须有1个字符
WHERE name LIKE ‘_ton anvil’


使用通配符的注意事项#

1.不要过度使用通配符
2.不要把通配符用在搜索模式的开始处
3.注意通配符的位置


正则表达式,伴随着REGEXP使用#

LIKE匹配的是整个串,REGEXP只要出现了类似的即可,即有子串即可
例如LIKE ‘abc’ ,只能返回abc。
REGEXP ‘abc’, 则可以返回 abcd


.可以匹配任意一个字符
WHERE name REGEXP ‘.000’


搜索2个串, |类似于或OR
WHERE name REGEXP ‘1000|2000’
注意: ‘1|2|3 Ton’ 指代的是1或2或3 Ton, 故不要连续用|后又跟字符,而应该用[]


匹配几个字符中的一个即可
WHERE name REGEXP ‘[123] TON’
则可返回 1 Ton 和 2 Ton


匹配1-9加a-z
WHERE name REGEXP ‘[1-9] [a-z]’


匹配 正则符号模样的特殊字符
用\作为前导
WHERE name REGEXP ‘\.’
则找到匹配.的, 此时.不代表任意字符


匹配某个字符的0个或1个
WHERE name REGEXP ‘sticks?’
则可匹配stick或者sticks


指定匹配数目 {n}
不少于指定数目的匹配 {n,}
匹配n到m数目的匹配 {n,m}


WHERE name REGEXP ‘[[:digit:]]{4}’
则匹配4个任意数字


^定位为文本的开始,$定位为文本的结束
注意: ^有另外一个用途,可以用来否定集合[]


特殊UDF#

在SQL中测试自己写的正则表达式对不对
SELECT ‘hello’ REGEXP ‘abc’
若正确,则返回1,否则返回0。 显然这个例子返回0


计算字段
SELECT Concat(name, ‘(’ , country, ‘)’ )


则返回的行里的每个值为
LSX(CHINA)
CRISTINA(USA)


RTrim(name)可以删除name中多余的右边空格,常可以用在拼接中


给计算字段取别名
SELECT Concat(name, ‘(’ , country, ‘)’ ) AS newtitle
则列名叫newtitle


执行算数计算
SELECT id, num, price, num*price AS sumProfit
则多返回一个列,叫做总利润


测试拼接字段
SELECT 3*2+5, 则返回11
SELECT Now(), 则返回数据库的当前时间


数据处理函数
Select name, Upper(name) AS upcase_name 都转为大写


分组+聚合#

返回价格的平均值#

SELECT AVG(price) AS avg_price
FROM products
WHERE id = 1003


计算的数量,包括NULL#

SELECT COUNT(*) AS num_cust
计算num的数量,不包括NULL
SELECT COUTN(num) AS num_cust
还有MAX,MIN,SUM


指定不同元素的统计#

SELECT AVG(DISTINCT price) AS avg_price


分出5个id,计算各id对应产品的数量#

SELECT id, COUNT(*) AS id_num
FROM products
GROUP BY id
1.除了聚集计算语句外, SELECT中出现的每个列都必须出现在GROUP BY中
2.GROUP BY 必须出在 ORDER BY 之前


过滤分组#

SELECT id, COUNT() AD orders
FROM orders
GROUP BY id
HAVING COUNT(
) >= 2


列出具有2个以上价格为10的产品#

SELECT id, COUNT() AS num
FROM products
WHERE price >= 10
GROUP BY id
HAVING COUNT(
) >=2
顺序:先WHERE过滤, 在COUNT过滤

语句使用顺序
SELECT
FROM
WHERE
GROUP BY
HAVING
ORDER BY
LIMIT

子查询#

找出订购了物品TNT2的所有客户id#

SELECT cus_id
FROM orders
WHERE sail_id IN (SELECT sail_id
FROM orderitem
WHERE prod_id = ‘TNT2’)
即先从产品表中,找到产品id为INT2的所有订单
再从订单表中,找到订单id匹配的所有客户。

检索出上述客户id的所有信息
SELECT cus_name, cus_age
FROM customer
WHERE cus_id IN (SELECT cus_id
FROM orders
WHERE sail_id IN (SELECT sail_id
FROM orderitem
WHERE prod_id = ‘TNT2’))
显示每个客户的订单总数
SELECT name, state, (SELECT COUNT(*)
FROM orders
WHERE orders.cus_id = cus.cus_id ) AS orders
FROM customers
ORDER BY name
对于customer中的每一个客户id, 都从订单表中进行检索,找出每个id的订单数量。

DDL#

创建表:#

CREATE TABLE 表名

列名 数据类型 列属性
……
PRIMARY KEY (主键列1,主键列2……)
)ENGINE=InnoDB

添加列:#

ALTER TABLE 表名
ADD 列名 数据类型

删除列:#

ALTER TABLE 表名
DROP COLUMN 列名

删除表:#

DROP TABLE 表名

重命名表:#

RENAME TABLE 旧表名 TO 新表名

插入行:#

INSERT INTO 表名(
列名1
列名2
……
)
VALUES(
数据1
数据2
……
),

数据3
数据4
);
这样就插入了2行

更新数据:
UPDATE 表名
SET 列名1=数据1
列名2=数据2
WHERE 查询条件

删除数据:#

DELETE FROM 表名
WHERE 查询条件

表中删除主键为:#

alert table table_test drop primary key;

表中增加主键为:#

alert table table_test add primary key(id);

[toc]

第一题:数学规律题(没想通)#

782. 变为棋盘 - 力扣(LeetCode)

看答案也没看懂,主要是没有深入研究规律,没精力了,先放着

1661270748809

1661270976714

第二题:矩阵前缀和应用#

剑指 Offer II 013. 二维子矩阵的和 - 力扣(LeetCode)

1661270797124

做过好几次了, 维护一个dp[y][x], 表示(y,x)到(0,0)的矩形和,这个和可以提前遍历一次得到

然后再通过加减矩阵的方式得到结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class NumMatrix {
int[][] dp;
int ylen;
int xlen;
public NumMatrix(int[][] matrix) {
ylen = matrix.length;
xlen = matrix[0].length;
dp = new int[ylen][xlen];
for (int y = 0;y<ylen;y++) {
int sum = 0;
for (int x = 0; x < xlen; x++) {
sum += matrix[y][x];
dp[y][x] = sum + (y-1>=0?dp[y-1][x]:0);
}
}
}

public int sumRegion(int row1, int col1, int row2, int col2) {
return sum(row2, col2) - sum(row2, col1-1) - sum(row1-1, col2) + sum(row1-1, col1-1);
}

int sum(int y, int x) {
if (y <0 || x < 0) {
return 0;
}
return dp[y][x];
}
}

/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix obj = new NumMatrix(matrix);
* int param_1 = obj.sumRegion(row1,col1,row2,col2);
*/

第三题:树和递归#

606. 根据二叉树创建字符串 - 力扣(LeetCode)

1661270915678

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public String tree2str(TreeNode root) {
if (root == null) {
return "";
}
String left = "(" + tree2str(root.left) + ")";
String right = "(" + tree2str(root.right) + ")";
if (left.length() == 2 && right.length() == 2) {
left = "";
right = "";
} else if(left.length() != 2 && right.length() == 2) {
right = "";
}
return root.val + left + right;
}
}