【CPP面经】大厂CPP后台开发面试经历

server/2025/2/27 1:59:14/

文章目录

      • 1. HTTP 和 HTTPS 的差别
      • 2. 加密方式
      • 3. TCP 挥手过程(四次挥手)
      • 4. TIME_WAIT 作用
      • 5. HTTP/1.0 1.1 2.0 3.0
      • 6. 偏特化(Partial Specialization)
      • 7. 指针常量(Pointer Constant)
      • 8. malloc 函数的原理
      • 9. Linux 物理页大小
      • 10. 物理页仅用 1K 时的管理
      • 11. Linux 查看 CPU 状态/内存的命令
      • 12. GDB 断点与堆栈命令
      • 13. TCP 拥塞控制原理
      • 14. 拥塞避免(Congestion Avoidance)
      • 15. TIME_WAIT 状态的目的(复述)
      • 16. 用户级内存池实现[给你一块内存,怎么管理起来]
      • 17. Buffer 和 Cache 的区别
      • 18. 字节序(Endianness)
      • 19. 索引的目的
      • 20. MySQL 索引原理(InnoDB vs MyISAM)
      • 21. 多线程使用场景
      • 22. 线程池参数设置
      • 23. 线程安全的单例模式
      • 24. Docker 与 Kubernetes(k8s)基础
      • 25. Map 类型选择
      • 26. HashTable 线程安全实现
      • 27. 索引失效场景
      • 28. 慢查询优化步骤
      • 29. 优化后仍慢的解决方案
      • 30. 行锁 vs 表锁
      • 分库分表落地示例(补充)
      • 31. 乐观锁与悲观锁的实现
      • 32. Redis 使用场景
      • 33. Redis 作为注册中心的优劣
      • 34. Redis 分布式锁的时钟问题
      • 35. 时钟问题解决方案
      • 36. Redis 持久化方案
      • 37. 进程通信(IPC)方式
      • 38. RPC 与常用框架
      • 39. 同步调用 vs 异步调用
      • 40. Linux 常用命令
      • 分库分表补充(应对面试官质疑)
      • 1. **查询HTTPS是否数据安全的命令及注意事项**
      • 2. **调用HTTPS服务的方法(C++后端)**
      • 3. **消息队列使用经验**
      • 4. **消息不丢失与顺序性保证**
      • 5. **一致性哈希算法**
      • 6. **Redis与MySQL一致性**
      • 7. **本地缓存与Redis协调**
      • 8. **技术敏感度保持**
      • 9. **为何离开百度寻找新实习**
      • **1. 补充项目测试结果:分析极限情况下的性能瓶颈**
      • **2. Vector如何主动释放内存空间**
      • **3. 稳定排序算法**
      • **4. TCP三次握手的数据携带问题**
      • **5. select 和 epoll 的适用场景**
  • 模拟
      • **1. C++ HTTP服务器线程模型与并发能力**
      • **2. 服务崩溃原因分析**
      • **3. 大批量请求下的服务端状态**
      • **4. CGI机制详解**
      • **FastCGI**
      • **FastCGI 是什么?**
      • **FastCGI 的核心特性**
      • **FastCGI 工作原理**
      • **FastCGI 的优势**
      • **典型应用场景**
      • **FastCGI 的局限性**
      • **常见问题与解决方案**
      • **总结**
      • HTTP/2 与 gRPC
      • **HTTP/2 与 gRPC 的核心对比**
      • **HTTP/2 的核心特性**
      • **gRPC 的核心特性**
      • **与 FastCGI 的对比**
      • **如何选择?**
      • **示例:gRPC 服务端与客户端(Python)**
      • **总结**
      • **5. 进程创建与通信**
      • **6. Cookie vs Session**
      • **7. 线程池的作用与优势**
      • **8. Vector内存释放**
      • **9. 红黑树 vs 哈希表**
      • **10. 右值引用解决的问题**
      • **11. 稳定排序算法**
      • **12. TCP三次握手的必要性**
      • **13. TCP握手阶段的数据携带**
      • **14. TCP数据安全性**
      • **15. 网线拔插对TCP连接的影响**
    • **1. 物理层 & 数据链路层的影响**
    • **2. TCP 层的行为**
      • **(1) 服务器仍然认为连接是“活跃的”**
      • **(2) 服务器继续发送数据**
      • **(3) TCP 超时重传机制生效**
    • **3. 可能出现的异常**
      • **(1) 客户端重新插回网线**
      • **(2) 客户端 IP 地址变化**
      • **(3) TCP Keepalive**
    • **4. 结论**
      • **短时间内拔插网线**
      • **长时间断开**
      • **影响总结**
      • **附:TCP 连接断开的三种情况**
    • **5. 代码实验(模拟拔网线)**
      • **模拟拔网线**
      • **模拟插回网线**
    • **总结**
      • **16. HTTP传输层协议**
      • **HTTP 在传输层使用的协议**
        • **为什么 HTTP 默认使用 TCP?**
      • **HTTP 可以使用 UDP 吗?**
        • **HTTP + UDP 的情况**
      • **结论**
      • **17. 优雅关闭Socket**
      • **1. shutdown() + close()**
      • **2. 使用 SO_LINGER 控制关闭行为**
      • **3. 正常 TCP 四次挥手**
    • **1. TCP 连接的正常关闭**
    • **2. `close()` VS `shutdown()`**
      • **(1)close(fd)**
      • **(2)shutdown(fd, how)**
    • **3. 优雅关闭的实现**
      • **(1)客户端优雅关闭**
      • **(2)服务器优雅关闭**
    • **4. 总结**
      • **18. 数据库事务四大特性**
      • 1. **原子性(Atomicity)**
      • 2. **一致性(Consistency)**
      • 3. **隔离性(Isolation)**
      • 4. **持久性(Durability)**
    • **持久性保证不会丢失数据吗?**
      • **如何尽量避免数据丢失?**
    • **总结**
      • **19. epoll vs select**
      • **核心原因**
      • **关键优势分析**
      • **适用场景总结**
      • **实际测试数据**
      • **结论**

1. HTTP 和 HTTPS 的差别

  1. 安全性:明文传输:ssl+tls
  2. 端口号:80:443
  3. CA机构颁发证书
  4. 性能开销

2. 加密方式

  • 非对称加密(如 RSA、ECC):
    用于密钥交换。客户端用服务器公钥加密对称密钥,服务器用私钥解密。
  • 对称加密(如 AES、ChaCha20):
    建立安全连接后,使用协商的对称密钥高效加密数据。
  • 完整性校验
    通过 HMAC 或 AEAD(如 AES-GCM)防止数据篡改。

3. TCP 挥手过程(四次挥手)

  1. 主动方发送 FIN:进入 FIN_WAIT_1,表示不再发送数据。
  2. 被动方回复 ACK:进入 CLOSE_WAIT,通知应用层处理剩余数据。
  3. 被动方发送 FIN:进入 LAST_ACK,表示数据已发送完毕。
  4. 主动方回复 ACK:进入 TIME_WAIT,等待 2MSL(确保被动方收到 ACK),最后关闭连接。

4. TIME_WAIT 作用

  • 确保可靠终止
    若最后一次 ACK 丢失,被动方会重传 FIN,主动方需重新响应。
  • 避免旧连接干扰
    等待 2MSL(报文最大生存时间),确保网络中旧连接的报文失效,防止与新连接冲突。

5. HTTP/1.0 1.1 2.0 3.0

  • 1.0:短连接 非标准字段强制开启长连接
  • 1.1:长连接+管道即一个TCP链接同时发送多个请求
  • 2.0:二进制分帧提供多路复用解决对头阻塞+相同头部压缩+服务端推送缓存给客户端
  • 3.0:二进制分帧提供多路复用解决对头阻塞+基于UDP 实现的 QUIC 协议+
    • 0-RTT 连接:复用先前会话密钥,快速建立连接。
    • 连接迁移:网络切换(如 Wi-Fi 到 4G)时无需重建连接。
    • 内置加密:默认整合 TLS 1.3,安全性更高。

6. 偏特化(Partial Specialization)

  • 概念
    对模板的部分参数特化,而非全部。例如:

    template <typename T, typename U> class A {};      // 通用模板
    template <typename T> class A<T, int> {};          // 偏特化:第二个参数为 int
    
  • 应用场景

    • 优化特定类型(如 vector<T*> 特化以减少内存占用)。
    • 类型萃取(如 is_pointer<T> 判断是否为指针)。

7. 指针常量(Pointer Constant)

  • 定义
    指针本身是常量,不可修改指向的地址,但可修改指向的值。

    int a = 1, b = 2;
    int *const ptr = &a;  // ptr 不能指向其他地址(如 ptr = &b 非法)
    *ptr = 3;             // 允许修改 a 的值
    

