Skip to content

字节C++客户端面经

1、自我介绍

2、是否愿意转语言,是否只愿意搞后端?

3、选一个项目来详细谈谈?

4、HTTP和HTTPS有什么区别?

  1. 安全性:
    • HTTP: 是一种明文传输协议,数据在传输过程中是未加密的,容易被中间人截获和窃听。因此,对于敏感信息(如登录凭证、个人数据等)的传输,HTTP 不安全。
    • HTTPS: 是在HTTP的基础上通过使用SSL/TLS协议添加了加密层的安全协议。通过使用公钥和私钥加密和解密数据,HTTPS保护了数据在传输过程中的安全性,有效防止了中间人攻击。
  2. 加密协议:
    • HTTP: 不提供数据加密和身份验证机制。
    • HTTPS: 使用SSL(Secure Sockets Layer)或其升级版TLS(Transport Layer Security)协议,通过加密算法保护数据的隐私性和完整性。
  3. 端口:
    • HTTP: 默认使用80端口。
    • HTTPS: 默认使用443端口。
  4. URL协议标识:
    • HTTP: URL以"http://"开头。
    • HTTPS: URL以"https://"开头。
  5. 证书:
    • HTTP: 不需要使用数字证书。
    • HTTPS: 需要服务器端使用数字证书,由可信任的证书颁发机构(CA)签发,用于验证服务器的身份。
  6. 性能:
    • HTTP: 由于不涉及加密解密等过程,相对于HTTPS,性能上更高效。
    • HTTPS: 加密和解密过程会引入一定的计算和网络开销,因此相对于HTTP,性能上略低一些。
  7. 搜索引擎排名:
    • HTTP: 搜索引擎可能会对使用HTTP的网站进行降权处理,因为它不安全。
    • HTTPS: 谷歌等搜索引擎更倾向于为使用HTTPS的网站提高排名,因为它提供更安全的用户体验。

5、谈一下HTTPS加密的具体过程?

  1. 客户端向服务端发起第一次握手请求,告诉服务端客户端所支持的SSL的指定版本、加密算法及密钥长度等信息。
  2. 服务端将自己的公钥发给数字证书认证机构,数字证书认证机构利用自己的私钥对服务器的公钥进行数字签名,并给服务器颁发公钥证书。
  3. 服务端将证书发给客户端。
  4. 客服端利用数字认证机构的公钥,向数字证书认证机构验证公钥证书上的数字签名,确认服务器公开密钥的真实性。
  5. 客户端使用服务端的公开密钥加密自己生成的对称密钥,发给服务端。
  6. 服务端收到后利用私钥解密信息,获得客户端发来的对称密钥。
  7. 通信双方可用对称密钥来加密解密信息。

6、谈一下在浏览器输入一段URL后获取资源的全过程?

  1. URL 解析:浏览器首先会对输入的 URL 进行解析,提取出其中的协议、主机名、端口号、路径等信息。
  2. DNS 解析:如果输入的 URL 中包含了主机名,浏览器会向 DNS 服务器发送 DNS 请求,将主机名解析为 IP 地址。
  3. 建立 TCP 连接:浏览器根据 URL 中的协议确定使用的传输协议,通常是 HTTP 或 HTTPS。浏览器会向服务器发起 TCP 连接请求,建立与服务器的连接。
  4. 发起 HTTP 请求:一旦 TCP 连接建立成功,浏览器就会构建一个 HTTP 请求报文,包括请求的方法(GET、POST 等)、请求头、请求体等信息,然后将请求报文发送给服务器。
  5. 服务器处理请求:服务器收到浏览器发送的 HTTP 请求后,根据请求的内容进行相应的处理,可能是查询数据库、读取文件等操作。
  6. 服务器返回响应:服务器处理完请求后,会生成一个 HTTP 响应报文,包括状态码、响应头、响应体等信息,然后将响应报文发送给浏览器。
  7. 浏览器渲染页面:浏览器收到服务器返回的响应后,会根据响应的内容进行页面的渲染,包括解析 HTML、加载 CSS 和 JavaScript、渲染页面等操作。
  8. 关闭 TCP 连接:页面渲染完成后,浏览器会关闭与服务器的 TCP 连接。

