目录
- 简述一下吗嗯Rpc调用过程:
- 为什么要进行Protobuf序列化的好处是什么呢?
- 还有哪些序列化的方式
- HTTP协议和 HTTPs有什么区别呢?
- 数字证书的这个东西做什么用呢?
- 长连接和短链接之间的区别
- cookie
- 常见的多线程和异步编程技术
- windows的IOCP模型
- 如何切换用户态和内核
- 操作系统的主要功能都有哪些?
- 调度线程
- 对线程之间的上下文切换是怎么实现
- 为什么协程切换的代价比线程切换低?
- 死锁产生的原因都有哪些呢?
- 如何去避免死锁的产生?
- 假如一个线程就是需要拿着个资源,现在拿不到这个资源或者是他拿到这个资源他一直他由于其他的原因可能有一些同步调用或者系统调用或者网络调用导致一直释放不了这个资源,通过编码的方式怎么能避免这些问题的产生呢?
- 虚拟内存和物理内存间的区别其实为什么要用虚拟内存
- PCB有一个内核区、用户区,栈段大小和具体地址
- gdb查看内存和修改内存的值
- 在rpc这个项目中遇到的最大难题
- 采用了cgi脚本对post进行响应
- 对WebSocket 协议有过了解吗
- Http必须youtcp构成吗?udp可以吗?
- 模板与宏的区别
- c++11新特性
- 指针一般是多少个字节大小
- 析构函数为什么要定义成虚函数吗?
- 线程和进程主要区别是什么呢
- 进程间通讯共享内存的方式是效率为什么最高?
简述一下吗嗯Rpc调用过程:
客户端:
构造makeOrderRequest对象 request,设置controller,回调函数,channel启动channel
1、定义一个自定义格式对象req_protobuf(结构体)
2、将request序列化到req_protobuf->pb_data(pb_data是二进制,是string类型接收)上
3、connect
4、write 自定义格式对象req_protobuf
5、read 自定义格式对象req_protobuf,执行回调
服务端:
1、从buffer中读取read数据流,按照自定义格式(验证数据完整和正确)解析对象req_protobuf(结构体)
2、从request对象取出protobuf的二进制pb_data数据 ,
3、将二进制pb_data数据反序列化为req_msg,
4、调用callmethod获得rsp_msg,
5、将rsp_msg序列化为rsp->pb_data,
6、按自定义的协议封装rsp,塞入buffer中,进行写事件;
为什么要进行Protobuf序列化的好处是什么呢?
跨语言(c++等)、解压缩要快
还有哪些序列化的方式
方式 | 大小 | 格式 | 优点 | 缺点 |
---|---|---|---|---|
XML | 重量级 | ⽂本格式 | - | - |
JSON | 轻量级 | 文本格式 | 可读性很高,跨平台跨语言 | 体积较大,多冗余内容(双引号,花括号) |
Protobuf | 轻量级 | 二进制 | - | - |
msgpack | - | - | - | - |
数据流小(json、xml)、msgpack要压缩kv、protobuffer只压缩v
HTTP协议和 HTTPs有什么区别呢?
http 明文 80端口
https
密文 443端口
过程:
1、tcp三次握手
2、ca数字证书放入非对称密钥获取会话密钥,对称密钥进行加密会话
3、http只有tcp三次握手(快),https是进⾏tcp三次握手+ SSL/TLS 的握⼿过程(慢)
数字证书的这个东西做什么用呢?
使用⾮对称加密交换密钥:
CA的公钥已经放到了游览器中,
用CA公钥取校验服务器的数字证书,
取出服务器的公钥,然后用公钥去加密发送的随机数pre-master
长连接和短链接之间的区别
HTTP/1.0短链接:每发起⼀个请求,都要新建⼀次 TCP 连接(三次握⼿)
HTTP/1.1 ⻓连接:可在同⼀个 TCP 连接⾥⾯,客户端可以发起多个请求,只要第⼀个请求发出去了,不必等其回来,就可以发第
⼆个请求出去,可以减少整体的响应时间。
cookie
解决的是无状态
cookie: Cookie就是由服务器发给客户端的特殊信息,存放在客户端,客户端每次向服务器发送请求的时候都会
带上这些特殊的信息。
这些信息并不是存放在HTTP响应体中的,而是存放于HTTP响应头。
常见的多线程和异步编程技术
现在假设整个城市就只有1个火车,1个售票员,每个乘客咨询售票员后需要思考1分钟再决定买哪趟车的票。
1.异步:在买票的人咨询后,需要思考1分钟,马上靠边站,但不用重新排队,什么时候想清楚可以立马去跟售票员去买票。在该人站在旁边思考的时候,后面的人赶紧上去接着买。这时候队伍是很快的挪动的,没有阻塞,售票员的最大化的效率。
2.多线程:火车站开n个窗口(但还是只有一个人售票),外面同时排n个队,售票员回答咨询者问题后,立马马上去下个窗口,然后继续轮换到下个窗口…哪个窗口的人决定好了,售票员立马过去买给他。这个时候乘客比较简单,但万一那个队伍有人思考半天纠结,后面的人就悲剧了。
windows的IOCP模型
使用线程池处理异步 I/O 请求的一种机制
IOCP模型通过socket绑定完成端口,在socket上投递事件,工作线程在完成端口上轮询接收、处理事件
IOCP充分利用内核对象的调度,只使用少量的几个线程来处理所有网络通信,消除了无谓的线程上下文切换,最大限度地提高了网络通信的性能
独特的异步I/O方式
① socket关联iocp,② 在socket上投递I/O请求,③ 事件完成返回完成通知封包,④ 工作线程在iocp上处理事件。
优秀的线程调度机制
完成端口可以抽象为一个公共消息队列,当用户请求到达时,完成端口把这些请求加入其抽象出的公共消息队列。这一过程与多个工作线程轮询消息队列并从中取出消息加以处理是并发操作。这种方式很好地实现了异步通信和负载均衡,因为它使几个线程“公平地”处理多客户端的I/O,并且线程空闲时会被挂起,不会占用CPU周期。
如何切换用户态和内核
Linux 系统中每个进程都有两个栈,分别是用户栈和内核栈,当应用程序运行在用户态的时候,就会使用用户栈,当应用程序运行在内核态的时候,就会使用内核栈。
用户栈到内核栈:
执行中断指令(int $0x80 指令),中断发生时,CPU 去一个特定的结构(比如 TSS)中,获取该进程的内核栈的地址信息,也就是内核栈的段选择子和栈顶指针(这两个东西是描述内核栈在内存的哪个地址空间),并分别送入 ss 寄存器和 rsp 寄存器,这时候 CPU 就指向了该进程的内核栈的栈顶位置了,这就完成了用户态到内核态的一次栈的切换。(*PS:如果不明白 ss 和 rsp 寄存器是干嘛的,可以看这篇文章:一个学渣对于stack的顿悟(1):从CPU的视角说起)
然后,IP 寄存器(指令指针寄存器)跳入中断服务程序开始执行,中断服务程序会把用户态的所有寄存器压入到内核栈中,如下图,CPU 自动地将用户态栈的段选择子 ss3,和栈顶指针 rsp3 都放到内核态栈里了。这里的数字 3 代表了 CPU 特权级,内核态是 0,用户态是 3。
内核栈到用户栈
当中断结束时,中断服务程序会从内核栈里将 CPU 寄存器的值全部恢复,最后再执行iret指令。
将 ss3/rsp3 都弹出栈,并且将这个值分别送到 ss 和 rsp 寄存器中,这时候 CPU 就指向了该进程的用户栈的栈顶位置了,这样就完成了从内核栈到用户栈的一次切换。
内核栈的 ss0 和 rsp0 也会被保存到前面所说的 CPU 的一个特定的结构(比如 TSS)中,以供下次切换时使用
操作系统的主要功能都有哪些?
1.进程和线程的管理 ——进程线程的状态、控制、同步互斥、通信调度等
2.内存管理——虚拟内存、内存分段、内存分页、段页式内存管理、Linux内存管理
3.调度机制——进程调度、⻚⾯置换、磁盘调度算法
4.网络系统——I/O多路复用:select/pol/epoll、高性能网络模式:Reactor和Proactor
5.文件管理——文件的存储、文件l/0
调度线程
时间片轮转调度、优先级调度、多级反馈队列调度
对线程之间的上下文切换是怎么实现
线程结构体task_struct有个TR寄存器,TR寄存器里有个TSS段选择符(TSS全称是任务状态段),TSS段指向的就是TSS结构体里保存着寄存器的状态,当线程切换时,把当前CPU的寄存器状态存进task_struct里指向的TSS结构体,然后TR换入新的TSS段选择符,新的TSS段选择符指向保存切换后的线程寄存器状态的TSS结构体,再然后从新的TSS结构体中把寄存器状态恢复到当前CPU的寄存器里,从而完成线程切换。
为什么协程切换的代价比线程切换低?
协程切换
1、协程切换只涉及基本的CPU上下文切换。
当前协程的 CPU 寄存器状态保存起来,然后将需要切换进来的协程的 CPU 寄存器状态加载的 CPU 寄存器上就 ok 了。
2、而且完全在用户态进行,
线程切换
1、除了和协程相同基本的 CPU 上下文,还有线程私有的栈和寄存器等;
2、线程的调度只有拥有最高权限的内核空间才可以完成,所以线程的切换涉及到用户空间和内核空间的切换;
死锁产生的原因都有哪些呢?
(互不请等)
互斥条件:进程对所需求的资源具有排他性,若有其他进程请求该资源,请求进程只能等待。
不剥夺条件:进程在所获得的资源未释放前,不能被其他进程强行夺走,只能自己释放。
请求和保持条件:进程当前所拥有的资源在进程请求其他新资源时,由该进程继续占有。
循环等待条件:存在一种进程资源循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。
如何去避免死锁的产生?
破坏互斥条件: 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
破坏请求和保持条件: 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
破坏不剥夺条件: 允许抢占资源。
破坏循环请求等待: 给资源统一编号,进程只能按编号顺序来请求资源。
安全状态、单个资源的银行家算法、多个资源的银行家算法、检查一个状态是否安全的算法:
1、查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。
2、假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
3、重复以上两步,直到所有进程都标记为终止,则状态时安全的。
嗯如何去避免死锁的产生嗯其实嗯可以采用就比如说嗯当当我长时间霍霍的话就是获取不了我需要的资源就就不要产生独占这个情况嘛就就就把所思放掉就相当于我先不要独占的这个资源吗就是打破其实就是打破死锁的四个要条件都都会都会就是不会产生这样的情况我就说是在编程过程中我们怎么通过这种编码的方式能避免有些有些
假如一个线程就是需要拿着个资源,现在拿不到这个资源或者是他拿到这个资源他一直他由于其他的原因可能有一些同步调用或者系统调用或者网络调用导致一直释放不了这个资源,通过编码的方式怎么能避免这些问题的产生呢?
保证上锁的顺序一致。
死锁恢复:利用抢占恢复、利用回滚恢复、通过杀死进程恢复
虚拟内存和物理内存间的区别其实为什么要用虚拟内存
PCB有一个内核区、用户区,栈段大小和具体地址
栈的⼤⼩是固定的,⼀般是 8 MB 。
gdb查看内存和修改内存的值
查看内存
使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:x/
n、f、u是可选的参数。
n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
n/f/u三个参数可以一起使用。
例如:
命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十六进制显示。
在rpc这个项目中遇到的最大难题
为什么需要应用层buffer?
方便数据处理,特别是应用层的包组装和拆解(粘包黏包问题);
方便异步的发送(发送数据直接塞到发送缓冲区里面,等待 epoll 异步去发送);
提高发送效率,多个包合并一起发送;
为什么要自定义协议格式?
既然用了 Protobuf 做序列化,为什么不直接把序列化后的结果直接发送,而要在上面在自定义一些字段?
1、为了方便分割请求:因为 protobuf 后的结果是一串无意义的字节流,你无法区分哪里是开始或者结束。 比如说把两个 Message 对象序列化后的结果排在一起,你甚至无法分开这两个请求。在 TCP 传输是按照字节流传输,没有包的概念,因此应用层就更无法去区分了;
2、为了定位:加上 MsgID 等信息,能帮助我们匹配一次 RPC 的请求和响应,不会串包;
3、错误提升:加上错误信息,能很容易知道 RPC 失败的原因,方便问题定位;
采用了cgi脚本对post进行响应
读取post的数据写入到cgi_input[1](pipe(cgi_input)创建匿名管道),
执行cgi脚本,
读取cgi脚本返回数据cgi_output[0],发送给游览器。
输入:
cgi输出:
对WebSocket 协议有过了解吗
为什么要使用WebSocket?
WebSocket 是客户端和服务器之间双向数据传输的标准协议。
WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。
WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求:
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
该请求和普通的HTTP请求有几点不同:
GET请求的地址不是类似/path/,而是以ws://开头的地址;
请求头Upgrade: websocket和Connection: Upgrade表示这个连接将要被转换为WebSocket连接;
Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
Sec-WebSocket-Version指定了WebSocket的协议版本。
为什么传统的HTTP协议不能做到WebSocket实现的功能?
这是因为HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。换句话说,浏览器不主动请求,服务器是没法主动发数据给浏览器的。这样一来,要在浏览器中搞一个实时聊天,在线炒股(不鼓励),或者在线多人游戏的话就没法实现了,只能借助Flash这些插件。
Http必须youtcp构成吗?udp可以吗?
HTTP/3 协议的 QUIC 协议:基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。
1、⽆队头阻塞,QUIC 连接上的多个 Stream 之间并没有依赖,都是独⽴的,也不会有底层协议限制,某个流发⽣丢包了,只会影响该流,其他流不受影响;
2、建⽴连接速度快,因为 QUIC 内部包含 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建⽴连接与 TLS 密钥协商,甚⾄在第⼆次连接的时候,应⽤数据包可以和 QUIC 握⼿信息(连接信息 + TLS 信息)⼀起发送,达到0-RTT 的效果。
3、连接迁移,QUIC 协议没有⽤四元组的⽅式来“绑定”连接,⽽是通过「连接 ID 」来标记通信的两个端点,客户端和服务器可以各⾃选择⼀组 ID 来标记⾃⼰,因此即使移动设备的⽹络变化后,导致 IP 地址变化了,只要仍保有上下⽂信息(⽐如连接 ID、TLS 密钥等),就可以“⽆缝”地复⽤原连接,消除重连的成本;
模板与宏的区别
1.宏是在预处理阶段处理,模板是在编译阶段处理
2.宏不会进行类型检查,只会单纯的进行文本替换,模板会进行类型检查。比如下面代码模板就会出错,而宏不会
3.宏直接就可以产生代码,而编译器遇到模板定义时,并不产生代码,只有当模板实例化后时才会产生代码。
c++11新特性
智能指针shared_ptr、unique_ptr、weak_ptr
引入了 auto 和 decltype 这两个关键字实现了类型推导
基于范围的 for 循环for(auto& i : res){}
类和结构体的中初始化列表
Lambda 表达式(匿名函数)
右值引用和move语义
指针一般是多少个字节大小
32位系统默认指针大小为4个字节(8位为一个字节),因为32位系统默认的内存寻址空间是4G,所以指针大小为4个字节可以完成对4G空间的寻址。2^32约为4个G;
64位系统默认指针大小为8个字节,理论上寻址空间可达到1800万个TB,指针大小为8个字节可完成对其的寻址。
析构函数为什么要定义成虚函数吗?
防止内存泄漏,当父类指针指向子类对象的时候,释放父类指针,如果此时析构函数不是虚函数,那么将只会调用父类的析构函数,不会调用子类的析构函数,造成内存泄漏问题。
线程和进程主要区别是什么呢
进程 | 线程 | 协程 | |
---|---|---|---|
资源分配和拥有的基本单位 | 程序执行的基本单位 | 线程内部调度的基本单位 | |
切换情况 | 进程CPU环境(栈、寄存器、页表和文件句柄等)的保存以及新调度的进程CPU环境的设置 | 保存和设置程序计数器、少量寄存器和栈的内容 | 先将寄存器上下文和栈保存,等切换回来的时候再进行恢复 |
切换者 | 操作系统 | 操作系统 | 用户 |
切换过程 | 用户态->内核态->用户态 | 用户态->内核态->用户态 | 用户态 |
调用栈 | 内核栈 | 内核栈 | 用户栈 |
拥有资源 | CPU资源、内存资源、文件资源和句柄等 | 程序计数器、寄存器、栈和状态字 | 拥有自己的寄存器上下文和栈 |
拥有自己的寄存器上下文和栈 | 不同进程之间切换实现并发,各自占有CPU实现并行 | 一个进程内部的多个线程并发执行 | 同一时间只能执行一个协程,而其他协程处于休眠状态,适合对任务进行分时处理 |
系统开销 | 切换虚拟地址空间,切换内核栈和硬件上下文,CPU高速缓存失效、页表切换,开销很大 | 切换时只需保存和设置少量寄存器内容,因此开销很小 | 直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快 |
进程间通讯共享内存的方式是效率为什么最高?
因为共享内存是直接将一块物理内存映射(mmap)到多个进程的虚拟地址空间中,多个进程可以直接读写共享内存中的数据,不需要像管道和消息队列那样进行数据的复制和传送,不存在多次用户态和内核态之间的切换,因此共享内存的传输效率最高。不过,由于共享内存的使用需要进程间进行同步与互斥,因此需要使用信号量等机制来实现进程之间的互斥与同步。