面试之——网络编程相关
1、 三次握手和四次挥手
SYS( synchronize) 同步标识
ACK (ACKnowledge Character) 中文译名: 确认字符
建立连接:
第一次握手: 客户端发syn包(syn=1, seq=随机数), 进入syn_sent状态;
第二次握手: 服务器收到包,要确认客户的syn(ack=j+1), 同时也发syn包(syn=1,ack=1,seq=seq的基础上再随机数),就是syn+ack包;
第三次握手: 客户端收到服务器的syn+ack包,向服务器发送确认包(ack=k+1),连接成功
关闭连接:
第一次挥手: 客户端数据发送完,附带一个fin包(M:序号)给服务器 (潜台词:我想关闭连接了)
第二次挥手: 服务端收到后,向客户端发送确认序号ack(M+1) (潜台词: 我知道了)
第三次挥手: 服务端发fin(N)包给客户端 (潜台词: 我也想关闭连接了)
第四次挥手: 客户端收到后,发包ack(N+1)给服务端,服务端收到后,close,客户端等,超时后也close
问题1: 为什么要三次握手?两次不行吗?
答: 三次握手的目的是建立可靠的通信信道。直白:双发互相确认对方的发送和接收能力。
第一次握手: A确认: 无; B确认: client发, 自己收
第二次握手: A确认: 自己发&收 ,B发&收; B确认: A发, 自己收
第三次握手: A确认: 确认完毕, B确认: A收 ,自己发, (加上第二次握手,确认完毕)
问题2:为什么连接的时候是三次握手,关闭的时候却是四次握手?
C ---> fin(M) ---> S 我想关闭,你可以吗?
C <--- ack(M+1) <--- S 我知道了
C <--- fin(N) <--- S 我可以关闭了
C ---> ack(N+1) ---> S 你先关,2msl时间后我再关
MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等
----------------------------------------------
摘自网络其他:
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?(不是发完fin包就立刻断开)
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
-----------------------------------------------
问题3:为什么要发送特定的数据包,随便发不行吗?
答:三次握手的另外一个目的就是确认双方都支持TCP,告知对方用TCP传输。
第一次握手:Server 猜测Client可能要建立TCP请求,但不确定,因为也可能是Client乱发了一个数据包给自己
第二次握手:通过ack=J+1,Client知道Server是支持TCP的,且理解了自己要建立TCP连接的意图
第三次握手:通过ack=K+1,Server知道Client是支持TCP的,且确实是要建立TCP连接
2、 TCP和UDP的区别?
1、tcp是基于连接的,必须先启动服务端,然后再启动客户端去连接服务端
2、udp是无连接的,先启动哪一端都可以 (应用: QQ聊天)
3、 OSI七层协议是什么?
待续。。。。
4、 GIL(Global Interpreter Lock)锁
python是假的多线程,因为GIL锁的存在,一个进程里面4个线程只有1个线程才能被cpu调用
5、 进程,线程,协程 区别?
1. 进程是一个资源单位(工厂),线程才是cpu上的执行单位。
2. 一个进程下可以有多个线程,多个线程共享该进程的地址空间(资源)。
3. 创建线程开销小,创建速度快
4. 协程:
在一个单线程里实现并发(IO才有意义)
修改共享数据不需要加锁
6、 什么是协程?
协程是"微线程",不是真实存在,程序员创造出来的, 功能: 操纵线程在代码之间来回切换执行
1. 想能更低:如果遇到非IO请求来回切换,性能更低
2. 性能更高:协程本身不能实现并发,遇到IO(耗时)切换才会实现并发(欺骗cpu)
1. 通过yield实现协程
def func1(): print('aaaaa') yield 1 print('bbbbb') yield 2 def func2(): print('cccccc') yield 11 g1=func1() g2=func2() g1.send(None) g2.send(None) g1.send(None)
2. 通过 greenlet模块实现
from greenlet import greenlet def test1(): print 12 gr2.switch() print 34 gr2.switch() def test2(): print 56 gr1.switch() print 78 gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch()
如果 协程+遇到IO(耗时)就切换 => 可以实现并发
7、 并发方案?
1. 多进程
2. 多线程
3. 单线程: 利用 "异步非阻塞"模块 实现单线程并发
单线程:
IO较多的话(不怎么用cpu),虽然可以开多个线程,但是开销仍然可以再小点,所以,可以开一个线程
一个线程把请求发出去,不等,再把其他请求的都发出去....
8、 锁是什么?干什么的?
python里面的锁:
GIL锁: 保护的是解释器级别的数据 GIL(Global Interpreter Lock)
Lock锁: 保护的是用户自己的数据
GIl本质是互斥锁
既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
作用:保护不同的数据的安全,就应该加不同的锁。
不完善,待续。。。。。。
9、进程池和线程池?
python2没有线程池
1、进程池 import time from concurrent.futures import ProcessPoolExecutor def task(name): time.sleep(2) print('%s' % name) if __name__ == '__main__': p = ProcessPoolExecutor(4) # 立刻造好4个进程 for i in range(1, 11): p.submit(task, 'kitty%s' % i) #submit() 往里面丢任务 print('主进程') 2、线程池 from concurrent.futures import ThreadPoolExecutor def func(): time.sleep(1) if __name__ == '__main__': t = ThreadPoolExecutor() #创建线程池的时候,如果不设置数量,默认是cpu核数*5 for i in range(100): t.submit(func,)
10、 IO多路复用的作用?
用于监听多个socket对象是否发生变化
import select while True: r, w, e = select.select([sk1, sk2], [sk1, sk2], [], 0.5) 让select模块帮助我们去检测sk1/sk2两个socket对象是否已经发生"变化"(变化只有两个:1. socket连接成功;2.socket得到响应) r = [] 如果r中有值 r = [sk1,] # 表示: sk1这个socket已经获取到响应的内容; r = [sk1, sk2] # 表示: sk1, sk2两个socket已经获取到响应的内容; w = [] 如果w中有值 w = [sk1,] # 表示: sk1这个socket已经连接成功; w = [sk1, sk2] # 表示: sk1, sk2两个socket已经连接成功; 注意: w先有值 伪代码: for client in w: content = “GET /url路径 HTTP/1.1\r\nUser-Agent:....” client.sendall(content.encode('utf-8')) for cli in r: response = cli.recv(8096) print(response) cli.close() IO多路复用, 能监听(感知)请求, 谁连接成功了, 谁收到消息了
IO多路复用的3种实现
select # 内部循环检测socket是否发生变化;1024个socket poll #内部循环检测socket是否发生变化; epoll #回调的方式
11、什么是异步非阻塞?
非阻塞: 其实就是遇到IO不等待
拿socket来说:
sk = socket.socket()
sk.setblocking(False) # 不等待
但是会报错,只要捕捉异常,跳过就OK
异步:
回调: 完成某个功能后(达到某个指定的状态),自动调用某个函数
例如:ajax请求成功后调用success函数
12、如何自定义异步非阻塞模块?
本质: socket + IO多路复用
1. 基于事件循环实现
2. 基于协程实现
以爬虫模块为例:
爬虫发送Http请求的本质: 创建socket对象
设置setblocking
IO多路复用: 循环监听socket是否发生变化,一旦发生变化,可以自定义操作(触发某个函数的执行)
13、HTTP协议的本质?
http协议是建立在TCP协议之上的一个协议,这个协议规定了请求头和请求体之间通过\r\n\r\n分割,请求头之间通过\r\n分割,这是发送的数据的格式。
字符串通过socket发送出去之后,只要拿到相应就断开了,这体现了它: 无状态,短连接。
常用的请求头和响应头:
请求头: Accept # text/html,application/xhtml+xml Accept-Charset # 字符编码 utf-8 Content-Type # 设置请求体的MIME类型(适用POST和PUT请求),application/x-www-form-urlencoded Referer # 请求来自哪个页面 User-Agent # 用户代理的字符串值 响应头: Access-Control-Allow-Origin # 指定哪些站点可以参与跨站资源共享, * Content-Type # 响应体的MIME类型 Set-Cookie # HTTP Cookie Status # HTTP响应状态
常用的8请求方法:
['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
状态码:
200 OK - [GET]:# 服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:# 用户新建或修改数据成功。 202 Accepted - [*]:# 表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:# 用户删除数据成功。 301 # 永久重定向 302 # 临时重定向 400 INVALID REQUEST - [POST/PUT/PATCH]:# 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 401 Unauthorized - [*]:# 表示用户没有权限(令牌、用户名、密码错误)。 403 Forbidden - [*] # 表示用户得到授权(与401错误相对),但是访问是被禁止的。 404 NOT FOUND - [*]:# 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 406 Not Acceptable - [GET]:# 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 410 Gone -[GET]:# 用户请求的资源被永久删除,且不会再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] # 当创建一个对象时,发生一个验证错误。 500 INTERNAL SERVER ERROR - [*]:# 服务器发生错误,用户将无法判断发出的请求是否成功。
14、浏览器上输入地址,回车,然后发生了什么?(HTTP请求生命周期)
- 域名解析:找IP,先本地找,再去域名解析服务器找;
- 连接成功;
- 浏览器发送数据;
- 服务端接收、处理数据并相应;
服务端做的事比较复杂,如果是Django或Flask。。。(回忆:框架的生命周期)
浙公网安备 33010602011771号