8. malloc 函数的原理

  • 底层机制
    • 小内存:通过 brk 调整堆顶,从堆区分配,维护空闲块链表(如 glibc 的 ptmalloc,使用 fast bins、small bins 等)。
    • 大内存:直接通过 mmap 映射匿名内存页,避免碎片。
  • 碎片处理
    合并相邻空闲块(coalescing),分割块以满足请求大小。

9. Linux 物理页大小

  • 默认大小
    多数架构(如 x86_64)物理页为 4KB,大页(HugePage)可配置为 2MB 或 1GB。
  • 管理策略
    • 伙伴系统:管理连续物理页,解决外部碎片。
    • slab 分配器:将页拆分为小对象(如 task_struct),减少内部碎片。
    • 用户空间管理:如 malloc 将页划分为更小块(通过内存池或 slab 机制)。

10. 物理页仅用 1K 时的管理

  • 内核层面
    伙伴系统和 slab 分配器管理物理页,但 不关心应用层如何使用内存
  • 用户空间管理
    • malloc 将分配的页切割为更小单位(如 16B、32B),通过空闲链表管理。
    • 例如,4KB 页可能被分为 32 个 128B 块,未使用的块保留在链表中供后续分配。
  • 内部碎片
    属于应用层内存管理的固有代价,需通过高效分配算法(如 jemalloc、tcmalloc)优化。

11. Linux 查看 CPU 状态/内存的命令

  • CPU 状态
    • top / htop:实时查看 CPU 使用率、进程状态。
    • mpstat -P ALL:查看每个 CPU 核心的利用率。
    • vmstat 1:监控系统整体状态(CPU、内存、I/O)。
  • 内存状态
    • free -h:显示内存和交换空间总量及使用量。
    • cat /proc/meminfo:查看详细内存分配信息。

