大家好,我是小林。
互联网岗位里,可以说后端开发是最卷,投的人最多的,但是隔壁的客户端开发投的就很少,有后端同学会被客户端部门捞起来去面试,所以如果卷不过后端,又想进大厂的同学,可以尝试投客户端开发,面试相对没那么卷,薪资待遇跟后端也是一样的。
今天分享一位快手客户端一二三面的面经,同学的技术栈是C++后端,但是面试不会问后端内容了,主要就围绕Cpp+操作系统+网络协议+算法来问,相比后端所需要准备的内容就少了 mysql 、redis、消息队列等后端组件,但是计算基础的深度会问的比较深一点。
由其第三面,直接给两个场景代码题手写出来,还是有点难度。。
快手一面
拥塞控制介绍一下
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大....
所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。
于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。
为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。
拥塞控制主要是四个算法:
慢启动
TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?
慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。
这里假定拥塞窗口cwnd和发送窗口swnd相等,下面举个栗子:
慢启动算法的变化过程如下图:
可以看出慢启动算法,发包的个数是指数性的增长。
那慢启动涨到什么时候是个头呢?
有一个叫慢启动门限ssthresh(slow start threshold)状态变量。
拥塞避免
当拥塞窗口cwnd「超过」慢启动门限ssthresh就会进入拥塞避免算法。
一般来说ssthresh的大小是65535字节。
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。
接上前面的慢启动的栗子,现假定ssthresh为8:
拥塞避免算法的变化过程如下图:
所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。
就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。
当触发了重传机制,也就进入了「拥塞发生算法」。
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
当发生了「超时重传」,则就会使用拥塞发生算法。
这个时候,ssthresh 和 cwnd 的值会发生变化:
拥塞发生算法的变化如下图:
接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。
还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则ssthresh和cwnd变化如下:
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像RTO超时那么强烈。
正如前面所说,进入快速恢复之前,cwnd和ssthresh已被更新了:
然后,进入快速恢复算法如下:
快速恢复算法的变化过程如下图:
也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。
http/https 的区别?
Https四次加密过程?
基于 RSA 算法的 TLS 握手过程比较容易理解,所以这里先用这个给大家展示 TLS 握手过程,如下图:
TLS 协议建立的详细流程:
1. ClientHello
首先,由客户端向服务器发起加密通信请求,也就是ClientHello请求。
在这一步,客户端主要向服务器发送以下信息:
(1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。
(2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。
(3)客户端支持的密码套件列表,如 RSA 加密算法。
2. SeverHello
服务器收到客户端请求后,向客户端发出响应,也就是SeverHello。服务器回应的内容有如下内容:
(1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。
(2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。
(3)确认的密码套件列表,如 RSA 加密算法。
(4)服务器的数字证书。
3.客户端回应
客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。
如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:
(1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。
(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。
服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
4. 服务器的最后回应
服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。
然后,向客户端发送最后的信息:
(1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。
get和post的区别?
进程线程有什么区别?
线程有哪些资源,栈中保存什么?
线程在操作系统中有一些特定的资源,包括:
栈中,主要保存了以下内容:
函数调用的时候,压栈怎么样的
函数调用时,会进行以下压栈操作:
静态链接库和动态链接库有什么区别?
动态链接库怎么装载到内存的?
通过用mmap把该库直接映射到各个进程的地址空间中,尽管每个进程都认为自己地址空间中加载了该库,但实际上该库在内存中只有一份,mmap就这样很神奇和动态链接库联动起来了。
虚拟内存介绍一下
中断是什么
在操作系统中,中断是指由硬件或软件触发的一种事件,它会暂时中止当前正在执行的程序,并转而执行与中断相关的处理程序。中断可以是内部的,如除法错误或越界访问,也可以是外部的,如硬件设备的输入/输出请求或时钟中断。
中断的作用是提供一种机制来处理和响应各种事件,例如处理硬件设备的输入/输出请求、处理异常情况、进行时钟调度等。当发生中断时,操作系统会根据中断类型确定要执行的中断处理程序,并在处理完中断后恢复原来的程序执行。
中断处理程序可以执行一系列操作,如保存当前进程的上下文、处理中断事件、与设备进行通信、调度其他进程等。通过使用中断,操作系统可以实现多任务处理、实时响应外部事件、提高系统的可靠性和稳定性。
操作系统的锁,自己实现一个读写锁
读者只会读取数据,不会修改数据,而写者即可以读也可以修改数据。
读者-写者的问题描述:
接下来,提出几个解决方案来分析分析。
方案一
使用信号量的方式来尝试解决:
接下来看看代码的实现:
上面的这种实现,是读者优先的策略,因为只要有读者正在读的状态,后来的读者都可以直接进入,如果读者持续不断进入,则写者会处于饥饿状态。
方案二
那既然有读者优先策略,自然也有写者优先策略:
在方案一的基础上新增如下变量:
具体实现如下代码:
注意,这里rMutex的作用,开始有多个读者读数据,它们全部进入读者队列,此时来了一个写者,执行了P(rMutex)之后,后续的读者由于阻塞在rMutex上,都不能再进入读者队列,而写者到来,则可以全部进入写者队列,因此保证了写者优先。
同时,第一个写者执行了P(rMutex)之后,也不能马上开始写,必须等到所有进入读者队列的读者都执行完读操作,通过V(wDataMutex)唤醒写者的写操作。
方案三
既然读者优先策略和写者优先策略都会造成饥饿的现象,那么我们就来实现一下公平策略。
公平策略:
具体代码实现:
看完代码不知你是否有这样的疑问,为什么加了一个信号量flag,就实现了公平竞争?
对比方案一的读者优先策略,可以发现,读者优先中只要后续有读者到达,读者就可以进入读者队列, 而写者必须等待,直到没有读者到达。
没有读者到达会导致读者队列为空,即rCount==0,此时写者才可以进入临界区执行写操作。
而这里flag的作用就是阻止读者的这种特殊权限(特殊权限是只要读者到达,就可以进入读者队列)。
比如:开始来了一些读者读数据,它们全部进入读者队列,此时来了一个写者,执行P(falg)操作,使得后续到来的读者都阻塞在flag上,不能进入读者队列,这会使得读者队列逐渐为空,即rCount减为 0。
这个写者也不能立马开始写(因为此时读者队列不为空),会阻塞在信号量wDataMutex上,读者队列中的读者全部读取结束后,最后一个读者进程执行V(wDataMutex),唤醒刚才的写者,写者则继续开始进行写操作。
算法题
快手二面
指针和引用值传递的概念
int double string强制转化为什么会精度丢失?
void*是什么?
void*是一种通用的指针类型,被称为"无类型指针"。它可以用来表示指向任何类型的指针,因为void*指针没有指定特定的数据类型。
由于void*是无类型的,它不能直接进行解引用操作,也不能进行指针运算。在使用void*指针时,需要将其转换为具体的指针类型才能进行操作。
void*指针常用于需要在不同类型之间进行通用操作的情况,例如在函数中传递任意类型的指针参数或在动态内存分配中使用。
malloc的参数列表 void*怎么转化为int*的?
可以使用类型转换将void*指针转化为int*指针。以下是将void*指针转化为int*指针的示例代码:
void* voidPtr = malloc(sizeof(int));// 分配内存并返回void*指针int* intPtr = (int*)voidPtr;// 将void*指针转化为int*指针// 现在可以通过intPtr指针访问int类型的数据*intPtr = 42;
在上述示例中,使用malloc函数分配了存储一个int类型数据所需的内存,并返回了一个void*指针。然后,通过将void*指针转换为int*指针,将其赋值给intPtr变量。现在,可以通过intPtr指针访问和操作int类型的数据。
算法题
快手三面
三面一个八股没问,直接来两个场景代码题,比较注重实操,太难啦。
本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://www.jmbhsh.com/shumazixun/33780.html