7、了解过浏览器的渲染没有?

浏览器的渲染过程是将从服务器获取的 HTML、CSS 和 JavaScript 文件解析、布局和绘制成用户可见的页面的过程。

这个过程可以分为以下几个步骤:

  1. HTML 解析:浏览器首先会将获取到的 HTML 文件解析成 DOM(文档对象模型),DOM 是一个树状结构,表示了整个 HTML 文档的结构和内容。
  2. CSS 解析:浏览器会解析 CSS 文件,并将其转换成 CSSOM(CSS 对象模型)。CSSOM 与 DOM 结构相似,表示了 HTML 文档中所有样式的信息。
  3. 合并 DOM 和 CSSOM:浏览器将 DOM 和 CSSOM 合并,形成一个渲染树(Render Tree)。渲染树只包括页面中需要显示的元素和样式信息,不包括那些不需要显示的元素,比如 <head> 中的内容。
  4. 布局(Layout):根据渲染树中的元素计算它们在页面中的位置和大小,这个过程称为布局或回流(reflow)。
  5. 绘制(Paint):浏览器根据计算好的布局信息将页面上的元素绘制成像素,这个过程称为绘制。
  6. 合成(Composite):浏览器将绘制好的像素按照合适的顺序合成到屏幕上,这个过程称为合成。

8、讲一下TCP和UDP的区别?

  1. 连接性:
    • TCP:面向连接的协议,通信前需要建立连接,然后进行数据传输,最后释放连接。提供可靠的、面向连接的通信,确保数据的完整性和顺序性。
    • UDP:无连接的协议,通信时不需要建立连接,直接进行数据传输。提供不可靠的、无连接的通信,不保证数据的完整性和顺序性。
  2. 可靠性:
    • TCP:提供可靠的通信,通过序号、应答和重传等机制,确保数据的可靠性,适用于对数据可靠性要求较高的场景。
    • UDP:不提供可靠性保证,数据可能会丢失或乱序,适用于对实时性要求较高的场景。
  3. 数据流:
    • TCP:提供面向流的通信,数据传输是连续的字节流,保证数据的有序性。
    • UDP:提供面向报文的通信,数据以数据报的形式传输,每个数据报相对独立,不保证有序性。
  4. 通信开销:
    • TCP:通信开销较大,因为需要建立连接、维护状态信息、进行可靠性保证。
    • UDP:通信开销较小,因为无连接、不维护状态信息,适用于对通信延迟要求较低的场景。
  5. 适用场景:
    • TCP:适用于需要可靠性、有序性的应用,如文件传输、网页访问、邮件传输等。
    • UDP:适用于实时性要求高、对可靠性要求不高的应用,如音频、视频流传输、在线游戏等。
  6. 头部开销:
    • TCP:头部开销相对较大,包含序号、确认号、窗口大小等字段,用于维护连接状态。
    • UDP:头部开销较小,只包含源端口、目标端口、长度和校验和等基本字段。

9、讲一下三次握手的具体过程?

在建立连接之前,Client处于CLOSED状态,而Server处于LISTEN的状态。

  1. 第一次握手:客户端主动给服务端发送一个SYN报文,并携带自己的初始化序列号一起发送给服务端。此时客户端处于一个SYN_SEND的状态。
  2. 第二次握手:服务端收到客户端发来的SYN报文之后,就会以自己的SYN报文作为应答,然后将自己的初始化序列号发送给客户端,并且会将客户端的初始化序列号+1作为自己的ACK值发送给客户端,以表示自己已经收到了客户端的SYN报文。此时服务端处于一个SYN_RECV的状态。
  3. 第三次握手:客户端收到服务端发来的SYN报文之后,会把服务端的初始化序列号+1作为ACK值发送给服务端,用来表示自己已经收到了服务端发来的SYN报文。此时客户端处于一个ESTABLISHED的状态。