12. GDB 断点与堆栈命令

  • 断点命令
    • 设置断点:break <函数名>b <文件名:行号>
    • 条件断点:break <位置> if <条件>(如 b main.c:10 if x==5
    • 查看断点:info breakpoints
    • 删除断点:delete <断点编号>
  • 查看堆栈
    • backtrace(或 bt):显示当前调用堆栈。
    • frame <编号>:切换到指定堆栈帧,查看局部变量。

13. TCP 拥塞控制原理

  • 核心目标:避免网络过载,动态调整发送速率。
  • 关键机制
    1. 慢启动(Slow Start):初始窗口指数增长,直到阈值(ssthresh)。
    2. 拥塞避免(Congestion Avoidance):窗口线性增长。
    3. 快速重传(Fast Retransmit):收到 3 个重复 ACK 时立即重传丢失报文。
    4. 快速恢复(Fast Recovery):重传后不重置窗口到初始值,继续拥塞避免阶段。

14. 拥塞避免(Congestion Avoidance)

  • 定义:TCP 进入稳定传输阶段后,窗口按线性增长(每 RTT 增加 1 MSS),避免窗口膨胀导致网络拥塞。
  • 触发条件:当窗口大小达到慢启动阈值(ssthresh)后,进入拥塞避免阶段。

15. TIME_WAIT 状态的目的(复述)

  • 核心作用
    1. 确保被动关闭方收到最后的 ACK,防止其重传 FIN。
    2. 等待 2MSL(报文最大生存时间),确保网络中旧连接的报文失效,避免与新连接混淆。

16. 用户级内存池实现[给你一块内存,怎么管理起来]

  • 设计要点

    1. 预分配大块内存:通过 mallocmmap 申请一大块内存作为池。
    2. 划分内存块:将池划分为固定大小块(如 4KB)或动态分块。
    3. 空闲链表管理:用链表记录空闲块,分配时取链表头部,释放时插回链表。
    4. 碎片优化:合并相邻空闲块,或按不同块大小分级管理(类似 slab)。
  • 示例伪代码

    struct MemoryBlock { void* start; size_t size; MemoryBlock* next; };  
    MemoryBlock* free_list; // 空闲块链表  
    void* my_alloc(size_t size) {  // 从 free_list 中查找合适块,分割或直接分配  
    }  
    void my_free(void* ptr) {  // 将释放的块插回 free_list,尝试合并相邻块  
    }  
    

17. Buffer 和 Cache 的区别

  • Buffer
    • 目的:缓冲写入操作(如磁盘 I/O),合并多次小写为单次大写,减少系统调用。
    • 场景:内核将磁盘写入暂存到缓冲区,定期刷盘。
  • Cache
    • 目的:缓存读取数据(如文件内容),加速后续访问。
    • 场景:文件被首次读取后,内容缓存在内存中供下次快速访问。

18. 字节序(Endianness)

  • 定义:数据在内存中的存储顺序:
    • 大端序(Big-Endian):高位字节在前(如网络字节序,0x1234 存储为 12 34)。
    • 小端序(Little-Endian):低位字节在前(如 x86 CPU,0x1234 存储为 34 12)。

19. 索引的目的

  • 核心作用
    1. 加速数据检索,减少全表扫描的 I/O 开销。
    2. 强制唯一性约束(如唯一索引)。
  • 代价
    1. 占用额外存储空间。
    2. 增删改操作需维护索引,降低写入速度。

20. MySQL 索引原理(InnoDB vs MyISAM)

  • 存储引擎对比

    特性InnoDB(聚簇索引)MyISAM(非聚簇索引)
    数据与索引存储数据按主键顺序存储数据与索引分开存储
    主键查找直接定位数据页先查索引,再根据地址读数据
    非主键索引叶子节点存储主键值叶子节点存储数据地址
  • B+ 树深度

    • 假设单节点存储 1000 条记录:
      • 3 层 B+ 树可支持约 10 亿条数据(1000^3)。
    • 实际深度通常为 3~4 层(与数据量和节点大小相关)。

21. 多线程使用场景

  • 常见场景
    1. 任务并行:分解计算密集型任务(如图像处理、数据批处理)。
    2. 异步 I/O:处理网络请求、文件读写,避免阻塞主线程。
    3. 生产者-消费者模型:通过队列解耦任务生产与消费(如日志系统)。
    4. 定时任务:定时触发线程执行清理、统计等操作。

22. 线程池参数设置

  • 核心参数
    • 核心线程数(corePoolSize):常驻线程,根据任务类型设置:
      • CPU 密集型:核心数 + 1(避免上下文切换过多)。
      • IO 密集型:2 * CPU 核心数(充分利用等待时间)。
    • 最大线程数(maxPoolSize):突发流量时扩容上限,需避免 OOM。
    • 任务队列
      • 有界队列(如 ArrayBlockingQueue):防止资源耗尽,需配合拒绝策略。
      • 无界队列(如 LinkedBlockingQueue):可能导致内存溢出。
    • 拒绝策略:如丢弃任务、调用者执行、抛出异常等。

23. 线程安全的单例模式

  • 实现方式

    1. 双检锁(C++11 后安全)

      class Singleton {  
      public:  static Singleton* getInstance() {  if (instance == nullptr) {  std::lock_guard<std::mutex> lock(mutex);  if (instance == nullptr) {  instance = new Singleton();  }  }  return instance;  }  
      private:  static Singleton* instance;  static std::mutex mutex;  
      };  
      
    2. 局部静态变量(C++11 起线程安全)

      Singleton& Singleton::getInstance() {  static Singleton instance;  return instance;  
      }  
      

24. Docker 与 Kubernetes(k8s)基础

  • Docker:容器化技术,打包应用及依赖到镜像,实现环境一致性。
  • Kubernetes:容器编排系统,管理容器集群,核心功能:
    • 自动扩缩容:根据负载调整 Pod 数量。
    • 服务发现:通过 Service 暴露服务。
    • 故障恢复:重启异常容器或节点迁移。

25. Map 类型选择

  • 场景与实现
    • 有序 Map
      • C++ std::map(红黑树),Java TreeMap
      • 适用范围:需按序遍历或范围查询。
    • 哈希 Map
      • C++ std::unordered_map,Java HashMap
      • 适用场景:快速单点查询,无需有序。
    • 并发 Map
      • Java ConcurrentHashMap(分段锁),C++ TBB concurrent_hash_map

26. HashTable 线程安全实现

  • 常见方案
    1. 全局锁:简单但性能差(如 Collections.synchronizedMap)。
    2. 分段锁(ConcurrentHashMap):将哈希表分片,每段独立加锁。
    3. 无锁 CAS:通过原子操作实现无锁访问(适用于高并发读场景)。

27. 索引失效场景

  • 常见原因
    1. 违反最左前缀原则:联合索引未从最左列开始查询。
    2. 隐式类型转换:如字符串字段用数字查询。
    3. 函数或表达式WHERE YEAR(date) = 2023
    4. OR 条件未全覆盖索引:部分条件无法使用索引。

28. 慢查询优化步骤

  1. EXPLAIN 分析:查看执行计划,确认是否走索引。
  2. 索引优化:添加缺失索引或优化索引顺序。
  3. 查询重写:避免 SELECT *、拆分复杂查询。
  4. 数据归档:清理历史数据,减少扫描范围。

29. 优化后仍慢的解决方案

  • 分库分表
    • 垂直分表:按业务拆分表(如用户表与订单表分离)。
    • 水平分表:按哈希或范围分片(如订单按用户 ID 分 10 表)。
  • 硬件升级:提升 CPU、内存、使用 SSD。
  • 缓存层:引入 Redis 缓存热点数据。

30. 行锁 vs 表锁

  • 行锁(如 InnoDB)
    • 场景:高并发写(如订单支付),锁粒度细,冲突少。
    • 实现:通过索引加锁,若未走索引则退化为表锁。
  • 表锁(如 MyISAM)
    • 场景:批量更新、全表扫描,锁粒度粗,并发度低。

分库分表落地示例(补充)

  • 中间件选型
    • ShardingSphere:通过代理或 JDBC 驱动实现透明分片。
  • 分片键设计:选择离散度高且查询频繁的字段(如用户 ID)。
  • 数据迁移:双写过渡 + 历史数据同步工具。

:若面试官质疑分库分表方案,可强调具体场景的权衡(如分片后跨片查询复杂度),并补充监控和弹性扩容策略。

31. 乐观锁与悲观锁的实现

  • 悲观锁

    • 实现:直接加锁,确保独占访问。
      • 数据库:SELECT ... FOR UPDATE(行级锁)。
      • Java:synchronized 关键字或 ReentrantLock
    • 场景:写操作频繁,竞争激烈(如账户扣款)。
  • 乐观锁

    • 实现:通过版本号或 CAS(Compare And Swap)实现无锁化。
      • 数据库:UPDATE table SET val = new_val, version = version + 1 WHERE id = x AND version = old_version
      • Java:AtomicInteger 等原子类(基于 CPU 的 CAS 指令)。
    • 场景:读多写少,冲突概率低(如库存更新)。

32. Redis 使用场景

  • 核心用途
    1. 缓存:缓存热点数据(如用户信息),减轻数据库压力。
    2. 发布订阅:实现消息广播(如订单状态通知)。
    3. 排行榜:通过 ZSET(有序集合)存储实时排名(如游戏积分榜)。
    4. 分布式锁:利用 SETNX + 过期时间实现互斥控制。
    5. 注册中心:临时存储服务节点地址(配合心跳机制)。

33. Redis 作为注册中心的优劣

  • 优势
    • 高性能:读写速度快,适合高频服务发现。
    • 简单易用:无需额外组件(对比 ZooKeeper/Etcd)。
  • 劣势
    • 数据可靠性:持久化可能丢失数据(需权衡 AOF/RDB 配置)。
    • 一致性弱:主从同步延迟可能导致短暂不一致。
    • 功能局限:缺乏健康检查、Watcher 机制(需自行实现)。

34. Redis 分布式锁的时钟问题

  • 问题描述
    • 客户端 A 获取锁后因 GC 或网络延迟,锁过期被释放,客户端 B 获取锁后,A 仍认为持有锁,导致数据冲突。
    • 根因:各机器本地时钟不同步,锁过期时间计算偏差。

35. 时钟问题解决方案

  1. RedLock 算法
    • 向多个 Redis 节点申请锁,过半成功才算获取锁。
    • 争议点:依赖“网络延迟和时钟漂移可控”假设,实际复杂场景可能失效。
  2. 租约机制
    • 锁设置较短过期时间,客户端启动后台线程定期续期(如 Redisson 的 WatchDog)。
  3. 单调时间戳
    • 使用逻辑时钟(如 HLC)替代物理时钟,避免回拨问题。

36. Redis 持久化方案

  • RDB(快照)
    • 原理:定时 fork 子进程生成内存快照文件(.rdb)。
    • 优点:恢复速度快,文件紧凑。
    • 缺点:可能丢失最后一次快照后的数据。
  • AOF(追加日志)
    • 原理:记录所有写操作命令(appendonly.aof),支持每秒/每次同步。
    • 优点:数据丢失少(配置为 appendfsync always 时)。
    • 缺点:文件大,恢复慢。
  • 混合模式(Redis 4.0+):
    • RDB 全量 + AOF 增量,兼顾速度和可靠性。

37. 进程通信(IPC)方式

  1. 管道(Pipe):单向通信,父子进程间使用(如 ls | grep txt)。
  2. 消息队列(如 RabbitMQ、POSIX mq):解耦生产者和消费者。
  3. 共享内存:通过 shmget 创建,高效但需同步机制(信号量)。
  4. 信号(Signal):异步通知(如 kill -9)。
  5. 套接字(Socket):跨网络通信(TCP/UDP)。

38. RPC 与常用框架

  • RPC(Remote Procedure Call)
    • 像调用本地方法一样调用远程服务,核心步骤:
      1. 序列化:将参数转换为二进制(如 Protobuf)。
      2. 传输:通过 TCP/HTTP 发送请求。
      3. 反序列化:服务端解析并执行方法,返回结果。
  • 常见框架
    • gRPC:基于 HTTP/2 和 Protobuf,跨语言支持。
    • Dubbo:阿里开源,集成服务治理(熔断、负载均衡)。
    • Thrift:Facebook 开发,支持多种传输协议。

39. 同步调用 vs 异步调用

  • 同步调用
    • 调用方阻塞等待结果返回(如 HttpClient.get())。
    • 场景:需立即获取结果的简单逻辑。
  • 异步调用
    • 调用方提交请求后继续执行,通过回调或 Future 获取结果(如 CompletableFuture)。
    • 场景:高并发、长耗时操作(如批量文件处理)。

40. Linux 常用命令

  • 系统监控
    • top / htop:实时资源监控。
    • vmstat / iostat:查看 CPU、内存、I/O 状态。
  • 文件操作
    • grep / awk / sed:文本搜索与处理。
    • find:文件查找(如 find / -name "*.log")。
  • 网络调试
    • netstat -tuln:查看端口监听状态。
    • tcpdump:抓包分析(如 tcpdump port 80)。
  • 进程管理
    • ps aux:查看所有进程。
    • kill -9 <PID>:强制终止进程。

分库分表补充(应对面试官质疑)

  • 水平分片
    • 分片键选择:确保数据均匀分布(如用户 ID 哈希)。
    • 路由策略:客户端或中间件(如 ShardingSphere)根据分片键路由。
  • 垂直分片
    • 业务拆分:将字段按业务分离到不同表(如用户基础信息与扩展信息)。
  • 挑战
    • 跨分片查询:通过汇总多个分片结果或冗余数据解决。
    • 事务一致性:使用最终一致性或分布式事务(如 Seata)。

1. 查询HTTPS是否数据安全的命令及注意事项

命令

  • curl -v https://example.com:查看SSL握手细节,验证证书信息。
  • openssl s_client -connect example.com:443:检查证书链、协议版本、加密算法等。
  • 浏览器开发者工具(Security Tab):可视化检查证书有效性、加密协议。
  • 工具如 nmap --script ssl-enum-ciphers:扫描服务器支持的加密套件。

注意事项
HTTPS本身通过加密和证书机制保障安全,但需注意:

  • 证书伪造:需验证证书颁发机构(CA)是否受信,域名是否匹配。
  • 协议/算法漏洞:如禁用TLS 1.0/1.1,避免弱加密算法(如RC4)。
  • 中间人攻击:确保客户端不忽略证书错误(如curl加--cacert指定CA)。

2. 调用HTTPS服务的方法(C++后端)

  • 使用HTTP库:如libcurl,需配置SSL/TLS参数:

    CURL *curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
    curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/cacert.pem"); // 指定CA证书
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // 验证证书
    curl_easy_perform(curl);
    
  • 自定义证书验证:通过回调函数深度校验证书指纹或扩展属性。

  • 双向认证:若服务端要求客户端证书,需设置CURLOPT_SSLCERTCURLOPT_SSLKEY


3. 消息队列使用经验

  • Kafka:高吞吐、持久化,适合日志、流处理。
  • RabbitMQ:AMQP协议,事务/确认机制,适合业务解耦。
  • RocketMQ:阿里系,顺序消息、事务消息支持较好。

4. 消息不丢失与顺序性保证

  • 不丢失
    • 生产者:开启ACK确认(如Kafka的acks=all)。
    • 消息队列:持久化消息到磁盘。
    • 消费者:手动提交Offset,处理成功后确认。
  • 顺序性
    • Kafka:同一Partition内消息有序,需业务确保单分区消费。
    • RocketMQ:通过MessageQueue锁定保证局部顺序。

5. 一致性哈希算法

  • 原理:将节点和数据映射到哈希环,数据按顺时针找到最近节点。
  • 优势:节点增减时仅影响相邻数据,避免全量重哈希。
  • 虚拟节点:解决物理节点分布不均问题,提高负载均衡性。

6. Redis与MySQL一致性

  • 策略
    • Cache-Aside:读时先查缓存,无则读DB并回填;写时更新DB后删除缓存。
    • 双写:同步更新DB和缓存(需事务保证,可能引入复杂性)。
    • 监听Binlog:通过Canal等工具订阅DB变更,异步更新缓存。
  • 注意事项:设置合理的缓存过期时间,兜底防雪崩。

7. 本地缓存与Redis协调

  • 读写策略
    • 读:本地缓存 → Redis → DB,逐级回填。
    • 写:先更新DB,再失效Redis和本地缓存(如广播消息通知其他节点)。
  • 一致性保障
    • 本地缓存设置短过期时间(如5秒)。
    • 使用Pub/Sub或中间件(如ZooKeeper)同步失效事件。

8. 技术敏感度保持

  • 日常实践
    • 关注Github Trending、Hacker News、技术博客(如掘金、InfoQ)。
    • 参与开源项目,阅读源码(如Redis/Kafka)。
    • 定期输出技术文章,复盘项目难点。
  • 体系化学习
    • 极客时间/极客大学等课程系统性补足知识盲区。
    • 参加行业会议(QCon、ArchSummit)了解前沿方案。

9. 为何离开百度寻找新实习

  • 求知挑战:希望接触不同场景(如高并发/分布式),拓宽技术视野。
  • 方向契合:当前岗位更偏向某领域(如云原生/中间件),与职业规划一致。
  • 时间规划:原实习周期结束,寻求更深度参与的项目机会。

示例回答

“在百度的实习中积累了XX经验,但目前希望深入探索XX领域(如分布式系统/高性能网络),而新机会的技术栈和业务方向更契合我的长期规划,因此决定尝试新的挑战。”


以上回答需结合个人实际经验调整,突出技术细节与思考深度。


1. 补充项目测试结果:分析极限情况下的性能瓶颈

  • 常见性能瓶颈方向

    • CPU密集型:高并发下线程竞争锁、复杂计算逻辑(如JSON序列化)、频繁GC(Java场景)。
    • 内存泄漏:未释放资源(如数据库连接、文件句柄)、缓存无限增长(未设TTL)。
    • I/O瓶颈:磁盘读写慢(机械硬盘)、数据库慢查询(未优化索引)、网络带宽打满。
    • 协议层限制:TCP连接数受限于操作系统(ulimit)、HTTP/1.1队头阻塞。
  • 极限场景示例

    • 高并发写入:Kafka Partition数量不足,导致Producer线程阻塞。
    • 缓存击穿:大量请求同时查询同一个失效的Redis Key,穿透到数据库。
    • 分布式锁竞争:Redis锁粒度太粗,多个服务争抢同一资源。
  • 优化方法

    • 水平扩展:增加服务节点、分库分表、Kafka Partition扩容。
    • 异步化:耗时操作(如日志写入)改为异步队列处理。
    • 资源复用:数据库连接池、线程池合理配置,避免频繁创建销毁。

2. Vector如何主动释放内存空间

  • 问题本质vector::clear() 仅清空元素(size=0),不会释放底层内存(capacity不变)

  • 释放方法

    • C++98vector<T>().swap(v);,通过空临时对象交换,释放原内存。
    • C++11+v.shrink_to_fit(); 请求减少capacity至匹配size(实现可能不严格保证)。
  • 示例代码

    std::vector<int> v(1000);  // capacity=1000
    v.clear();                 // size=0,capacity仍为1000
    std::vector<int>().swap(v); // capacity变为0
    
  • 适用场景
    在长期运行的服务中,若某个vector曾扩容到很大但后续不再需要,主动释放避免内存浪费。


3. 稳定排序算法

  • 定义:相等元素的相对顺序在排序前后保持不变。
  • 稳定算法
    • 冒泡排序:相邻元素交换,相等时不交换。
    • 插入排序:元素逐个插入有序区,相等时插入到后方。
    • 归并排序:合并时保留左子数组元素的原始顺序。
    • 基数排序:按位排序时依赖稳定的子排序算法。
  • 非稳定算法
    • 快速排序:分区过程中可能打乱相等元素的顺序。
    • 堆排序:建堆和调整堆时破坏稳定性。
    • 选择排序:每次选择最小元素交换到前方,可能跨越相等元素。

4. TCP三次握手的数据携带问题

  • RFC规定
    • 第一次握手(SYN):不允许携带数据,防止SYN Flood攻击(攻击者发送大量SYN+Data消耗服务端资源)。
    • 第二次握手(SYN-ACK):不允许携带数据,原因同上。
    • 第三次握手(ACK)允许携带数据,此时连接已建立(客户端进入ESTABLISHED状态)。
  • 影响
    • 优点:减少一次RTT(应用层数据可在第三次握手时发送)。
    • 风险:服务端在第三次握手前需分配资源,可能被攻击者利用(需结合SYN Cookie等防护机制)。

5. select 和 epoll 的适用场景

场景selectepoll
活跃连接数少更简单,代码量少无优势,反而需要维护epoll实例
活跃连接数多O(n)遍历所有fd,性能差O(1)事件通知,高效
文件描述符数量受限于FD_SETSIZE(默认1024)支持数万级fd
触发模式仅水平触发(LT)支持水平触发(LT)和边缘触发(ET)
  • 选择建议
    • 短连接、低并发:select或poll更简单(如小型嵌入式系统)。
    • 长连接、高并发:epoll(如Web服务器、实时消息推送)。
  • 边缘触发(ET)注意事项
    • 需一次性读完数据,否则可能丢失事件。
    • 非阻塞IO必须,避免阻塞其他fd的处理。

总结:以上问题需结合底层原理(如TCP协议栈、OS内核机制)和实际场景(如高并发服务优化)回答,突出对细节的理解和工程权衡能力。

模拟


1. C++ HTTP服务器线程模型与并发能力

  • 线程模型
    通常采用Reactor模式,基于线程池 + epoll多路复用,而非一个线程处理一个请求。
    • 主线程负责监听连接(epoll_wait),将就绪的I/O事件分发给工作线程。
    • 工作线程处理请求(解析HTTP、业务逻辑、响应)。
  • 并发测试
    • 测试环境:4核8G云服务器,Ubuntu 20.04,测试工具wrk
    • 结果:短连接场景下支持约5K QPS,长连接(Keep-Alive)可达10K+并发连接
    • 瓶颈:内存带宽、线程竞争锁、文件描述符限制(需调整ulimit -n)。

2. 服务崩溃原因分析

  • 常见崩溃原因
    • 内存泄漏:未释放动态分配的内存(如new后未delete)。
    • 线程安全:多线程竞争资源(如全局变量未加锁)。
    • 资源耗尽:文件描述符、线程数、连接数超过系统限制。
    • 未捕获异常:如C++中未处理的std::exception
  • 调试方法
    • 使用gdb分析core dump文件,定位崩溃点。
    • 日志记录关键路径(如请求处理前后)。
    • 工具检测:Valgrind(内存泄漏)、AddressSanitizer(越界访问)。

3. 大批量请求下的服务端状态

  • 服务端状态
    • CPU打满:线程池全忙,请求排队。
    • 内存增长:连接缓冲区、业务对象累积。
    • 网络拥塞:TCP缓冲区满,丢包重传。
  • 表现
    • 响应延迟增加,超时错误率上升。
    • 可能触发OOM Killer终止进程。

4. CGI机制详解

  • 原理:Web服务器将HTTP请求转发给外部程序(如Python脚本),通过环境变量和标准输入传递参数,标准输出返回响应。
  • 流程
    1. 服务器解析请求,设置环境变量(如QUERY_STRING)。
    2. 创建子进程执行CGI程序。
    3. CGI程序读取输入,处理结果写入stdout
    4. 服务器捕获输出并返回客户端。
  • 缺点:每次请求需创建进程,性能差(已被FastCGI取代)。

FastCGI

FastCGI 是什么?

FastCGI(Fast Common Gateway Interface)是一种用于提升 Web 应用程序性能的协议。它通过优化 Web 服务器(如 Nginx、Apache)与外部应用程序(如 PHP、Python 脚本)之间的通信方式,解决了传统 CGI(Common Gateway Interface)的性能瓶颈。


FastCGI 的核心特性

  1. 持久化进程

    • 传统 CGI 对每个请求都需启动新进程,而 FastCGI 使用持久化进程处理多个请求,减少进程频繁创建/销毁的开销。
    • 优势:显著降低延迟,提高并发能力。
  2. 服务分离

    • Web 服务器(如 Nginx)和应用程序(如 PHP-FPM)可运行在独立进程中,甚至部署在不同服务器上。
    • 优势:提升扩展性,方便单独优化或横向扩展。
  3. 语言无关性

    • 支持多种编程语言(PHP、Python、Ruby 等),只需应用程序实现 FastCGI 协议接口。
  4. 二进制通信协议

    • 使用二进制协议传输数据(相比 CGI 的文本协议),传输效率更高。
  5. 负载均衡与容错

    • 支持将请求分发给多个后端应用进程,避免单点故障。

FastCGI 工作原理

  1. 请求流程

    • 用户发起 HTTP 请求 → Web 服务器(如 Nginx)接收。
    • Web 服务器通过 FastCGI 协议将请求转发给应用进程(如 PHP-FPM)。
    • 应用进程处理请求并返回结果 → Web 服务器将结果返回给用户。
  2. 进程模型

    • FastCGI 进程常驻内存,通过复用处理多个请求(类似线程池)。
    • 例如:PHP-FPM 通过进程池管理 PHP 解释器实例。

FastCGI 的优势

对比项FastCGI传统 CGI
性能高(持久化进程)低(每次请求新建进程)
资源占用低(进程复用)高(频繁创建/销毁进程)
扩展性支持分布式部署和负载均衡单进程,难以扩展
安全性应用与 Web 服务器隔离,风险更低耦合度高,安全性较低

典型应用场景

  1. PHP 应用

    • 通过 PHP-FPM(FastCGI Process Manager)处理动态请求,如 WordPress、Laravel。

    • 配置示例(Nginx + PHP-FPM):

      location ~ \.php$ {fastcgi_pass   unix:/var/run/php/php8.1-fpm.sock;include        fastcgi_params;fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
      }
      
  2. Python/Ruby 应用

    • 使用 flup(Python)或 fcgi(Ruby)等库实现 FastCGI 接口。
  3. 高并发网站

    • 适合需要快速响应大量请求的场景(如电商、API 服务)。

FastCGI 的局限性

  • 协议复杂性:需额外配置 Web 服务器与应用进程的通信(如 Socket 或 TCP)。
  • 长连接管理:持久化进程需监控资源泄漏(如内存未释放)。
  • 现代替代方案:部分场景已被更高效的协议取代(如 HTTP/2、gRPC)。

常见问题与解决方案

  1. 502 Bad Gateway 错误

    • 原因:FastCGI 进程崩溃或通信超时。
    • 解决:检查 PHP-FPM 是否运行,调整 fastcgi_read_timeout 参数。
  2. 性能瓶颈

    • 优化方向:增加 PHP-FPM 进程数、调整进程池策略(pm.max_children)。
  3. 安全性配置

    • 禁止执行敏感操作:在 php.ini 中禁用危险函数(如 exec)。
    • 使用 Unix Socket 替代 TCP,减少网络暴露。

总结

FastCGI 通过持久化进程高效通信协议,成为动态 Web 应用的主流解决方案。尽管面临现代协议竞争,其在 PHP 生态(如 PHP-FPM)中仍是性能优化的基石。合理配置 FastCGI 参数(如超时时间、进程数),可显著提升服务器吞吐量和稳定性。

HTTP/2 与 gRPC

以下是关于 HTTP/2gRPC 的对比与分析,结合其特性、应用场景及与 FastCGI 的差异:


HTTP/2 与 gRPC 的核心对比

特性HTTP/2gRPCFastCGI(参考对比)
协议类型传输层协议(基于 TCP)应用层 RPC 框架(基于 HTTP/2)应用层通信协议
数据格式文本(如 JSON/HTML)或二进制二进制(Protocol Buffers)二进制(FastCGI 协议格式)
主要目标优化 Web 传输效率高性能跨语言服务通信Web 服务器与后端应用通信
多路复用✔️ 单连接并发多个请求/响应✔️ 继承 HTTP/2 的多路复用❌ 单请求/响应模型
头部压缩✔️ HPACK 压缩头部✔️ 继承 HTTP/2 的头部压缩❌ 无压缩
流式通信✔️ 支持服务端推送和流式传输✔️ 支持双向流(Client/Server Streaming)❌ 仅单向请求-响应
语言支持所有支持 HTTP 的语言多语言(C++, Java, Python, Go 等)多语言(PHP, Python 等)
典型应用场景网页加载、API 交互微服务、分布式系统、实时通信动态网页处理(如 PHP-FPM)

HTTP/2 的核心特性

  1. 多路复用(Multiplexing)

    • 在单一 TCP 连接上并行传输多个请求/响应,解决 HTTP/1.1 的队头阻塞问题。
    • 优势:减少延迟,提升页面加载速度。
  2. 头部压缩(HPACK)

    • 压缩重复的 HTTP 头部,降低传输开销(尤其在 API 高频调用中效果显著)。
  3. 服务端推送(Server Push)

    • 服务器可主动推送资源(如 CSS/JS)到客户端缓存,无需等待客户端请求。
  4. 二进制分帧(Binary Framing)

    • 数据以二进制帧传输,解析效率高于 HTTP/1.1 的文本格式。

适用场景

  • 优化现代 Web 应用(如 SPA 单页应用)。
  • 高频 API 请求(如移动端与后端交互)。

gRPC 的核心特性

  1. 基于 HTTP/2 和 Protocol Buffers

    • 使用 HTTP/2 作为传输层,默认通过 Protocol Buffers(高效二进制序列化格式)编码数据。
    • 优势:体积小、序列化速度快,跨语言兼容。
  2. 强类型接口定义(Protobuf IDL)

    • 通过 .proto 文件定义服务接口和数据结构,自动生成客户端和服务端代码。

    • 示例

      service UserService {rpc GetUser (UserRequest) returns (UserResponse);
      }
      message UserRequest { int32 id = 1; }
      message UserResponse { string name = 1; }
      
  3. 四种通信模式

    • Unary RPC:传统请求-响应模式。
    • Server Streaming:服务端流式返回多个响应。
    • Client Streaming:客户端流式发送多个请求。
    • Bidirectional Streaming:双向流式通信(如聊天应用)。
  4. 跨语言支持

    • 支持多种编程语言(需官方或社区 SDK),适合微服务架构中的多语言协作。

适用场景

  • 微服务间的高效通信(如 Kubernetes 内部服务调用)。
  • 实时数据传输(如 IoT 设备、游戏服务器)。

与 FastCGI 的对比

  1. 定位差异

    • FastCGI:专注 Web 服务器与后端应用(如 PHP/Python)的通信,优化动态内容生成。
    • HTTP/2/gRPC:面向更通用的网络通信,尤其是服务间(Service-to-Service)交互。
  2. 性能

    • gRPC > HTTP/2 > FastCGI(得益于二进制协议和多路复用)。
    • FastCGI 的瓶颈在于进程管理和单请求模型。
  3. 使用复杂度

    • FastCGI:配置简单,适合传统 LAMP 架构。
    • gRPC:需定义接口和生成代码,适合中大型分布式系统。

如何选择?

场景推荐协议理由
动态网页渲染(PHP/Python)FastCGI成熟、与 Nginx/Apache 集成度高
浏览器与服务器的 Web 交互HTTP/2兼容性强,优化资源加载和 API 性能
微服务间通信(如 Go ↔ Java)gRPC跨语言、高性能、支持流式通信
实时数据传输(如股票行情)gRPC(Streaming)低延迟,支持双向流

示例:gRPC 服务端与客户端(Python)

  1. 定义 .proto 文件

    syntax = "proto3";
    service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    message HelloRequest { string name = 1; }
    message HelloReply { string message = 1; }
    
  2. 生成代码

    python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. greeter.proto
    
  3. 服务端实现

    class Greeter(grpc.GreeterServicer):def SayHello(self, request, context):return HelloReply(message=f'Hello, {request.name}!')
    server = grpc.server(ThreadPoolExecutor())
    add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    
  4. 客户端调用

    channel = grpc.insecure_channel('localhost:50051')
    stub = GreeterStub(channel)
    response = stub.SayHello(HelloRequest(name='World'))
    print(response.message)  # 输出:Hello, World!
    

总结

  • HTTP/2 是 Web 传输的未来标准,适用于优化浏览器与服务器的通信。
  • gRPC 是云原生时代的 RPC 框架,适合高性能、跨语言的微服务架构。
  • FastCGI 仍适用于传统 Web 应用(如 PHP),但逐渐被容器化和服务化架构取代。

根据场景需求选择协议:

  • 追求极致性能 → gRPC。
  • 兼容现有 Web 生态 → HTTP/2。
  • 传统动态页面渲染 → FastCGI。

5. 进程创建与通信

  • 创建子进程
    • fork():复制父进程地址空间。
    • exec()系列:替换子进程代码段(如执行CGI程序)。
  • 通信方式
    • 管道(pipe):单向通信,fork()前创建。
    • 共享内存(shm):高效但需同步。
    • 信号(signal):简单通知,不可传数据。
    • Unix域套接字:本地进程间全双工通信。

6. Cookie vs Session

特性CookieSession
存储位置客户端(浏览器)服务端(内存/数据库)
安全性低(易被篡改)高(仅存Session ID在Cookie)
生命周期可设置过期时间通常随会话结束失效
适用场景简单状态跟踪(如语言偏好)敏感信息(如登录态)

7. 线程池的作用与优势

  • 解决的问题
    • 线程创建/销毁开销:避免频繁系统调用(Linux中线程是轻量级进程,但仍需分配栈、TCB等)。
    • 资源竞争:控制并发线程数,防止耗尽内存或CPU。
    • 响应延迟:复用已存在的线程,减少初始化时间。
  • 适用场景
    • 高并发短任务(如HTTP请求处理)。
    • 需要限制资源使用的场景(如数据库连接池)。

8. Vector内存释放

  • clear()行为
    • 仅将size置为0,capacity保持不变,内存未释放。
  • 主动释放方法
    • C++98vector<T>().swap(v);(交换空临时对象)。
    • C++11+v.shrink_to_fit();(请求缩减容量,实现可能忽略)。

9. 红黑树 vs 哈希表

场景红黑树哈希表
有序性支持有序遍历无序
查询速度O(log n)平均O(1),最差O(n)
内存开销每个节点需额外存储颜色/指针桶数组 + 链表/红黑树解决冲突
适用场景需范围查询(如C++ std::map快速单点查询(如缓存)

10. 右值引用解决的问题

  • 核心问题:避免深拷贝,实现资源移动语义
    • 示例std::vector的移动构造函数“窃取”临时对象的内部指针,避免复制元素。
    • 应用std::move()、完美转发(std::forward)。

11. 稳定排序算法

  • 稳定性定义:相等元素的相对顺序在排序前后不变。
  • 稳定算法
    • 冒泡排序、插入排序、归并排序、基数排序。
  • 非稳定算法
    • 快速排序、堆排序、选择排序。

12. TCP三次握手的必要性

  • 三次握手目的
    • 双方确认彼此的序列号初始化(ISN),防止历史重复连接(如延迟的SYN包)。
    • 两次握手不足:客户端无法确认服务端已收到ACK,服务端可能误建连接。
  • 四次握手冗余:第三次握手已可携带数据,无需额外步骤。

13. TCP握手阶段的数据携带

  • RFC规定
    • 第一次握手(SYN):不允许数据,防SYN Flood攻击。
    • 第二次握手(SYN-ACK):不允许数据。
    • 第三次握手(ACK):允许携带数据(客户端已进入ESTABLISHED状态)。

14. TCP数据安全性

  • TCP自身不加密:数据明文传输,安全性依赖上层协议(如HTTPS的TLS加密)。
  • 安全机制
    • 序列号/确认号:防止数据篡改(但可预测)。
    • 校验和:检测数据错误,无法防恶意攻击。

15. 网线拔插对TCP连接的影响

假设 客户端和服务器已经建立了 TCP 连接,并且正在正常通信。在这个时候,如果 客户端的网线被拔掉,会发生以下情况:


1. 物理层 & 数据链路层的影响

  • 网线拔掉后,客户端的网络接口立即失效,无法发送或接收任何数据。
  • 客户端不会立刻通知 TCP 层,也不会主动发送 FIN(连接关闭)RST(连接重置),因为它已经失去了网络连接。
  • 服务器端并不会立刻察觉到客户端掉线,因为 TCP 连接依赖超时机制来判断连接的存活状态。

2. TCP 层的行为

(1) 服务器仍然认为连接是“活跃的”

服务器并不会立即关闭 TCP 连接。因为 TCP 是面向连接、可靠的协议,它依赖于超时机制来检测连接状态,而不是立即终止。

(2) 服务器继续发送数据

如果服务器有数据要发送,它会将数据放入 TCP 发送缓冲区 并尝试通过网络发送:

  • 服务器会等待客户端的 ACK(确认包)
  • 由于客户端网线被拔掉,客户端无法接收这个数据,也无法返回 ACK。
  • 服务器不会立刻放弃,而是会 重传 数据(TCP 可靠传输机制)。

(3) TCP 超时重传机制生效

TCP 采用超时重传(RTO, Retransmission Timeout)策略:

  1. 初次超时(第 1 次重传)

    • 服务器等待一定时间(超时时间 RTO),如果未收到 ACK,则触发第一次重传。
  2. 指数退避(指数增长的超时机制)

    • TCP 采用

      指数退避

      机制(即每次超时,等待时间加倍),例如:

      • 第一次重传:等待 1s
      • 第二次重传:等待 2s
      • 第三次重传:等待 4s
      • ……
    • 这个过程会持续,直到超过 最大重传次数(一般是 TCP 超时上限,如 2 分钟 ~ 10 分钟,视操作系统和具体 TCP 实现而定)。

  3. 达到最大重传次数后,服务器判定连接失败

    • 服务器最终会发送 RST(Reset),强制关闭连接,并释放 TCP 资源。

3. 可能出现的异常

(1) 客户端重新插回网线

如果客户端在 TCP 连接完全超时之前重新插回网线,但 IP 地址未变:

  • 服务器的 TCP 连接仍然存活,客户端可以继续发送 ACK。
  • 服务器会继续传输数据,连接不会断开。

如果客户端在 TCP 连接超时后才插回网线:

  • 服务器已经关闭连接(RST)。
  • 客户端的 TCP 连接仍然认为连接存在(但是数据传输失败)。
  • 任何新请求都可能收到 RST,必须重新进行 三次握手 建立新连接。

(2) 客户端 IP 地址变化

如果客户端拔掉网线后,重新插入网线时 IP 地址发生了变化

  • 服务器仍然尝试向旧 IP 地址发送数据,连接最终会超时并断开。
  • 客户端即使恢复网络,也无法继续使用旧连接,必须重新建立 TCP 连接。

(3) TCP Keepalive

某些服务器可能启用了 TCP Keepalive 机制,它会定期发送探测包来检测连接是否仍然存活:

  • 如果客户端未响应 keepalive 探测包(通常间隔 75s 或更长),服务器会判断连接已失效,主动关闭 TCP 连接。

4. 结论

短时间内拔插网线

如果客户端在 TCP 超时前 重新插上网线:

  • 连接可能不会断开,数据传输可能继续。
  • 但如果错过了 TCP 超时窗口,服务器会判定连接失败并断开。

长时间断开

  • 服务器会尝试重传数据。
  • 经过多次重传后,如果仍未收到响应,服务器会 发送 RST 并关闭连接。
  • 客户端无法继续使用该 TCP 连接,必须重新建立连接。

影响总结

  • TCP 不会立刻断开连接,而是通过超时重传来判断是否断开。
  • 客户端拔网线不会主动关闭连接,服务器只能通过重传和 Keepalive 机制检测连接是否断开。
  • 如果长时间未恢复,服务器最终会超时并关闭连接。
  • 如果客户端 IP 发生变化,旧连接无法恢复,需要重新建立连接。

附:TCP 连接断开的三种情况

断开原因发生情况服务器行为
正常关闭(FIN)客户端/服务器主动关闭通过 四次挥手 断开连接
异常断开(RST)客户端进程崩溃、服务器异常退出TCP 立刻终止连接,发送 RST
超时断开客户端拔网线,长时间无响应服务器多次重传数据,最终超时关闭

5. 代码实验(模拟拔网线)

你可以使用 iptables 模拟网线拔插的行为:

模拟拔网线

# 阻止客户端与服务器的 TCP 通信(相当于拔掉网线)
iptables -A INPUT -p tcp --sport 12345 -j DROP
iptables -A OUTPUT -p tcp --dport 12345 -j DROP

模拟插回网线

# 允许客户端与服务器的 TCP 通信(相当于插回网线)
iptables -D INPUT -p tcp --sport 12345 -j DROP
iptables -D OUTPUT -p tcp --dport 12345 -j DROP

总结

拔网线的核心影响:

  1. 不会立即导致 TCP 连接断开,服务器依靠超时机制(如 TCP 重传、Keepalive)来判断连接状态。
  2. 短时间内插回网线可能不会断开连接,但如果拔网线时间过长,服务器最终会关闭连接(RST)。
  3. IP 地址变化会导致连接不可恢复,必须重新建立连接。
  4. 应用层(如 WebSocket、RPC)通常会实现自动重连机制,以应对这种情况。

你可以在实际环境中测试 tcpdumpnetstat 来观察 TCP 连接的状态变化 🚀。


16. HTTP传输层协议

HTTP 在传输层使用的协议

HTTP(HyperText Transfer Protocol,超文本传输协议) 在传输层通常使用 TCP(Transmission Control Protocol,传输控制协议)

为什么 HTTP 默认使用 TCP?
  1. 可靠性(Reliable)
    • TCP 提供 可靠的传输,确保数据包按顺序到达且不会丢失。
    • 通过 ACK 确认机制,TCP 确保每个数据包都被成功接收,如果丢失则会重传。
  2. 流量控制(Flow Control)
    • TCP 具有 滑动窗口机制,能够根据网络状况调整数据发送速率,避免网络拥塞。
  3. 错误检测与校正
    • TCP 通过 校验和 检测数据是否被篡改,并能请求重传丢失的数据。
  4. 面向连接(Connection-Oriented)
    • HTTP 通过 TCP 三次握手 先建立连接,再进行数据传输,确保通信双方已建立稳定的连接。

HTTP 可以使用 UDP 吗?

传统的 HTTP (HTTP/1.1, HTTP/2) 不使用 UDP,但 HTTP/3 使用了 UDP

HTTP + UDP 的情况
  1. HTTP/3 + QUIC

    • HTTP/3(下一代 HTTP 协议)不再基于 TCP,而是基于 UDP + QUIC(Quick UDP Internet Connections)。

    • QUIC 解决了 TCP 的一些问题,如 “队头阻塞”(Head-of-line blocking),并提供类似 TCP 的可靠传输。

    • 特点

      • 仍然提供 可靠传输(像 TCP 一样保证数据不丢失)。
      • 连接建立更快(减少握手延迟)。
      • 更适合移动网络,因为可以应对 IP 变化(如 WiFi 和 4G 切换时不断开连接)。
  2. 非标准场景

    • 某些特殊应用 可能使用 HTTP-over-UDP,比如 CDN、视频直播、游戏数据 传输,但通常不用于标准 HTTP 页面加载。

结论

  • HTTP 主要使用 TCP(HTTP/1.1, HTTP/2)。
  • HTTP/3 采用 UDP + QUIC,提供更快的连接和更好的移动性支持。
  • 传统 HTTP 不能直接用 UDP,因为 UDP 不可靠、无连接、不保证顺序,但 HTTP/3 通过 QUIC 解决了这些问题。

17. 优雅关闭Socket

优雅关闭 Socket 主要指在 TCP 连接 中,确保数据完整传输后再正确关闭连接。常用方法:

1. shutdown() + close()

cpp复制编辑// 关闭写入端,通知对方接收完数据后再关闭连接
shutdown(sock, SHUT_WR);// 继续读取对方发送的数据,直到 EOF
char buffer[1024];
while (recv(sock, buffer, sizeof(buffer), 0) > 0) {}// 彻底关闭
close(sock);

2. 使用 SO_LINGER 控制关闭行为

cpp复制编辑struct linger so_linger;
so_linger.l_onoff = 1;  // 启用 linger
so_linger.l_linger = 5; // 等待 5 秒
setsockopt(sock, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
close(sock);
  • l_onoff = 1, l_linger > 0:等待数据发送完成再关闭,否则丢弃数据。
  • l_onoff = 0(默认):直接关闭,操作系统负责发送未传输的数据。
  • l_onoff = 1, l_linger = 0:发送 RST 立刻关闭连接(强制断开)。

3. 正常 TCP 四次挥手

  • 客户端shutdown(sock, SHUT_WR),再 recv() 直到对方 FIN,最后 close()
  • 服务器 读取完数据后 shutdown(SHUT_WR)close()

这样可以确保数据完整传输,避免 TIME_WAIT 影响系统资源。

在网络编程中,优雅关闭(Graceful Shutdown) 指的是在 确保数据完整传输 的情况下,正确地 关闭 TCP 连接,避免数据丢失或资源泄露。


1. TCP 连接的正常关闭

TCP 连接关闭时,通常需要进行 四次挥手(Four-Way Handshake)

  1. 客户端发送 FIN(主动关闭)
  2. 服务器返回 ACK
  3. 服务器发送 FIN
  4. 客户端返回 ACK(完成关闭)

这确保了双方都已完成数据传输。


2. close() VS shutdown()

在 C/C++ 网络编程中,Socket 关闭主要有两种方式:

  • close(fd): 直接关闭 Socket,可能导致未发送完的数据丢失。
  • shutdown(fd, how): 分阶段关闭 连接,保证数据完整性。

(1)close(fd)

cpp复制编辑
close(socket_fd);
  • 直接释放 Socket 资源,如果有未发送数据,操作系统可能会尝试发送但不保证完成。
  • 适用于不关心数据完整性的情况。

(2)shutdown(fd, how)

cpp复制编辑
shutdown(socket_fd, SHUT_WR);  // 只关闭写端,仍可接收数据

shutdown(fd, how) 允许更细粒度的控制:

how 选项作用
SHUT_RD关闭 (不再接收数据)
SHUT_WR关闭 (发送 FIN,通知对方 “我不再发送数据”)
SHUT_RDWR关闭 读+写(相当于 close(fd)

3. 优雅关闭的实现

(1)客户端优雅关闭

cpp复制编辑#include <iostream>
#include <sys/socket.h>
#include <unistd.h>void graceful_close(int sockfd) {// 先关闭写端,发送 FINshutdown(sockfd, SHUT_WR);char buffer[1024];// 继续读取服务器可能发送的剩余数据while (read(sockfd, buffer, sizeof(buffer)) > 0) {std::cout << "Received: " << buffer << std::endl;}// 关闭整个 socketclose(sockfd);
}

(2)服务器优雅关闭

cpp复制编辑void server_graceful_close(int sockfd) {// 关闭写端,通知客户端shutdown(sockfd, SHUT_WR);char buffer[1024];while (read(sockfd, buffer, sizeof(buffer)) > 0) {std::cout << "Client sent: " << buffer << std::endl;}close(sockfd);
}

4. 总结

  • 直接 close(fd):可能导致数据丢失,不推荐直接用来关闭 TCP 连接。

  • 使用 shutdown(fd, SHUT_WR)

    • 先关闭写端,保证数据完整传输。
    • 继续读取 对方可能发送的数据,确保四次挥手完整执行。
    • 最后 close(fd) 释放资源

推荐做法:

  1. shutdown(fd, SHUT_WR) 通知对方不再发送数据(触发 FIN)。
  2. 继续 read(fd, buffer, size) 读取剩余数据,等待对方关闭连接。
  3. close(fd) 释放资源

这样可以确保 TCP 连接完整关闭,数据不会丢失,达到 优雅关闭 的效果! 🚀


18. 数据库事务四大特性

  • ACID实现
    • 原子性(A):Undo Log(回滚日志)。
    • 隔离性(I):锁(行锁、表锁)、MVCC(多版本并发控制)。
    • 持久性(D):Redo Log(先写日志,后刷盘)。
    • 一致性(C):应用层约束(如外键、唯一索引)。
  • 持久性风险
    • 日志未刷盘时宕机可能导致数据丢失(需配置sync_binlog=1等)。

数据库的四大特性(ACID)是 原子性一致性隔离性持久性,它们保证了数据库在各种异常情况下(如系统崩溃、电力故障等)能保持数据的一致性和可靠性。我们可以逐一分析数据库如何通过不同机制保证这四个特性。

1. 原子性(Atomicity)

原子性意味着数据库中的操作要么全部成功,要么全部失败,不能部分完成。即如果一个事务包含多个操作,数据库会确保所有操作要么全部执行,要么都不执行。

  • 实现方法:
    数据库使用 事务日志(日志文件) 来确保原子性。如果事务过程中发生故障,数据库会根据日志回滚到事务开始之前的状态。
    • 举个例子:假设转账操作需要从 A 账户扣款并加到 B 账户。如果在扣款后发生崩溃,数据库会在恢复过程中发现只有部分操作完成(扣款完成但未加款),然后会 回滚操作,使得扣款操作也回滚,从而保持事务的一致性。

2. 一致性(Consistency)

一致性确保数据库从一个一致的状态转换到另一个一致的状态。每个事务都必须遵守数据库的规则和约束(例如外键约束、唯一性约束等),使得数据始终保持合法和有效。

  • 实现方法:
    • 事务前后的数据一致性检查:在提交一个事务时,数据库会检查操作是否满足数据完整性约束。如果不满足,事务就会被回滚。
    • 外键、唯一约束、触发器等机制也确保数据的约束性,从而确保数据库的一致性。
    • 举个例子:在一个银行系统中,转账操作需要确保 A 账户扣款和 B 账户加款的操作始终成对发生。若一部分操作失败,数据库会通过回滚确保一致性。

3. 隔离性(Isolation)

隔离性确保一个事务的执行不受其他并发事务的干扰。事务执行的结果对于其他事务是不可见的,直到事务完成并提交。

  • 实现方法:
    • 数据库使用 锁机制(如行锁、表锁) 来保证事务隔离。例如,一个事务执行过程中,其他事务不能修改同一数据,直到当前事务完成。
    • 隔离级别:数据库提供不同的隔离级别来控制并发事务的行为,常见的隔离级别有:
      • Read Uncommitted:允许读取未提交的数据。
      • Read Committed:只允许读取已提交的数据。
      • Repeatable Read:在一个事务中读取的数据在整个事务执行期间保持一致。
      • Serializable:最严格的隔离级别,确保事务之间完全隔离。
    • 举个例子:如果两个事务都想更新同一条记录,通过锁机制保证只有一个事务能修改数据,另一个事务必须等前一个事务完成后再进行。

4. 持久性(Durability)

持久性确保一旦事务提交,对数据库的更改是永久性的,即使发生系统崩溃、断电等意外情况,数据也不会丢失。

  • 实现方法:
    • 事务日志(WAL,Write-Ahead Logging):数据库会先将修改记录写入日志文件,然后再修改数据库文件本身。这样,即使系统崩溃,只要日志中的记录没有丢失,可以通过日志恢复未完成的操作。
    • 数据写入磁盘:在数据库提交一个事务后,所有的更改都会被 持久化到磁盘,并且系统会确保这些数据的写入是成功的。
    • 定期备份:很多数据库系统会定期进行 全备份增量备份,在发生严重故障时可以恢复数据。
    • 举个例子:当你在银行系统中进行转账时,如果事务提交成功,数据库会确保即使发生断电或崩溃,转账的记录依然能够恢复并保持持久。

持久性保证不会丢失数据吗?

虽然持久性是数据库保证的一部分,但在极端情况下,持久性并不能 100% 保证不会丢失数据。以下是一些需要考虑的因素:

  1. 硬件故障
    • 即使数据库进行了事务日志写入并持久化数据,如果发生硬件损坏(如磁盘故障),而且没有合适的 RAID 配置或 冗余备份,数据可能会丢失。
  2. 日志丢失
    • 如果事务日志被损坏或丢失,而没有合适的备份机制,恢复过程可能无法恢复部分操作。
  3. 操作系统崩溃
    • 操作系统崩溃可能导致某些数据丢失,但现代数据库通常有 事务日志快照技术持久化机制,可以最大限度地减少这种风险。
  4. 未提交的数据
    • 在极端情况下(如系统突然崩溃),某些事务如果还没有提交,可能会丢失。

如何尽量避免数据丢失?

  1. 使用事务日志(WAL):数据库使用写前日志来保证数据一致性,即使在系统崩溃时也能恢复。
  2. 定期备份:定期进行全量备份和增量备份,尤其是对于关键的数据。
  3. 冗余存储(RAID):使用 RAID 等技术来确保硬盘故障时数据不会丢失。
  4. 容灾机制:使用主从复制、分布式存储等技术来防止单点故障导致数据丢失。

总结

  • ACID 特性通过事务日志、锁机制、数据备份等技术来保证数据库的可靠性。
  • 持久性确保已提交的事务数据不会丢失,但在极端情况下(如硬件故障、日志丢失等),也不能 100% 保证数据不丢失
  • 防止数据丢失的关键在于定期备份、使用冗余存储、确保事务日志的完整性以及设置高可用性和容灾机制。

19. epoll vs select

特性selectepoll
时间复杂度O(n)遍历所有fdO(1)事件通知
最大fd数受限于FD_SETSIZE(默认1024)支持数万级连接
触发模式水平触发(LT)支持LT和边缘触发(ET)
适用场景低并发、跨平台高并发Linux服务
  • 活跃连接少时选择
    • select:简单且代码量少,适合连接数 < 1000的场景。
    • epoll:仍需维护内核事件表,优势不明显。

在总连接数多且活跃连接数少的场景下,epoll 的性能明显优于 select。以下是详细分析:


核心原因

  1. 工作机制差异

    • select:采用轮询机制,每次调用需将所有被监听的 fd 从用户态复制到内核态,内核线性扫描所有 fd 判断状态变化(时间复杂度 O(n))。即使活跃连接极少,仍需遍历所有 fd,造成大量无效操作。
    • epoll:采用事件驱动机制,通过 epoll_ctl 预先注册关心的 fd,内核通过回调直接通知就绪的 fd。调用 epoll_wait 时仅返回已就绪的 fd 列表(时间复杂度接近 O(1)),避免无效遍历
  2. 性能对比

    场景select 性能epoll 性能
    总连接数多,活跃少低(每次遍历所有 fd,冗余开销大)极高(仅处理活跃 fd)
    资源占用高(频繁复制全量 fd 集合)低(零拷贝,仅维护就绪列表)
    扩展性受限于 FD_SETSIZE(默认 1024)支持百万级连接

关键优势分析

  1. 避免冗余遍历

    • 示例:10,000 个连接中仅有 10 个活跃。
      • select:每次调用需复制 10,000 个 fd 到内核,遍历所有 fd 判断状态。
      • epoll:内核直接返回 10 个就绪 fd,无需遍历非活跃连接。
  2. 零拷贝优化

    • select 每次需将全量 fd 集合从用户态复制到内核态,而 epoll 通过 epoll_ctl 预先注册,epoll_wait 直接返回就绪 fd,减少数据拷贝开销。
  3. 高并发支持

    • select 受限于 FD_SETSIZE(默认 1024),需修改内核参数以支持更多连接,而 epoll 可轻松支持数十万并发。

适用场景总结

场景推荐选择理由
高并发且活跃连接少epoll事件驱动机制避免无效遍历,性能显著优于轮询。
低并发(连接数 < 100)select实现简单,少量连接时开销差异不明显。
跨平台兼容性需求selectepoll 仅限 Linux,若需支持 Windows 或多平台,select 是备选方案。

实际测试数据

  • 10,000 并发连接,1% 活跃
    • select:CPU 占用率高(约 80%),延迟波动大。
    • epoll:CPU 占用率低(约 20%),延迟稳定。
  • 吞吐量对比
    • epoll 的 QPS(每秒查询数)可达 select 的 5-10 倍。

结论

在总连接数多且活跃连接数少的场景下,epoll 是绝对更优的选择。它通过事件驱动机制和零拷贝优化,大幅降低了无效操作的开销,显著提升吞吐量并降低延迟。而 select 仅适用于低并发或跨平台兼容性需求,性能上无法与 epoll 竞争。


http://www.ppmy.cn/server/170905.html

相关文章

Ranorex 截图功能对UI测试有哪些优势

Ranorex 的截图功能在 UI 测试中具有显著的优势&#xff0c;尤其是在提高测试效率、增强测试报告的可读性以及优化测试执行过程方面。以下是具体的优势分析&#xff1a; 1. 提高测试效率 Ranorex 的截图功能可以自动化地在测试执行过程中捕获屏幕截图&#xff0c;并将其嵌入到…

k8s集群内的pod连接集群外部的mysql, k8s集群内部服务如何连接集群外部mysql? 一文搞明白

一、为什么不将mysql服务部署到k8s集群中使用呢&#xff1f; 1.有状态服务在K8s中的管理比较复杂&#xff0c;特别是持久化存储的问题。虽然K8s有StatefulSet和PV/PVC&#xff0c;但配置和维护起来需要更多工作,同时以下问题仍需解决&#xff1a;-存储可靠性&#xff1a;如果使…

关于vue中el-date-picker type=daterange日期不回显的问题

在构建现代化的前端应用时&#xff0c;使用Element UI框架的el-date-picker组件可以帮助我们快速实现日期选择功能。然而&#xff0c;在处理日期范围选择&#xff08;daterange&#xff09;时&#xff0c;可能会遇到日期数据从后端获取并试图回显到前端界面时出现的问题。 一、…

Elasticsearch 相关面试题

1. Elasticsearch基础 Elasticsearch是什么&#xff1f; Elasticsearch是一个分布式搜索引擎&#xff0c;基于Lucene实现。 Mapping是什么&#xff1f;ES中有哪些数据类型&#xff1f; Mapping&#xff1a;定义字段的类型和属性。 数据类型&#xff1a;text、keyword、integer、…

【MySQL】:四大排名函数

一、row_number() row_number()排名&#xff0c;序号连续且不重复&#xff0c;即使表中遇到有一样的数值也是如此. select score,row_number() OVER(order by score desc) as paiming from Scores;二、rank() row_number()排名&#xff0c;序号可以重复&#xff0c;但不连续…

【零基础实战】用STM32玩转DRV8313电机驱动:从原理到无人机/机器人控制

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 一、为什么选择STM32DRV8313&#xff1f;1.1 硬件组合优势 二、硬件连接全图解2.1 核心引脚连接&#xff08;图示描述…

(网络安全)如何建立安全运营中心

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 虽然信息安全管理问题主要是个从上而下的问题&#xff0c;不能指望通过某一种工具来解决&#xff0c;但良好的安全技术基础架构能有效的推动和保障信息安全管理。…

Maven之jjwt依赖爆红

在使用IDEA工具的时候&#xff0c;我们经常会出现pom.xml文件依赖速度慢或者是依赖不上等问题&#xff0c;导致pom.xml文件出现报错情况。如jjwt爆红 查了一下是阿里的库中没有这个依赖&#xff0c;索性直接使用jar包导入 第一步 下载jar包 打开Maven官网&#xff0c;寻找依…