TCP中TIME_WAIT状态
如何产生
首先调用close()发起主动关闭的一方,在发送最后一个ACK后才会进入time_wait状态,该发送方会保持2MSL事件之后回到初始状态,MSL是数据包在网络空间的最大生存时间。
2MSL = 去向ACK最大生存时间 + 来向FIN最大生存时间
产生的原因
实现TCP全双工连接的可靠释放
如果主动关闭的一方不维护这样一个TIME_WAIT状态,那么在FIN到达的时候会用RST包响应对方,被认为是有错误发生服务端不会断开连接。
使旧的数据包因为过期而消失
TCP协议栈无法区分前后两条TCP连接的不同,会把它视为同一个连接,如果不维护TIME_WAIT状态可能产生数据错乱而导致各种无法预知的现象。
如何避免产生time_wait状态
服务器设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP链接处于time_wait状态,可以重用端口。
TCP三次握手
为什么需要三次握手
防止已经失效的连接请求报文段突然又传送到了服务端,因而产生错误。主要为了防止server端一直等待,浪费资源。
ping涉及到的协议
- 通过DNS协议将域名转换为地址
- 通过ARP协议将IP地址转换为MAC地址
- 发送ICMP回显请求给目标主机并等待回显应答
TCP粘包问题
保护消息边界和流
保护消息边界:传输协议把数据当作一条独立的消息在网上传播,接收端一次只能接收发送端发出的一个数据包。
面向流则是指无消息保护边界的,如果发送端连续发送数据,接收端有可能在一次接受动作中会接受两个或更多的数据包。
当缓冲区足够大时,有可能会一次接收到两个甚至更多的数据包,这样就需要接收端额外拆包,增加了工作量。
TCP粘包是指发送方发送的若干包数据到接收方接受时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
什么时候需要考虑粘包问题
- 如果利用tcp每次发送数据就与对方建立连接,然后发送完一段数据就关闭连接,这样就不会出现粘包问题
- 如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接受存储就OK,也不用考虑粘包。如果双方建立连接,需要在连接后一段时间内发送不同结构的数据,需要分包,一般可能会在头加入一个数据长度之类的包,以确保接受。、
粘包出现的原因
- 发送端需要等到缓冲区满才发送出去,造成粘包,也就是说为了提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据
- 接收方不及时接收缓冲区的包,造成多个包接受。接收方引起的粘包问题是因为接收方进程不及时接收数据,从而导致粘包现象(一次取走多个数据包)。
如何应对粘包
接收方创建一预处理线程,对接收到的数据进行预处理,将粘连的包分开。
Linux杀死进程
1 | kill -l pid // 以用户注销的方式结束进程,也试图杀死所留下的子进程,但不是总能够成功 |
查看当前系统的负载,说一下平均负载的三个参数
1 |
|
进程和线程的区别
- 进程是操作系统资源分配的基本单位,线程是操作系统调度的基本单位。一个进程可以拥有多个线程。
- 线程更加轻量级。创建一个线程比创建一个进程要快10~100倍。而且进程上下文切换比线程上下文切换消耗来得大。
- 进程有独立的地址空间,启动一个进程,系统会分配地址空间,建立数据表来维护代码段、堆栈段和数据段,非常昂贵。而线程之间不需要系统分配独立的地址空间,比较轻量级,在CPU上下文切换时比较小,线程之间可以共享进程的地址空间、全局变量、打开文件、子进程、信号和信号处理程序等。
- 多进程更加健壮,而多线程只要有一个线程死掉整个进程也会死掉。
阻塞IO和非阻塞IO
阻塞IO
进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程,操作成功则进程获取到数据。
特点:
- 进程阻塞挂起不消耗CPU资源,及时相应每个操作
- 实现难度低、开发应用较容易
- 适用于并发量小的网络应用开发
- 不适用于并发量大的应用,需要为每个请求分配一个处理进程以及时响应,系统开销大
非阻塞IO
进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞,进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回给进程。
异步IO
当进程发起一个IO操作,进程返回,但也不能结果,内核把整个IO处理完毕之后,会通知进程结果。如果IO操作成功则进程直接获取到数据。
IO复用模型
多个进程的IO可以注册到一个复用器上,然后用一个进程调用该select,select会监听所有注册进来的IO;
如果select没有监听的IO在内的内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任意IO在内核缓冲区中有可读数据时,select调用就会被返回;
而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
多个进程注册后,只有一个select调用进程被阻塞
典型应用:
- select、poll、epoll三种方案,nginx都可以选择使用这三种方案
特点:
- 专一进程解决多个进程IO’的问题,性能好;
- 实现、开发难度较大
- 适用于高并发服务应用开发:一个进程(线程)响应多个请求;
信号驱动IO模型
当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时就会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。
服务器常见的并发模型
单线程循环
多线程/多进程
主要特点是每个网络请求由一个进程/线程处理,线程内部使用阻塞式系统调用,在实际场景中适用预先分配的进程/线程/池,以减少频繁创建销毁线程的开销,往往获得更好的性能。
往往由一个单独的线程处理accept连接,其余线程处理具体的网络请求(收包、处理、发包);还可以多个进程单独listen,accept网络连接
优点:
- 实现相对简单
- 利用多核CPU资源
缺点
- 线程内部还是阻塞的,如果一个线程在handle的业务逻辑中sleep了,这个线程也就挂了
单线程IO复用
Linux高并发服务器中常用epoll作为IO复用机制,线程将需要处理的socket读写事件都注册到epoll中,当有网络IO发生时,epoll_wait返回,线程检查并处理到来的是socket上的请求。
优点:
- 实现简单
- 减少锁开销
- 减少线程切换开销
缺点:
- 只能使用单核cpu,handle时间过长就会导致整个服务挂死
适用场景:高IO、低计算、handle处理时间短
典型应用:redis
多线程/多进程IO复用
每个子进程都监听服务,并且都使用epoll机制来处理进程的网络请求,子进程accept()后将创建已连接描述符,然后通过已连接描述符来与客户端通信。
优点:支撑较高并发
缺点:异步编程不直观、容易出错
适用场景:支撑高并发
典型应用:Nginx
协程
在应用层用户态模拟线程,在用户态管理协程的调度与切换
优点:
- 减少上下文切换开销
- 编程友好、同步的方式写出异步代码
缺点:
- 多个协程运行在一个线程上,一个协程阻塞将导致整个线程阻塞
HTTP协议由哪些部分组成
HTTP协议包含:通用头域、请求消息、响应消息和主体消息
HTTP协议包含:http协议的请求和http协议的响应
http协议的请求又包含以下内容:
- 请求方法-URL-协议/版本号
- 请求头
- 请求正文
http协议的相应包含以下内容:
- 状态行
- 响应头
- 响应正文
通用头域
Cache-Control头域
指定请求和响应遵循的缓存机制,在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。
Date头域
表示消息发送的时间,时间的描述格式由rfc822定义。描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的地区。
Pragma头域
用来包含实现特定的指令
请求消息
响应消息
实体消息
JSON和Protobuf
什么是Protobuf
Google开发的一种协议,允许对结构化数据进行序列化和反序列化。
与JSON有什么不同
可以互相交换使用。JSON是一种消息格式,源自于JavaScript子集,以文本格式交换,几乎支持所有的编程语言。
Protobuf不仅仅是一种消息格式,还是一组用于定于和交换这些消息的规则和工具。拥有比JSON更多的数据类型。
根据实验,比JSON更快
为什么选择JSON,JSON的优势是什么
Protobuf缺少详细的文档,社区不够健壮。
Python 元类
元类就是类的类,就是type
如何创建一个元类
1 | Person = type("Person", (), {"age": 10}) |
类的创建过程
- 当Python见到class关键字时,首先会解析class…中的内容。例如解析基类信息,最重要的是找到元类信息
- 元类找到后,Python需要准备namespace(也可以认为是type中的dict参数),如果元类实现了
__prepare__函数,则会调用它来得到默认的namespace - 之后是调用exec来执行类的body,包括属性和方法的定义,最后这些定义会保存在namespace
- 上述步骤结束后,就得到了创建类所需要的所有信息,这时Python会调用元类的构造函数来创建真正的类