10、为什么是四次挥手?三次行不行?

四次挥手:

  1. 第一次挥手(FIN=1,seq=x):客户端发送一个 FIN 报文给服务器,表示客户端不再发送数据了,但仍然可以接收数据。
  2. 第二次挥手(ACK=1,ack=x+1):服务器收到客户端的 FIN 报文后,会发送一个确认报文给客户端,表示已经收到了客户端的关闭请求,并且确认了客户端发送的所有数据。
  3. 第三次挥手(FIN=1,seq=y):服务器自己也准备关闭连接时,会发送一个 FIN 报文给客户端,表示服务器也不再发送数据了。
  4. 第四次挥手(ACK=1,ack=y+1):客户端收到服务器的 FIN 报文后,会发送一个确认报文给服务器,表示已经收到了服务器的关闭请求,并且确认了服务器发送的所有数据。

为什么三次不行?

  1. 全双工通信:TCP 是一种全双工通信协议,允许在同一时间里双方都可以发送和接收数据。因此,在关闭连接时,需要分别关闭双方的发送通道和接收通道,这就需要四次挥手来完成。
  2. 确认关闭:在四次挥手中,每一次挥手都需要得到对方的确认,这样可以确保双方都知道对方已经关闭了发送通道。如果只有三次挥手,就无法保证对方已经收到关闭请求并进行了相应的处理。
  3. 数据传输完整性:在四次挥手中,最后一次挥手是为了确保双方都已经完成了数据的传输和接收,可以安全地关闭连接。如果只有三次挥手,无法保证双方都已经完成了数据的传输,可能会导致数据丢失或不完整。

11、进程和线程的区别?

  1. 定义:
    • 进程(Process):是指正在运行的一个程序。每个进程都有自己独立的内存空间和系统资源,是操作系统进行资源分配和调度的基本单位。
    • 线程(Thread):是进程中的一个执行单元,一个进程可以包含多个线程。线程共享进程的资源,包括内存空间和文件等,但拥有独立的栈空间和寄存器集合。
  2. 资源分配:
    • 进程:拥有独立的地址空间,需要操作系统分配独立的内存空间和系统资源。
    • 线程:共享进程的地址空间和系统资源,创建和销毁线程的开销通常比创建和销毁进程要小。
  3. 通信和同步:
    • 进程:进程之间的通信需要使用进程间通信(IPC)机制,如管道、信号量、消息队列等。
    • 线程:线程之间共享同一进程的地址空间,可以直接读写共享的变量,因此通信更加方便。
  4. 调度和切换:
    • 进程:进程切换的开销比较大,因为需要切换地址空间和系统资源。
    • 线程:线程切换的开销比较小,因为线程共享进程的资源,只需要切换寄存器和栈即可。
  5. 并发性:
    • 进程:进程是独立的执行单元,多个进程之间并发执行。
    • 线程:线程是进程的执行单元,多个线程共享进程的资源,可以并发执行。

12、用什么方式实现线程同步?

  1. 互斥锁(Mutex):互斥锁是最常见的一种线程同步机制,它保证在同一时间内只有一个线程可以访问共享资源。当一个线程获得了互斥锁后,其他线程必须等待该线程释放锁才能继续执行。
  2. 信号量(Semaphore):信号量是一种更加灵活的同步机制,它允许多个线程同时访问共享资源,但是通过控制信号量的数值来限制可以访问的线程数量。
  3. 条件变量(Condition Variable):条件变量用于在多线程之间传递条件信息,它通常和互斥锁一起使用,用于线程的等待和唤醒。
  4. 读写锁(Reader-Writer Lock):读写锁允许多个线程同时读取共享资源,但是只允许一个线程写入共享资源。这种锁适用于读操作远远多于写操作的场景。
  5. 自旋锁(Spin Lock):自旋锁是一种忙等待的锁,在获取锁失败时不会立即放弃CPU,而是会一直循环检查锁的状态,直到获取锁为止。
  6. 屏障(Barrier):屏障用于多个线程之间的同步点,它可以让多个线程在一个点上等待,直到所有线程都到达了这个点才继续执行。

