平常在本地使用 typoral 写markdown 笔记,通过git管理本地的笔记文件,定时上传github,使用hexo环境编译笔记并同步到github主页,但是由于 typoral 只是一个文档编辑器,每次要插入笔记引用和笔记yaml头等操作时很麻烦。
面经
TCP怎么保证稳定性
- 序列号/应答/超时重传机制:接收方在就收到消息后,需要发送一个应答信号,其中确认序号表示期望下一帧接受的数据的序号。如果发送发超过一定的时间没有收到应答信号,会重新传输,这个时间为 一个包往返时间 x 2 + 固定偏移时间。
- 滑动窗口:如果每收到一个包都需要应答的话,发送速度就很慢。因此TCP中使用滑动窗口来表明接收端一次最大可接受的数据的长度。发送方不需要等待接受方的应答信号,可以一次发送多个包出去,接受方也不用每个包都应答,只需要对接收到的最后一包序号做应答即可,如果发送发接收到了相同的应答信号超过三次,重发该序号以后的包。
- 拥塞控制:按照上述方法,如果把窗口设置的很大,发送方连续发送太多的包,就会导致网络拥堵,因此使用 慢开始 快恢复 的策略解决。通过一个拥塞窗口变量控制。起始拥塞窗口大小为1,发送方只发送一个包,收到应答后,一次发送两个包,依次,4,8,12….当拥塞窗口大小大于阈值时,进入拥塞避免阶段,将拥塞窗口的增长方式更换为线性增长,即每次加1。期间如果出现了超时,那么发送方将阈值设置为当前窗口的一半,拥塞窗口设置为1,重新开始慢启动的过程。如果出现了拥塞,即发送方收到了三次相同的应答信号,代表 了一部分包,只将拥塞窗口减半,并采用线性速度增长,这就是快恢复过程。这样可以达到:在TCP通信时,网络吞吐量呈现逐渐的上升,并且随着拥堵来降低吞吐量,再进入慢慢上升的过程,网络不会轻易的发生瘫痪。
TCP三次握手四次挥手
三次握手:服务端处于监听状态,等待客户端的连接请求。
- 客户机发送请求报文 SYN= 1, ACK = 0 信号,并设置一个随机的初始序号x
- 服务器收到请求连接的报文后,如果同意连接,发送应答报文, SYN= 1,ACK =1 确认序号为x+1,初始序号值设置随机y
- 客户机收到服务器的应答信号后,确认收到的应答序号是否为 x+1,并再次应答,应答的确认序号为 y+1,序号为 x+1
源 IP 可以伪造,所以要随机。报文不一定会按发送的时序到达目标,所以要加一。
三次握手的原因:首先前两次发送和应答是必要的,相当于请求连接方需要确认对方存在,所以需要确保能收到对方的应答。而第三次应答也是必要的,这是为了防止服务端打开重复的连接。当初始客户端发送的连接请求超时后,客户端会重新发送新的连接请求。但是超时的请求最终会到达服务端,通过第三次握手,客户端可以过滤调延时到达的连接请求。至于四次就不需要了,因为三次已经很稳定了。
四次挥手:由于TCP是全双工的协议,因此在一方请求释放后还需要等待另一方也同意释放,才算完全释放(确认号都为收到的序号+1,ACK值在连接建立后都为1)
- 客户机发送 FIN=1 sew = x 的释放报文
- 服务端收到释放报文后发送一个 响应报文,确认序号为 x+1 此时服务端可以接着将剩余数据发送完,服务端进入close wait状态
- 当服务端准备释放后,向客户机发送FIN=1的释放报文,序号为 y 确认序号为 x+1
- 客户机收到服务端的释放报文后,向服务端发送确认报文,序号为x+1,确认序号为y+1,并等待两个 时间周期(报文最大生存周期) 即可关闭。
四次挥手的原因:当一方想要请求释方连接时,需要主动发出释放请求,同时还需要知道对方同意释放了,因此前两次是必要的。但是由于是全双工通信,此时服务端可能还有数据每发完,因此当服务端发完剩余数据后还需要再发送一个释放连接请求,然后应答。延时等待是 因为 因为防止服务端未收到客户端发送的应答信号,服务端会再次重发FIN,此时客户端认为服务端错发了数据,就会回应RST信号,这时服务端也会认为出错。同时也为了让本连接时间段内产生的报文从网络中消息。
TIME WAIT 是主动关闭端在最后一次应答后需要等待的时间 发生在主动释放端
CLOSE WAT 是被动关闭端在收到请求释放信号后的状态,这个时间用将自己剩余的数据发送完
TCP的模型,状态转移
OSI七层模型和TCP/IP四层模型,每层列举2个协议
物理层:用于定义如何在不同传输媒体上传输bit流,向数据链路层屏蔽不同的传输介质的差异。传输的是比特流。协议有 802.11,RJ45等
数据链路层:用于点的点的数据帧的传输。由于物理层传输的bit可能有误差,因此数据链路层将物理层的bit流封装成帧同时采用一定的差错检验重传的机制来为网络层提供高质量的数据帧传输服务。PPP MAC协议
网络层:用于数据包的源到宿的传递和网际互连服务,通信单位是主机。传输的是数据包。IP ARP ICMP协议
传输层:用于端到端(进程)的可靠报文传递和恢复,传输的单位为报文,TCP UDP
会话层:建立、管理和终止会话,传输单位为SPDU,主要包括的协议为RPC NFS
表示层: 对数据进行翻译、加密和压缩,传输单位为PPDU,主要包括的协议为JPEG ASII
应用层: 允许访问OSI环境的手段,传输单位为APDU,主要包括的协议为FTP HTTP DNS 应用层 :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等协议。数据单位为报文。
搜索baidu,会用到计算机网络中的什么层?每层是干什么的
首先如果是新接入网络的主机需要通过DHCP协议向DHCP服务器获取自身的ip。
- 首先将一个DHCP请求报文传递给传输层,传输层将目的端口和源端口设置为 67 68 后组成一个UDP报文
- 将UDP报文向下传递给网络层,由于此时还没有ip地址,因此将目的ip和源ip分别设置为 255 0 组成一个ip数据包
- 将ip数据包向下传递给数据链路层,由于此时主机并不知道DHPC服务器的MAC地址,因此将源和目的MAC都设置为255 以广播的形式将请求以广播的形式发出
- 当DHCP服务器接收到广播信息后 向上层层处理 得到IP,UDP报文后得知这是一个DHPC请求,需要对他做出响应,将分配的IP地址,子网掩码,DNS服务器的IP地址,默认网关路由器的IP地址和子网掩码 等信息放进一个DHCP响应报文中发出。
- 主机收到 该帧后就可以设置自身的IP地址等信息
在知道了自身的IP地址以及网关路由器的IP地址后,便可以对输入的域名进行解析了。首先会在本地缓存中查找,如果没有就要通过DNS协议像DNS服务器获取域名到IP地址的转换。主机在向网关路由器发包时还需要通过 ARP协议去查询网关路由器的 MAC地址,如果本地已经有了ARP缓存可以直接读出来。完成上述步骤之后,便可以通过DNS协议向DNS服务器查询域名对应的IP地址了。获取IP地址后,主机通过应用层的http协议向服务端请求页面,如果使用的是https协议,还需要加密。http使用TCP协议来传输,TCP协议是传输层的一个协议,http报文传递给传输层后 TCP 可能会对报文分片并加上TCP协议相关的信息后传递给网络层,网路层使用IP协议实现数据包的网际路由功能,根据一定的路由选择算法例如内网的 Ford 和 OSPF算法,外网的BGP路由算法来实现网际数据的发送,当然在内网传递的数据报还需要通过ARP协议来获取网关路由器的MAC地址,并使用MAC协议在数据链路层转发到网关路由器。
HTTP 和 HTTPS 的区别
- HTTP 使用明文通信,不对通信内容加密,存在被侦听的风险,不安全。而HTTPS 使用TLS协议对内容进行加密,更安全
- HTTP 会话建立的过程只需要TCP的三次握手,而HTTPS建立延迟较高,因为需要TLS握手协商对称加密的密钥
- HTTPS 要求服务端向认证机构申请获取证书,同时需要客户端安装对应的根证书
- HTTP端口80 HTTPS 端口443
HTTPS 缺点:
- 握手的时候还要 协商 对称密钥 所以HTTPS协议握手阶段延迟增加
- 需要认证证书和根证书,所以麻烦,并且加密解密需要cpu资源
HTTPS 的加密原理是 首先通过 客户端向服务端获取公钥,即证书,这个证书需要服务端向特定机构认证。客户端在获取服务端发来的证书后需要对证书认证,而这个证书实际就是一个公钥,客户端使用公钥对生成的 随机数进行 加密,发给客户端,客户端使用私钥解密获得这个随机数,这个随机数即为随后对话过程中对称加密用的私钥。这样做是因为公钥加密比较耗时,对称加密速度快。
http+ssl实现https / https如何加密
- 客户端向服务器端发起SSL连接请求;(在此过程中依然存在数据被中间方盗取的可能,下面将会说明如何保证此过程的安全)
- 服务器把公钥(证书)发送给客户端,并且服务器端保存着唯一的私钥;
- 客户端用公钥对双方通信的对称秘钥(类似客户端生成的随机数)进行加密,并发送给服务器端;
- 服务器利用自己唯一的私钥对客户端发来的对称秘钥进行解密,在此过程中,中间方无法对其解密(即使是客户端也无法解密,因为只有服务器端拥有唯一的私钥),这样保证了对称秘钥在收发过程中的安全,此时,服务器端和客户端拥有了一套完全相同的对称秘钥。
- 进行数据传输,服务器和客户端双方用公有的相同的对称秘钥对数据进行加密解密,可以保证在数据收发过程中的安全,即是第三方获得数据包,也无法对其进行加密,解密和篡改
认证 签名
HTTP 返回码
- 1xx 请求已成功接收,需要进一步处理
- 2xx 请求已经成功、接受、理解
- 200 :ok 客户端请求成功
- 206:服务器已经正确处理部分GET 请求,实现断点续传或同时分片下载,该请求必须包含Range 请求头来指示客户端期望得到的范围
- 3xx 要完成请求需要进一步操作
- 300: 可选重定向请求,被请求的资源有一系列可供选择的反馈信息,由浏览器/用户自行选择其中一个
- 301: 该资源已被永久移动到新位置,将来任何对该资源的访问都要使用本响应返回的若干个URI之一
- 302: 请求的资源现在临时从不同的URI中获得
- 304: 请求报文头部包含条件,但是无法满足条件服务器就会返回304
- 4xx 客户端请求报文格式错误或者无法完成请求
- 403: 服务器收到请求,但是拒绝提供服务
- 404:请求资源不存在,无法找到文件。举个例子:输入了错误的URL
- 5xx 服务器无法完成合理的请求
- 500:服务器正在执行请求时发生错误
- 503:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求
http1.0 1.1 2.0改进
HTTP 1.0:(默认短链接)HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接。
HTTP 1.1:默认 长连接流水线模式 ,需要自己断开。客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果。防止客户端无法解析。
HTTP2 对底层传输改动比较大:
- HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题 。(以前key-value 的消息头,使用静态表和动态表压缩,动态表会变化,使用哈夫曼编码吧 )
- 多路复用,HTTP1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞。而 HTTP/2 做到了真正的多重请求-响应。HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息,并行地在同一个 TCP 连接上双向交换消息;同时,流还支持优先级和流量控制。
- 服务端推送 :服务端推送是一种在客户端请求之前发送数据的机制。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。(如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西。这相当于在一个 HTML 文档内集合了所有的资源)
为什么Http2可以多路复用?
因为Http传输的文本格式限制了并发的解析,不读到分隔符/换行符就不知道什么时候能把该请求读完,所以也不知道该从哪里读下一条请求,而二进制格式传输数据的方式,采用帧结构,每一帧前都有帧头表示该帧的长度。这样就可以快速分配内存,执行下一次读取。
IP地址作用,以及MAC地址作用
MAC地址是设备的硬件地址,每个网络设备都有唯一的MAC号,用于数据链路层对同一个局域网的网络设备寻址。而IP地址是IP协议提供的,为互联网中每个设备/主机分配的唯一逻辑地址,以此来屏蔽物理地址的差异。
请介绍一下操作系统中的中断
中断是指程序执行过程中,遇到急需处理的事件时,CPU暂停当前正在执行的任务转,保存现场,然后去处理对应的中断事件,处理完后再返回中断处继续执行之前的任务。 中断分为两步:中断响应 和 中断处理,前者由硬件完成后者程序完成。产生中断的原因有三种。
- CPU外部事件,例如IO中断,定时器中断等
- 程序处理异常,例如栈溢出,浮点溢出,指针越界等
- 程序使用了系统调用引起
TCP/UDP的异同
- TCP 是面向连接的,而UDP是无连接的
- TCP 是点对点传输,而UDP可以一对多多对一,多对多等
- TCP 是可靠传输,保证传输数据包无差错,不丢失,按序到达;而UDP是尽可能交付
- TCP 传输的是字节流 具有拥塞控制 和 流量控制,会根据接受方的接受缓冲或者网络状况设置数据包的长度和发送发速度。而UDP面向报文传输,不合并不拆分,保留上面传下来的报文边界。
- TCP 首部开销大,20字节,UDP 开销少8字节。源端口 目的端口 数据长度,校验和
- TCP 效率低但是可靠性好 UDP 反之。所以二者应用场景不同,TCP通常用在文本传输等需要保证正确性的场合,而UDP用在例如视频传输这种实时性要求高但是对错误有一定容忍的场合。
各自应用场景: 对于效率较高,但是准确性要求相对高的场景。例如文件传输,接受邮件,远程登录等;对于实时性的场景,但是对准确性要求没那么高的场景例如 QQ聊天,在线视频(多人同时视频)偶尔断续不是太大问题,并且此处完全不可以使用重发机制。
TCP粘包的原因和解决方法
发送端:如果接受取的缓冲区比较大,TCP发送端为了减少发送数据包的数量,将发送间隔较短的数据包合成一个大的数据包一起发送。而TCP传送的是字节流,没有消息边界,像流水一样,因此这样接收端收到后无法较好的将数据包拆分。
接收端:如果接收端没有及时将接收缓冲区的数据取出,也会导致粘包
解决办法:从协议上入,发送端强制发送,接收端强制接收(破环了协议的封装性);从应用程序上:双方约定一些协议,(包定长、包尾、加入包头信息)
UDP如何传输文本文件
在应用层上加入tcp的可靠性传输,加包号,接收方对包号进行检查,滑动窗口机制(发送文件的数据量)和拥塞机制(发送方速度)
UDP为什么有边界
udp基于数据报,一个数据报必须作为一条单独的消息发送,即使接收端连续收到两条消息,应用程序也必须要通过两次读取才行,所以就有了边界。
请你说一说HTTP协议
HTTP是超文本传输协议的简称。定义了客户机如何从服务器获取web页面的请求协议,以及web服务器如何把web页面传送给客户机。HTTP采用了请求响应模型,客户机发送一个请求报文,请求报文内容 为:首先第一行为请求类型,有get head post , URL 和 协议版本,接着是请求头部信息,key-value形式组成,然后空行接请求主题。响应报文的内容为:协议版本 和 应答状态号及对应的描述,接着是响应头部信息,空行和响应主体信息。
HTTP有如下特点:
- 简单快速:从上述描述可以看出报文结构简单。由于HTTP协议简单,使得HTTP 服务器的程序规模小,因而通信速度很快。
- 无连接:是指每次请求响应完毕后就关闭连接,这样可以避免一个客户机一直占用端口
- 灵活:可以传输任意类型的数据,使用 cotent-type来描述
- 无状态:无状态是指HTTP协议对于事务处理没有记忆能力。缺少状 态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接 传送的数据量增大,优点是,在服务器不需要先前信息时它的应答就较快
- 使用80端口,TCP/IP协议传输
- 支持C/S B/S模式
一个使用HTTP的例子如下:
- 在浏览器输入URL,本地浏览器首先使用DNS协议向根域名服务器获取域名对应的IP地址(也有可能本地有缓存就不需要像服务器获取)
- 获取到服务器IP地址后,根据IP地址和80端口,和服务器建立TCP连接
- 浏览器发送读取文件的HTTP请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器
- 服务端获取到请求报文后,将对应的HTML文本发送给浏览器
- 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接; 若connection 模式为keepalive,则 该连接会保持一段时间,在该时间内可以继续接收请求;
- 浏览器解析响应报文 显示收到的html内容
GET 和 POST的区别
本质上说,GET 适用于从服务端读取数据,而POST用于更新服务端的数据。GET只需要发送一次请求报文,服务端响应200 ok即可。而POST 则先发送 header,响应 100 continue 后 再发送主要数据,接受200 ok即可。
总的来说二者有如下区别:
- GET 通过URL 提交数据,把数据放在 url 后,而 POST 放在 HTTP 包体中
- GET 使用 URL 所以支持的参数长度有限,且只支持 ascii 编码,而POST没有这个限制
- GET 的参数直接暴露在 url 中所以不能用来传递敏感参数,没有POST安全
- GET 浏览器会主动缓存get的参数,而Post不会
- GET 和 POST 本身并无差别,都是TCP连接, 但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
socket编程中服务器端和客户端主要用到哪些函数
(请问你有没有基于做过socket的开发?具体网络层的操作该怎么做)
- TCP 服务端
- 打开 一个 socket
- bind 绑定ip地址和端口号到 socket 上
- 使用 listen 监听端口
- 收到连接请求后 使用 accept 函数建立连接
- 调用 read / write 读取发送数据
- TCP 客户端
- 打开一个 socket
- 设置要访问的服务器 端口号 和 ip
- connect 和主机建立连接
- read / write 读取发送数据
- close 关闭连接
- UDP 服务端:
- 打开 一个 socket
- 使用 bilind 绑定要监听的端口
- 使用 recvfrom 接收客户端的消息
- sendto 发送数据
- 关闭 close
- UDP 客户端
- 打开一个socket
- 设置服务器的地址 ip 和 端口
- 使用sendto 和 recvfrom 向主机发送和接受数据
- 关闭 close
(socket 的 type 参数 SOCK_STREAM SOCK_DGRAM 表示使用的TCP/UDP协议)
select,epoll的区别,原理,性能,限制
和阻塞式I/O模型一样都会阻塞当前线程,但是多路复用的优点是 可以一次性监视所有socket的变化,通过将文件描述符集合传递到内核,让内核监视所有socket的状态,一旦其中有一个数据准备好就会返回,效率相对 阻塞I/O更高。缺点是 需要进行两次系统调用,一次是 select 一次是 recvfrom。因此当连接数不是很高的话,多线程+阻塞式I/O模型效率反而更高。
- select : 每次在使用 select 之前要将文件描述符集合 从用户空间拷贝到内核空间,并遍历一遍(在内核空间遍历)如果有就绪的文件,则直接返回;如果没有,根据select设置的超时参数来决定等待多久再返回(如果超时参数为null,为阻塞式,只有当某个文件就绪后才会唤醒该进程,否则就根据超时时间主动返回select),将fd从内核态拷贝到用户空间,然后在应用层轮询遍历找到响应的连接。
- 由于文件描述符集合使用数组实现,因此可处理的最大连接数不是很多(多了也不行因为轮询遍历就会很慢)
- 需要轮询判断具体哪个文件描述准备就绪,因此连接数多了之后 复杂度多
- poll:相对 select 么有太大的区别,只是将数组换成了链式结构,从而支持同时处理更多的连接了
- epoll:解决了上述需要轮询的问题,通过注册回调函数实现,需要监听的文件描述符用一个红黑树维护,当某个事件发生时通过注册的回调函数将响应的文件描述符添加到链表中。因此 不再需要程序轮询遍历。
- LT:LT模式是指水平触发,支持阻塞和非阻塞模式,内核会告诉你一个文件描述符是否就绪,如果你不进行任何操作,还是会继续通知你。对读比较友好,得到通知后读一次就行不一定非要一次读完所有的数据因为下次还会再同时,但是对写不友好,因为发送缓冲区大部分都是空的,如果你并不想发数据,并且socket一直在监控中他就会一直通知写事件,所以必须保证没有数据要发送的时候把他从监控列表中移除
- ET:边缘触发模式,只支持非阻塞,内核在通知你一个文件描述符就绪后,如果你不进行处理,以后不会再通知。这个对读不友好,必须非阻塞的一下读完所有的数据,否则如果没有新增的数据到来,那么之前残留的没读完的数据他也不会再通知了,但是如果一次读的数据过长会带来饥饿问题。
- 两个模式的优缺点是:ET模式效率更高,减少了epoll 重复触发的次数,但是ET模式下必须使用非阻塞模式的套接口,避免由于一个文件的阻塞读写操作而忽略了其他的响应,因此这种可能会漏处理,需要更严谨的程序逻辑。
select 应用场景 select 的 timeout 参数精度为微秒,而 poll 和 epoll 为毫秒,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。select 可移植性更好,几乎被所有主流平台所支持 poll 应用场景 poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select epoll 应用场景 只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。
其实 select 不好是因为 内核 是没有记忆的,因此每次都要将 文件 描述符从用户空间向内核空间拷贝,实际中首先管理的文件描述符变动不大,因此没都全盘拷贝做了很多无用功。为了使得内核对我们需要管理的文件描述符有记忆,在内核中新建了一个 eventepoll 文件对象,它是存在内核中的,并且其中维护一个红黑树用于存储所有的文件描述符,用户通过 epoll_ctr 来向它注册要管理的文件描述符。同时还维护一个就绪队列,当某个文件就绪就将它添加到就绪队列中,通过回调函数实现。而用户只用调用 epoll_wait 去访问就绪队列是否为空即可。所以它不用遍历所有管理的文件描述符,也不用每次都要将所有的文件描述符从用户空间拷贝到内核空间。参考连接
epoll wait 流程:
- 服务器创建非阻塞 socket(server_fd)。
- epoll_create 创建 epoll 事件驱动 (epoll_fd)。
- epoll_ctl 监控 server_fd 的可读事件 EPOLLIN。
- 服务进程通过 epoll_wait 获取内核就绪事件处理。
- 如果就绪事件是新连接,accept 为客户端新连接分配新的文件描述符 client_fd,设置非阻塞,然后 epoll_ctl 监控 client_fd 的可读事件 EPOLLIN。
- 如果就绪事件不是新连接,read 读取客户端发送数据进行逻辑处理。
- 处理逻辑过程中需要 write 回复客户端,write 内容很大,超出了内核缓冲区,没能实时发送完成所有数据,需要下次继续发送;那么 epoll_ctl 监控 client_fd 的 EPOLLOUT 可写事件,下次触发事件进行发送。下次触发可写事件发送完毕后, epoll_ctl 删除 EPOLLOUT 事件。
- 客户端关闭链接,服务端监控客户端 fd,如果 read == 0,close 关闭对应 fd 从而完成四次挥手
为什么ET一定要非阻塞
阻塞:请求获取资源后,如果没有没有获取到则一直等待 非阻塞:没有获取到则返回为空 如果是 水平触发模式 每次 epoll_wait 后的文件描述符里一定是有东西可以读/写的,因此使用 阻塞的I/O 调用一次 read write 一定不会导致该文件描述符阻塞,而且只读一次都没事,因为是水平触发,你可以只调用一次读,如果此时没读完,下次epoll_wait 他还会再触发,还可以接着读;当然水平触发模式下也可以用非阻塞的方法式,就是 while 循环一直读,直到没有数据返回 erro_block 后退出循环。
但是在边缘触发模式下,只有数据缓冲空到满时会触发一次通知,最后一帧如果没有新增数据那么就不会再通知,所以读的时候必须一下子读完所有的数据 即 while 循环读取所有的数据。但是此时如果你再使用阻塞的IO读,那么最后一帧数据读完后必定阻塞,因为阻塞IO就是如果没数据了就会阻塞读,所以边缘触发模式只能采用非阻塞IO读,最后没有数据了返回ERRO退出while循环
ET模式读的饥饿问题
解决方法:创建一个描述符队列,通知要读的但还未读的就放入该队列中,定时定量去读,读完则将该描述符移除。
使用Linux epoll模型的LT水平触发模式,当socket可写时,会不停的触发socket可写的事件,如何处理?
当需要写出数据时,把数据write到fd中;如果数据较大,无法一次性写出,那么在epoll中监听EPOLLOUT事件 当EPOLLOUT事件到达时,继续把数据write到fd中;如果数据写出完毕,那么在epoll中关闭EPOLLOUT事件
多线程同时操作一个socket问题
设置EPOLL ONESHOT标志位,保证一个socket连接在任一时刻只被一个线程处理
TCP 快速重传为什么是三次冗余 ACK,这个三次是怎么定下来的?
统计概率问题:只收到两次,一定是乱序;乱序也会造成收到三次ack,只不过概率小,如果丢包,一定会收到三次。主要的考虑还是要区分包的丢失是由于链路故障还是乱序等其他因素引发。两次duplicated ACK时很可能是乱序造成的!三次duplicated ACK时很可能是丢包造成的!四次duplicated ACK更更更可能是丢包造成的!但是这样的响应策略太慢。丢包肯定会造成三次duplicated ACK! 综上是选择收到三个重复确认时窗口减半效果最好,这是实践经验。
为什么会乱序:负载均衡、路由器流量调整造成的多路径传送
请你来介绍一下udp的connect函数
- 因为UDP可以是一对一,多对一,一对多,或者多对多的通信,所以每次调用sendto()/recvfrom()时都必须指定目标IP和端口号。在未建立UDP套接字的上调用connect
- 变成了一对一的连接,可以和TCP一样使用write() / send() 传递数据,而不需要每次都指定目标IP和端口号,写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址。可以给已连接的UDP套接字调用sendto,但是不能指定目的地址。sendto的第五个参数必须为空指针,第六个参数应该为0。
- 但是它和TCP不同的是它没有三次握手的过程,调用connect只是预先记录了ip地址和端口号;
- 依然是不可靠传输,如果不调用connnet 那么给同一个目标发送消息 是 连接套接字→发送报文→断开套接字→连接套接字→发送报文→断开套接字 →………效率比较低;connnet 后中间就不会再断开套接字了;连接套接字是需要一定开销的,比如需要查找路由表信息。所以,UDP 客户端程序通过 connect 可以获得一定的性能提升
- 在一个已连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect指定协议地址的数据报。这样就限制一个已连接UDP套接字能且仅能与一个对端交换数据报
- 还可以通过在已建立连接的UDP套接字上,再次调用connect()。TCP套接字只能调用一次connect()函数。
- 指定新的IP地址和端口号
- 断开连接
- 由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接的UDP套接字不接收任何异步错误。
ARP地址解析协议
每个主机都会在自己的工作区建立ARP缓冲区(ARP列表),表示IP地址和MAC地址间的对应关系。(不是永久的,隔一会儿更新,因为IP与MAC的对应关系会变)ARP广播查询目的主机的MAC地址,目的主机ARP单播响应自己的MAC地址收到广播响应时比对是否和自己的IP地址相同,相同则响应,不同则丢弃不管,如果广播主机一直没有收到响应,表示ARP查询失败。本网段有机器中病毒或发生故障,不断发送查询导致网络拥塞RARP 逆地址解析协议 MAC解IP
DHCP协议
动态主机配置协议。系统连接到网络上,并获取所需要的配置参数手段。通常被应用在大型的局域网络环境中,主要作用是集中的管理、分配IP地址,使网络环境中的主机动态的获得IP地址、Gateway地址、DNS服务器地址等信息,并能够提升地址的使用率。
网桥的作用
网桥是一个局域网与另一个局域网之间建立连接的桥梁
ICMP网络控制报文
Ping 检查两台主机是否网络联通 向目的主机发送ICMP Echo 请求报文,目的主机收到后回复Echo应答报文,根据时间和成功率计算出主机间的通信时间和丢包率。Traceout 从源主机到目的主机经过的节点路径,发送IP封装的无法交付的UDP用户数据包,并由目的主机发送终点不可达差错报告报文,TTL由1开始发送,每次累加1,直到收到差错报告报文,即可知从源到目的经过多少路由
TTL (time to live) 生存时间
表示数据包在网络中的时间,经过一个路由该数据包TTL就减1,这是因为有些路由会形成环,如果没有TTL,那么数据包就会在网络中打转,拥塞网络
网络层和传输层协议的区别
网络层用于主机之间的逻辑通信,传输层主要用于进程之间的逻辑通信
应用层和传输层的keep alive区别
传输层TCP的 keep alive 用于 探测连接的对端是否存活,如果对端因为网络,死机导致掉线 或者 处于半连接状态 那么都可以通过 保活报文来处理。
HTTP 的keep alive 打开 是指长连接 可以连续发送多个请求 而不用断开连接
请你说一下阻塞,非阻塞,同步,异步
阻塞和非阻塞:调用者在事件没有发生的时候,一直在等待事件发生,不能去处理别的任务这是阻塞。调用者在事件没有发生的时候,可以去处理别的任务这是非阻塞。
同步和异步:调用者必须循环自去查看事件有没有发生,这种情况是同步。调用者不用自己去查看事件有没有发生,而是等待着注册在事件上的回调函数通知自己,这种情况是异步
讲述一下Socket编程的send() recv() accept() socket()函数
- socket 用于创建一个套接字,返回文件描述符。由于一个机器中可能有很多进程都需要使用相同的TCP 或者 UDP 协议发送接收数据,为了区分不同主机中不同进程,将IP 地址 端口号 绑定到 套接字中,就可以唯一区分互联网中两个主机中需要通信的两个进程。
- send:用于向 TCP 连接的另一端发送数据,一般客户机用 send 将请求报文发送给服务器,服务器用 send 做应答。调用 send 的时候将用户空间中将要发送的数据写入发送缓冲区,然后使用协议发送。
- recv:用于从 TCP 连接的另一端接收数据,等待接收缓冲区中数据接收完毕后,recv函数将缓冲区中的数据读给应用层。
- accept:用于接收 TCP 连接,内部会维护一个 半连接队列 和 一个连接完成的队列,如果连接完成队列为空,则阻塞,否则返回队列第一个连接成功的文件描述符。
http协议会话结束标志怎么截出来
看tcp连接是否有断开的四步挥手阶段
TCP/IP数据链路层的交互过程
数据链路层通过 MAC 地址作为通信目标,数据包到达网络层准备往数据链路层的时候,首先查找自己的ARP表,查找局域网中的 ip-mac映射关系,如果找到了,就将目标IP的MAC地址封装到数据链路层的数据包的包头, 如果没找到就使用广播的方式在局域网中查找,对应IP地址的设备在收到广播后会以单拨的形式将自己的mac地址回复给请求的机器
IP层怎么知道报文该给哪个应用程序,它怎么区分UDP报文还是TCP报文
端口号 包头协标识字段
server端监听端口,但还没有客户端连接进来,此时进程处于什么状态
如果是阻塞式的方式,就处于阻塞状态,如果调用 epoll select poll 这样的 I/O 复用模型,因为需要轮询判断有无响应。是运行状态。
TTL是什么
TTL是 Time To Live的缩写,该字段指定IP包被路由器丢弃之前允许通过的最大网段数量
HTTPS一定安全嘛
不是绝对安全的,可以通过中间人攻击。中间人攻击是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。HTTPS 使用了 SSL 加密协议,是一种非常安全的机制,目前并没有方法直接对这个协议进行攻击,一般都是在建立 SSL 连接时,拦截客户端的请求,利用中间人获取到 CA证书、非对称加密的公钥、对称加密的密钥;有了这些条件,就可以对请求和响应进行拦截和篡改。
客户端断电或网络异常怎么处理
TCP keep-alive 借助心跳机制来感知这种状况,一般的做法是,服务端往对端发送一个心跳包并启动一个超时定时器,如果能正确收到对端的回应,说明在线,如果超时,可以进行一系列操作,比如重试、关闭连接等等。
cookies 和 seesion区别
cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上,通常。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。主要功能是:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)记录用户信息
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)浏览历史信息
Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息,存储在服务端
Cookie 和 Session 有什么不同
- 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
- 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。
- 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
- 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。
- 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie
cookies 和 seesion 的关联
SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统根据seesion和cookie来验证用户登录状态。
用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。
当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
cookie 被禁用了怎么用seesion
可以在 URL 中携带 seesion 参数
HTTP接口的幂等性
幂等性是系统服务对外一种承诺,承诺只要调用接口成功,外部多次调用对系统的影响是一致的。比如在 扣款中 用户如果点击了多次支付,应该只扣一次款。
解决幂等问题可以通过多个角度实现,在客户端 只允许按钮可以被按下一次。在服务端可以使用状态机等,还可以使用token机制,客户端请求时 添加 token 参数,重复的请求token相同,服务端就可以区分了。
https://segmentfault.com/a/1190000020172463
三次握手后最后一次 ack 丢包了会怎样
服务端 会根据超时重传机制重新发送 SYN+ACK报文,一定次数后 还没响应就关闭; 客户端在发送第三次响应包后 就进入了 链接状态,他感知不到服务端是否收到最后一次的握手响应,他会向服务端正常发送数据,Server端将以 RST包响应,方能感知到Server的错误。
ICMP网路控制报文
Ping 检查两台主机是否网络联通 向目的主机发送ICMP Echo 请求报文,目的主机收到后回复Echo应答报文,根据时间和成功率计算出主机间的通信时间和丢包率。
Traceout 从源主机到目的主机经过的节点路径,发送IP封装的无法交付的UDP用户数据包,并由目的主机发送终点不可达差错报告报文,TTL由1开始发送,每次累加1,直到收到差错报告报文,即可知从源到目的经过多少路由
TCP xxx 攻击
IPV4 和 6 的区别
DNS解析过程
什么场景下机器产生大量的time_wait,有什么影响
DNS解析过程以及DNS劫持
阻塞socket和非阻塞socket的区别
从局域网访问一个URL的整个过程,
NAT技术,DNS,ARP协议等
如果有一个包1M,怎么分片
把TCP的头部写出来看看
TCP中有一个包丢了怎么重发,接收端失序的包放在哪里
服务端怎么解析http请求
服务端怎么同时处理多个请求
socket通信编程需要调用哪些api
socket发送速度过快会发什么什么
socket中的EAGAIN错误码
IPv4和IPv6的区别
除了select,poll,epoll,如果让你实现第四种IO多路复用,你会怎么设计
为了进一步提高并发量,客户端可以怎么做,服务端可以怎么做
如果目标服务器被代理服务器劫持会怎么样
reactor模型组成
请自己设计一下如何采用单线程的方式处理高并发
包丢了,tcp的发送端措施,除了重传机制,还有别的吗
第一次握手丢失了会怎么办?第二次握手丢失了怎么办?
设计可靠udp, 效率要比tcp要高
说说都了解哪些网络协议?我们视频在网络层是怎么实现的?
计算机网络-链路层
个人理解的 首先数据链路层的作用是:物理层比特流在介质上传输肯定有错误,因此依靠链路层通过 CRC
等方法进行差错检验,然后配合滑窗的方法(这个TCP中也有)进行错误重传和流量控制等,向网络层提高高质量的数据传输服务。从应用场景上看,每个主机ISP
过独立网线连接,这就是点对点通信,通常使用PPP
协议控制。
计算机网络-网络层
数据链路层 是专注于两个设备之间的 交互连接,而网络层则是为了跨区域之间的交流。
计算机网络-概述
来自 www.cyc2018.xyz 的内容
计算机网络-应用层
位于计算机网络体系结构的最上层,前面四层做的所有事情就是为了他服务,他也是设计和建立计算机网络的最终目的,通俗的讲,就是我们开发的应用软件,就处于这一层。比如,QQ,浏览器访问网页,等等你看得到的应用软件都要使用这一层协议中的某一种,随着信息往下走,会逐渐在数据帧中增加其他层的协议。
计算机网络-传输层
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。
应用层-HTTP
HTTP
请求和响应报文
客户端发送一个请求报文给服务器,服务器根据请求报文中的信息进行处理,并将处理结果放入响应报文中返回给客户端。
请求报文结构:
1 | GET http://www.example.com/ HTTP/1.1 |
- 第一行是包含了请求方法、URL、协议版本;
- 接下来的多行都是请求首部 Header,每个首部都有一个首部名称,以及对应的值。 ( : 的形式)
- 一个空行用来分隔首部和内容主体 Body
- 最后是请求的内容主体
响应报文结构:
1 | HTTP/1.1 200 OK |
- 第一行包含协议版本、状态码以及描述,最常见的是 200 OK 表示请求成功了
- 接下来多行也是首部内容
- 一个空行分隔首部和内容主体
- 最后是响应的内容主体
URL
HTTP 使用 URL( U niform Resource Locator,统一资源定位符)来定位资源,它是 URI(Uniform Resource Identifier,统一资源标识符)的子集,URL 在 URI 的基础上增加了定位能力。URI 除了包含 URL,还包含 URN(Uniform Resource Name,统一资源名称),它只是用来定义一个资源的名称,并不具备定位该资源的能力。例如 urn:isbn:0451450523 用来定义一个书籍名称,但是却没有表示怎么找到这本书。
http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name
从上面的URL可以看出,一个完整的URL包括以下几部分:
- 协议部分:该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符
- 域名部分:该URL的域名部分为“www.aspxfans.com”。一个URL中,也可以使用IP地址作为域名使用
- 端口部分:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口
- 虚拟目录部分:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/news/”
- 文件名部分:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名
- 锚部分:从“#”开始到最后,都是锚部分。本例中的锚部分是“name”。锚部分也不是一个URL必须的部分
- 参数部分 从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“boardID=5&ID=24618&page=1”。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。
HTTP 方法
客户端发送的 请求报文 第一行为请求行,包含了方法字段
GET
获取资源 当前网络请求中,绝大部分使用的是 GET 方法
HEAD
获取报文首部 和 GET 方法类似,但是不返回报文实体主体部分。主要用于确认 URL 的有效性以及资源更新的日期时间等。
POST
传输实体主体 POST 主要用来传输数据,而 GET 主要用来获取资源. 更多 POST 与 GET 的比较请见第九章
PUT
上传文件 由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
1 | PUT /new.html HTTP/1.1 |
PATCH
对资源进行部分修改 PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
1 | PATCH /file.txt HTTP/1.1 |
DELETE
删除文件 与 PUT 功能相反,并且同样不带验证机制。
1 | DELETE /file.html HTTP/1.1 |
OPTIONS
查询支持的方法 查询指定的 URL 能够支持的方法。 会返回 Allow: GET, POST, HEAD, OPTIONS
这样的内容
CONNECT
要求在与代理服务器通信时建立隧道 使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。代理
1 | CONNECT www.example.com:443 HTTP/1.1 |
TRACE
追踪路径 服务器会将通信路径返回给客户端。发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。
HTTP 状态码
服务器返回的 响应报文 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
状态码 | 类别 | 含义 |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
1 | 200 OK //客户端请求成功 |
HTTP 首部
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。
Socket
socket 的诞生是为了应用程序能够更方便的将数据经由传输层来传输,所以它本质上就是对 TCP/IP 的运用进行了一层封装,然后应用程序直接调用 socket API 即可进行通信。
面经2
同步互斥和通信的区别
同步是指多个线程之间存在的一种松散的协作关系,一个线程的执行与否依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。例如生产消费者模型中,消费者需要等待生产者释放信号的条件后才会有动作
互斥是指某个共享资源在某一时刻只能有一个线程对他进行读写,而其他想要获取它的线程只能休眠。线程互斥可以看做特殊的同步
什么是系统调用
系统调用是操作系统提供给用户的一组接口,用户程序工作在用户态只能受限的访问系统的资源。通过系统调用用户可以主动从用户态转到内核态,进而获得更高的权限,访问操作系统更多的资源。write fork 等都是系统调用
用户态切换到内核态的3种方式
系统调用 异常(例如缺页异常) 外部中断
从出发方式看,可以在认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一样的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断处理机制基本上是一样的,用户态切换到内核态的步骤主要包括:
- 从当前进程的描述符中提取其内核栈的ss0及esp0信息。
- 使用ss0和esp0指向的内核栈 ; 将当前进程的cs,eip (CS+EIP = PC),eflags,ss(栈段地址), esp(栈顶指针)信息保存起来,这个过程也完成了由用户栈找到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
- 将先前由中断向量检索得到的中断处理程序的 cs,eip 信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
软件调用 system_call 函数 触发 80H 中断,然后通过80H的中断向量找到系统调用的处理函数,在该函数中根据不同的系统调用号 找到内核中对应的函数去处理,系统调用号是不同调用函数独有的,用户空间通过CPU寄存器暂存调用号。处理完毕后返回值通过 CPU 寄存器暂存。
互斥锁机制,以及互斥锁和读写锁的区别
互斥锁: 用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
读写锁:分为读锁 和 写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒,优先唤醒写锁。适用于读取数据的频率远远大于写数据的频率的场合。
互斥锁和读写锁的区别:
- 互斥锁 只允许一个进程访问 资源,不区分读写,因此不能多个线程同时读。
- 读写锁区分读 和 写,而互斥锁不区分
Linux的四种锁机制
读写锁:
互斥锁:
自旋锁:spinlock 在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源。
RCU锁:在加上写锁后,所有的读操作都被阻塞了,RCU锁就是解决这个问题的。它支持在写的时候同时高并发读,只有多个同时写的线程需要加自旋锁,但是这个也是很高效的。本质是当线程要对临界资源修改时,创建数据副本,对副本修改,修改完毕后,等待所有的读线程退出临界区后(宽松区)才会将原始的数据指针指向修改后的数据副本。这个指针的修改过程是 原子的,不会发生线程调度和中断,同时只能有一个线程访问并修改数据指针的指向。
说一说进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞
- 创建状态:进程正在被创建
- 就绪状态:进程被加入到就绪队列,等到CPU调度运行
- 运行状态:进程获得时间片,正在CPU中运行,可以和就绪态相互转换
- 阻塞状态:进程因为等待某种 IO/系统 资源设备而被阻塞 而暂时不能运行,当获取资源后 会 转换为就绪态等待再次被调度
- 终止状态:进程运行结束
早期内存小,资源紧张,I/O设备访问速度慢,可能会出现很多进程都处于阻塞状态等待I/O,这时候可以通过交换技术把阻塞的进程换到外存,腾出内存空间。从而出现了进程的挂起状态:进程被交换到外存,进程状态就成为了挂起状态。而同进程中解决内存占用过大的问题是通过虚拟内存技术解决。
- 动态就绪:进程在内存,只要CPU调度就可以运行
- 静态就绪:进程在外存,处于就绪状态,换入到内存然后CPU给调度就可以运行
- 活动阻塞:进程在内存,但是由于某种原因被阻塞了
- 静止阻塞:进程在外存,同时被某种原因阻塞了
新的转换过程:
- 内存不够时:活动就绪 -> 静止就绪,调到外存
- 内存不够时:活动阻塞 -> 静止阻塞,调到外存
- 内存不够 且 时间片用完:运行态 -> 静止就绪,调到外存
A* a = new A; a->i = 10;在内核中的内存分配上发生了什么
可以首先介绍一下内存的模型 各个段的作用
- A *a 是一个局部指针变量,在栈区,开辟 4/8字节的空间分配给指针
- new A 是动态内存分配,在堆区分配,大小为类A的大小
- 将指针 a 的内存区域填入栈中类A申请到的地址的地址。即指针指向 new 分配的地址
- a->i:先找到指针a的地址 0x000m,通过a的值 0x000n和i在类a中偏移offset,得到a->i的地址0x000n + offset,进行*(0x000n + offset) = 10的赋值操作,即内存0x000n + offset的值是10
给你一个类,里面有static,virtual,之类的,来说一说这个类的内存分布
static修饰成员变量…. 在全局数据区分配内存;static修饰成员函数…..在代码区
如果一个类是局部变量则该类数据存储在栈区,如果一个类是通过new/malloc动态申请的,则该类数据存储在堆区。对于含有虚函数的对象,在首地址处有个虚函数表指针,指针指向了该类的虚函数表,虚函数表是个数组一样的表里面每一项存放的是该类的虚函数地址(虚函数指针),虚函数表存放在 代码段中的只读数据区,虚函数表中的函数指针指向的是代码段中的虚函数。
软链接和硬链接区别
链接除了解决了文件共享的问题,还可以隐藏文件的原本路劲,节省空间,提高安全性等。硬链接相当于一个 inode 有不同的文件名,删除源文件,文件实际不会从系统上删除,引用依然有效。软链接相当于创建了一个新的文件,新的 inode 只不过存储的是指向的文件的路径,使用 ln -s 创建,删除源文件,软连接就失效了。
什么是大端小端以及如何判断大端小端
大端是低字节存在高地址,小端是低字节存在低地址。联合体变量总是从低地址往高地址存储,所以使用联合体判断
1 | union Test{ |
静态变量什么时候初始化
对于C语言,不存在类的构造问题,静态变量存放在 全局数据区,在编译时就可以初始化;而 c++ 的静态对象存在构造问题,全局静态对象在 main函数执行之前初始化,局部静态对象在第一次使用的时候初始化。
用户态和内核态区别
是操作系统的两种运行级别,二者权限不同。用户态的权限级别最低,运行在用户态的程序不能直接访问操作系统的数据结构,只能通过系统调用,异常(例如缺页异常),中断从用户态转向内核态。
两个进程访问临界区资源,会不会出现都获得自旋锁的情况?
???
单核cpu,并且开了抢占可以造成这种情况。
windows消息机制知道吗,请说一说
当用户有操作(鼠标,键盘等)时,系统会将这些时间转化为消息。每个打开的进程系统都为其维护了一个消息队列,系统会将这些消息放到进程的消息队列中,而应用程序会循环从消息队列中取出来消息,完成对应的操作。
C++的锁你知道几种?
锁包括互斥锁,自旋锁和读写锁, RCU锁
说一说你用到的锁
生产者消费者问题利用互斥锁和条件变量可以很容易解决,条件变量这里起到了替代信号量的作用
内存溢出和内存泄漏
内存溢出:指程序申请内存时,没有足够的内存供申请者使用。内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据
- 集合类中有对对象的引用,使用完后未清空,使得不能回收
- 代码中存在死循环或循环产生过多重复的对象实体
- 启动参数内存值设定的过小
- 使用的第三方软件中的BUG
内存泄漏:内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。
- 堆内存泄漏:
- 系统内存泄漏:
- 虚函数:
你都使用什么线程模型
OPEN MP
Future模型:Future是把结果放在将来获取,当前主线程并不急于获取处理结果。允许子线程先进行处理一段时间,处理结束之后就把结果保存下来,当主线程需要使用的时候再向子线程索取。在 c++ 中 使用 pakage_task 类 对可执行对象封装后 可以返回一个 future 对象,通过future 对象可以异步的获取 可执行对象在其他线程中执行后返回的结果。在线程池的实现中,就是通过 package_task 对函数和参数进行统一封装后返回future对象,用于异步获取任务的执行结果。
fork&join模型:该模型就是 递归的将一个大任务拆解为小任务。然后由单独的线程分别执行小任务,当每个子线程执行结束之后逐级回溯,返回结果进行汇总合并,最终得出想要的结果。在图像分块多线程中采用了fork join 的思路,最大线程数预先根据cpu核心数设置,如果输入图像大于1000 就分四个线程分别处理。让后汇总结果进入下一步。
actor模型:actor模型属于一种基于消息传递机制并行任务处理思想,它以消息的形式来进行线程间数据传输,避免了全局变量的使用,进而避免了数据同步错误的隐患。actor在接受到消息之后可以自己进行处理,也可以继续传递(分发)给其它actor进行处理。在使用actor模型的时候需要使用第三方Akka提供的框架。
生产者消费者模型:生产者消费者模型都比较熟悉,其核心是使用一个缓存来保存任务。开启一个/多个线程来生产任务,然后再开启一个/多个来从缓存中取出任务进行处理。这样的好处是任务的生成和处理分隔开,生产者不需要处理任务,只负责向生成任务然后保存到缓存。而消费者只需要从缓存中取出任务进行处理。使用的时候可以根据任务的生成情况和处理情况开启不同的线程来处理。比如,生成的任务速度较快,那么就可以灵活的多开启几个消费者线程进行处理,这样就可以避免任务的处理响应缓慢的问题。(线程池就是采用了这个模型,生产者为用户,用户通过GUI线程像 任务队列中放置 地标,多个消费者从 任务队列中取任务完成)
master-worker模型:master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后在master中根据任务的具体情况进行分发给其它worker子线程,然后由子线程处理任务。如需返回结果,则worker处理结束之后把处理结果返回给master。
说一下微内核与宏内核
宏内核:除了最基本的进程、线程管理、内存管理外,将文件系统,驱动,网络协议等等都集成在内核里面,例如linux内核。
- 优点:效率高。
- 缺点:稳定性差,开发过程中的bug经常会导致整个系统挂掉。
微内核:内核中只有最基本的调度、内存管理。驱动、文件系统等都是用户态的守护进程去实现的。
- 优点:稳定,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃
- 缺点:效率低。典型代表QNX,QNX的文件系统是跑在用户态的进程,称为resmgr的东西,是订阅发布机制,文件系统的错误只会导致这个守护进程挂掉。不过数据吞吐量就比较不乐观了。
说一下僵尸进程
正常进程:父进程和子进程是异步的,就是父进程在创建子进程后,子进程什么时候结束,运行到什么状态,父进程是不知道的。Unix中,子进程退出后,除了会释放子进程占用的系统资源,但是会保留子进程的进程号,退出状态,运行时间等这种基本信息。父进程可以通过 waitpid() 系统调用来获得这个信息,调用子进程信息才会完全从内核中删除。
孤儿进程:就是父进程提前退出,子进程会被init初始进程托管,并在子进程退出后由它代为处理最后的信息。
僵尸进程:就是子进程退出后,父进程未调用 waitpid 系统调用 获取 子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,占用着进程号。
- 因为系统的进程号是有限的,用完后就无法创建新的进程。
- 僵尸进程是每个进程必经的阶段
- 可以同过 ps 命令查看 进程,状态为 Z 的就是僵尸进程,可以通过 kill 命令清楚
- 子进程在 退出的时候可以向父进程发送信号,告诉它及时处理,或者 fork 两次让他变成孤儿进程由init 进程接管
守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。
请问GDB调试用过吗,什么是条件断点
5种IO模型
- 阻塞式I/O:如果当前进程获取不到I/O时间,就进入休眠状态,会阻塞当前线程的其他任务,但是并不会增加cpu的负担
- 非阻塞式:类似于轮询,隔段时间检查一下时间是否就绪,中途可以区处理别的事物。但是反复轮询会降低cpu效率
- 多路复用:多路复用和阻塞式类似,也会阻塞当前线程,但是在一个线程中可以管理多个IO事件,原理就是统一管理一批文件描述符,当其中某一个有响应后就处理。
- 信号驱动:应用程序使用套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据
- 异步式:信号驱动I/O是由内核通知应用程序何时启动一个I/O操作,而异步I/O模型是由内核通知应用程序I/O操作何时完成
如何设计server,使得能够接收多个客户端的请求
多线程,线程池,io复用
死循环+来连接时新建线程的方法效率有点低,怎么改进?
提前创建好一个线程池,用生产者消费者模型,创建一个任务队列,队列作为临界资源,有了新连接,就挂在到任务队列上,队列为空所有线程睡眠。改进死循环:使用select epoll这样的技术
怎么实现线程池
- 设置一个生产者消费者队列,作为临界资源
- 初始化n个线程,并让其运行起来,加锁去队列取任务运行
- 当任务队列为空的时候,所有线程阻塞
- 当生产者队列来了一个任务后,先对队列加锁,把任务挂在到队列上,然后使用条件变量去通知阻塞中的一个线程
内存池
内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。
动态链接库与静态链接库的区别
如果可执行文件使用静态库编译,那么编译的时候会将静态库中的可执行代码编译进可执行文件中,而动态库是在程序运行时才加载的。
因此区别是:
- 使用静态库编出的可执行文件更大,但是运行执行效率更高。而动态库需要运行时加载所以慢一点
- 动态库是系统中所有进程共享的,如果两个程序使用了相同的动态库,那么只需加载进内存中一次,而使用静态库编译的文件则在每个程序中都有一份实现,因此动态库更节省内存
- 动态库方便后续维护,如果需要更新功能,更换动态库即可不用重新编译可执行文件,而静态库不行
- 静态库中不能再包含其他动态或静态库,而动态库可以
C语言函数调用的原理
函数调用流程如下:
- 首先要将当前PC设置为下调用函数的下一条指令,便于调用完毕后返回。同时将PC设置为子函数地址
- 还要将调用者栈帧的 栈底 bp 指针压入栈中, 并将 sp 指针赋给 bp , 即当前栈顶为子函数的栈底
- 将调用函数的入口参数 从右到左 依次入栈
- 通过PC跳转执行,最后返回值 可以通过eax保存,具体的返回值保存方法
- 返回值类型的,如果所用内存较大 eax 寄存器无法直接保存下,那就预先在栈上开辟一个空间,然后将变量的地址作为第一个隐含的函数入口参数传入函数中;否则直接用 eax cpu寄存器作为媒介返回。
分页和分段存储管理有何区别
- 页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。段则是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。
- 页的大小固定且由系统决定;而段的长度却不固定,决定于用户所编写的程序。
- 分页的地址空间是一维的,程序员只需利用一个记忆符,即可表示一个地址;而分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。
多重继承虚函数怎么实现
如果继承自多个类,并且每个类都有虚函数,那么实例化后的对象 就有N个虚表指针;如果虚表中最后一个是 星号 代表还有下一个虚表,否则无。
C++原子类型如何实现
- 单核系统中 简单的关中断 屏蔽任务调度即可
- 在多核系统中 不仅如此还存在多个核的缓存不一致性的问题 ,还需要结合CAS实现 原子操作。CAS一般有硬件指令级的支持,原理就是,首先从内存中读取要修改的变量,将他和期望值比较,如果和期望值不同,那么就说明它被修改了就什么都不做返回false,如果相同,则对变量修改并返回true。
1 | // 比如我要执行 i++ 首先是 从内存中读取 i 发现他等于 0 然后在cpu缓存中执行+1为1后要把它写回内存,这时候 CAS 作用就来了,如果回写的时候,我先读一遍发现 i 不是 0 而是 其他值,说明在我执行i+1过程中有其他人修改了内存值,因此此时 返回false 不能将自己cpu中缓存的1写给i; |
进程调度算法详解
多个线程轮流打印1-100
思路
- 通过一个变量来控制线程同步的逻辑
- 如果
1 | std::mutex mtx; |
inline 和 define区别
define 是预处理阶处理的,只是简单的替换,并没有入口参数类型检查啥的。而Inline 在编译阶段将代码段直接擦插入到每个调用它的地方,但是并不是函数调用的形式,因此效率比较高且安全可靠 是一种空间换时间的手段。
两个线程对一个int a = 1 的变量同时进行 a++ 操作一万次,那么最后a的值是多少呢
10000 - 20000 之间 考虑多线程冲突
什么是闭包 lambda表达式
c++11 中 匿名函数 可以实现闭包,但是概念上 闭包是指 包含函数指针和上下文数据环境的一个结构体,相当于捕获的上下文数据+函数地址。如果一个函数没有捕捉自由变量那么其实就可以实现为一个函数指针,如果捕获了自由变量,他就是一个闭包,当他脱离当前的环境,依然可以正常执行。c98中没有匿名函数,但是可以通过仿函数来模拟闭包。
因此匿名函数返回的就是一个匿名的闭包实例,他是个右值,可以通过捕获的方式封装当前作用域的变量或者他们的引用。
C++11 vector库中有什么改进
有了移动拷贝构造函数 避免了很多不必要的对象内存赋值
模板类和泛型编程
C++这种语言不像python javascript这种动态语言一样,一个函数可以传入任何类型的参数。C++的模板弥补了这个缺点,在定义函数或者类时可以将参数类型设置为模板,当调用的时候编译器再根据调用的类型去实例化对应的参数类型。优点是更灵活,大大减少代码量。缺点是 模板的定义和声明必须放在同一个文件中 这点和c++常规的定义声明分开编写的原则不同。
磁盘IO的原理
操作系统使用DMA直接将数据从 磁盘 拷贝到 页缓存中,或者将数据写回磁盘,这个过程不需要处理。但是将应用程序地址空间和内核空间之间的拷贝需要 CPU 开销。
序执行 内存屏障 voliate
Linux的字符集
读写锁的实现原理
C++死锁检测
操作系统的内存映射是如何实现的 (共享内存如何实现的)
共享内存能放什么样的数据结构,是用什么数据结构实现的
进程在操作系统的存在方式
epoll原理和类似的中间件之间的区别(select,poll)
消息队列内核是如何实现的
信号量Linux是如何实现的,如何进行进程的通信
信号了解吗,进程的信号通信底层是如何实现的
讲讲I/O模型
PV操作如何实现的
什么是中断
liunx下如何shell下查看cpu内存资源使用情况用什么命令
介绍一下IO多路复用
缓存的管理方式(LRU,LFU)
将一个文件从内存中写入磁盘,设计一种数据结构来加速这个过程
(应该是LSM树)
负载均衡算法
CPU流水线
缓存一致性协议
程序中通过地址读取一个变量的过程
(虚拟地址到物理地址,MMU,CR3,转换的细节,这里问了一个我不懂的名词,说是MMU获取页目录地址的过程)
进程优先级和CPU的任务调度策略(优先级反转)
高并发下同时操作任务队列
Linux
Linux命令 awk,strace,gdb调试相关
Linux常用命令
Linux与Windows最大的区别