13、使用过哪些进程间通信的方式?

  1. 管道(Pipes): 管道是一种半双工通信方式,主要用于具有亲缘关系的进程之间的通信。管道是一种线性数据流,数据只能单向流动。它有两种类型:
    • 无名管道(Anonymous Pipes): 通常用于父子进程之间的通信。创建管道使用 pipe() 系统调用。
    • 命名管道(Named Pipes): 用于无关联的进程之间的通信,它们以文件系统中的命名管道文件形式存在。
  2. 消息队列(Message Queues): 消息队列允许进程通过消息进行异步通信。消息队列允许多个进程通过将消息发送到队列,然后其他进程从队列中接收消息来进行通信。消息队列通常有操作系统提供的 API 来管理消息的发送和接收。
  3. 共享内存(Shared Memory): 共享内存是一种高效的通信方式,允许多个进程共享相同的物理内存区域。这使得数据在进程之间的传输非常快速,因为它们可以直接读写相同的内存。然而,共享内存需要进行同步以避免数据竞争。
  4. 信号(Signals): 信号是异步通信的一种方式,用于通知进程某些事件的发生,如错误或异常。每个信号都有一个数字标识符,当事件发生时,进程可以注册信号处理程序来处理信号。
  5. 套接字(Sockets): 套接字是一种用于网络通信的通用通信机制,但也可以在同一台计算机上的不同进程之间使用。套接字提供了面向流和面向数据报的通信方式,允许进程通过网络套接字进行通信。
  6. 文件(File): 进程可以通过读写文件来实现通信。一个进程可以将数据写入文件,而另一个进程则可以读取该文件的内容。这种方式不够高效,但是可以应用在不同进程之间的通信需求较少的情况下。
  7. 信号量(Semaphores): 信号量是一种用于控制多个进程对共享资源的访问的同步机制。信号量可以用于避免竞争条件,确保一次只有一个进程可以访问共享资源。
  8. 共享文件映射(Memory-Mapped Files): 共享文件映射允许进程将文件映射到它们的地址空间中,以便多个进程可以访问相同的文件数据。这在共享大量数据时非常有用。

14、手撕单例模式?

  1. 懒汉模式

懒汉模式是在首次请求单例实例时才进行初始化,以延迟对象的创建。这种方式在需要时才创建单例,节省了资源,但需要考虑线程安全。

C++
class LazySingleton {
public:
    // 获取单例实例的静态成员函数
    static LazySingleton& getInstance() {
        // 使用静态局部变量来延迟初始化
        static LazySingleton instance;
        return instance;
    }

    // 其他成员函数和数据成员
private:
    LazySingleton() {
        // 构造函数的实现
    }

    // 禁用拷贝构造函数和赋值操作符
    LazySingleton(const LazySingleton&) = delete;
    LazySingleton& operator=(const LazySingleton&) = delete;
};

懒汉模式的优点是在需要时才创建单例,但需要注意多线程环境下的线程安全问题。

  1. 饿汉模式

饿汉模式是在类加载时就创建单例实例,无论是否需要。这种方式在程序启动时就创建单例,无需考虑线程安全问题。

C++
class EagerSingleton {
public:
    // 获取单例实例的静态成员函数
    static EagerSingleton& getInstance() {
        return instance;
    }

    // 其他成员函数和数据成员
private:
    EagerSingleton() {
        // 构造函数的实现
    }

    // 禁用拷贝构造函数和赋值操作符
    EagerSingleton(const EagerSingleton&) = delete;
    EagerSingleton& operator=(const EagerSingleton&) = delete;

    // 在类加载时创建单例对象
    static EagerSingleton instance;
};

// 在类外部初始化静态成员变量
EagerSingleton EagerSingleton::instance;

饿汉模式的优点是不需要考虑线程安全问题,但缺点是在程序启动时创建单例可能占用资源,甚至在不需要单例时浪费了资源。

15、反问环节?

一面已过,后天二面。