TCP/IP c/c++

一、TCP/IP 基础概念(必问)

这部分是面试的 "打底题",考察对网络基础的理解,常以问答形式出现:

1. TCP/IP 协议栈分层

TCP/IP 4 层模型 对应 OSI 7 层 核心协议 / 功能
应用层 应用层 + 表示层 + 会话层 HTTP/FTP/Telnet,定义应用交互规则
传输层 传输层 TCP/UDP,端到端数据传输(端口标识)
网络层 网络层 IP/ICMP/ARP,跨网络路由(IP 地址标识)
链路层 数据链路层 + 物理层 以太网 / PPP,局域网内帧传输(MAC 地址)

2. TCP vs UDP 核心区别(高频)

特性 TCP UDP
连接性 面向连接(三次握手) 无连接
可靠性 可靠(确认 / 重传 / 排序) 不可靠(无确认)
传输方式 面向字节流 面向数据报
拥塞 / 流量控制 支持(滑动窗口 / 拥塞控制) 不支持
头部开销 20-60 字节 8 字节
适用场景 文件传输 / HTTP / 邮件 视频 / 语音 / 直播 / 游戏

3. 端口范围

  • 知名端口:0~1023(如 80=HTTP,443=HTTPS,22=SSH);
  • 注册端口:1024~49151;
  • 动态 / 私有端口:49152~65535(客户端随机使用)。

二、TCP 核心原理(面试重点)

这部分是考察深度的核心,常问 "为什么" 和 "过程":

1. TCP 三次握手(为什么需要三次?)

过程(服务端先listen):
  1. 客户端 → 服务端:SYN(同步序列号),客户端进入SYN_SENT状态;
  2. 服务端 → 客户端:SYN+ACK(确认客户端 SYN,同步自己的序列号),服务端进入SYN_RCVD状态;
  3. 客户端 → 服务端:ACK(确认服务端 SYN),双方进入ESTABLISHED状态。
核心问题:为什么不是两次?
  • 两次握手无法确认 "客户端能收到服务端的报文",可能导致服务端为无效客户端分配资源(半连接队列溢出);
  • 三次握手能确保双方收发能力都正常,且完成序列号同步。

2. TCP 四次挥手(为什么需要四次?)

过程:
  1. 主动关闭方 → 被动方:FIN(请求关闭),主动方进入FIN_WAIT1
  2. 被动方 → 主动方:ACK(确认 FIN),被动方进入CLOSE_WAIT,主动方进入FIN_WAIT2
  3. 被动方 → 主动方:FIN(被动方数据发送完毕,请求关闭),被动方进入LAST_ACK
  4. 主动方 → 被动方:ACK(确认 FIN),主动方进入TIME_WAIT,被动方收到后进入CLOSED
  5. 主动方等待2MSL(报文最大生存时间)后进入CLOSED
核心问题:为什么不是三次?
  • TCP 是全双工通信,关闭时需要分别关闭 "发送" 和 "接收" 通道,被动方收到 FIN 后可能还有数据要发,因此需要先 ACK 确认关闭,等数据发完再发 FIN。

3. TIME_WAIT 状态(高频)

  • 作用:① 确保最后一个 ACK 到达(若被动方没收到 ACK,会重发 FIN);② 避免旧连接的延迟数据包干扰新连接(2MSL 可让旧数据包失效)。
  • 问题:TIME_WAIT 过多会占用端口,导致新连接无法绑定;
  • 解决 :设置SO_REUSEADDR选项(允许端口快速重用)。

4. TCP 可靠性机制(核心)

  • 确认应答(ACK):接收方收到数据后返回确认;
  • 超时重传:发送方超时未收到 ACK 则重传(超时时间动态计算);
  • 滑动窗口:实现流量控制(接收方通过窗口大小告知发送方可发送的字节数);
  • 拥塞控制:慢启动→拥塞避免→快重传→快恢复(避免网络拥塞);
  • 序列号 / 确认号:解决乱序、重复问题;
  • 校验和:检测数据传输中的错误。

三、C/C++ Socket 编程(代码实践必问)

1. 核心流程(服务端 + 客户端)

服务端(TCP 回显服务器示例):

cpp

运行

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUF_SIZE 1024

int main() {
    // 1. 创建socket(文件描述符)
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置SO_REUSEADDR,解决TIME_WAIT占用端口
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2. 绑定IP和端口
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;         // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
    server_addr.sin_port = htons(PORT);       // 端口转换(主机序→网络序)

    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind failed");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 监听连接(半连接队列大小设为5)
    if (listen(listen_fd, 5) == -1) {
        perror("listen failed");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);

    // 4. 接受客户端连接(阻塞)
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
    if (conn_fd == -1) {
        perror("accept failed");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }
    printf("Client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    // 5. 收发数据(回显)
    char buf[BUF_SIZE];
    ssize_t recv_len;
    while ((recv_len = recv(conn_fd, buf, BUF_SIZE-1, 0)) > 0) {
        buf[recv_len] = '\0';
        printf("Received from client: %s\n", buf);
        send(conn_fd, buf, recv_len, 0); // 回显数据
    }

    // 6. 关闭连接
    close(conn_fd);
    close(listen_fd);
    return 0;
}
客户端示例:

cpp

运行

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUF_SIZE 1024

int main() {
    // 1. 创建socket
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 连接服务端
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_addr.sin_port = htons(SERVER_PORT);

    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect failed");
        close(client_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 发送数据
    char buf[BUF_SIZE];
    printf("Enter message to send: ");
    fgets(buf, BUF_SIZE, stdin);
    send(client_fd, buf, strlen(buf)-1, 0); // 去掉fgets的换行符

    // 4. 接收回显数据
    ssize_t recv_len = recv(client_fd, buf, BUF_SIZE-1, 0);
    if (recv_len > 0) {
        buf[recv_len] = '\0';
        printf("Received from server: %s\n", buf);
    }

    // 5. 关闭连接
    close(client_fd);
    return 0;
}

2. 核心函数 / 知识点解析

(1)字节序转换(必懂)
  • 主机序:多数 CPU 是小端序(低字节存低地址);
  • 网络序:强制大端序
  • 核心函数:
    • htons():主机序 → 网络序(短整型,如端口);
    • htonl():主机序 → 网络序(长整型,如 IP);
    • ntohs()/ntohl():反向转换。
(2)地址转换
  • 旧接口:inet_addr("127.0.0.1")(字符串→IP)、inet_ntoa(in_addr)(IP→字符串);
  • 新接口(推荐):inet_pton()/inet_ntop()(支持 IPv6,更安全)。
(3)IO 多路复用(高性能核心,面试重中之重)
模型 原理 缺点 优点
select 监听 fd_set 集合,轮询检查 上限 FD_SETSIZE(默认 1024)、需重置集合 跨平台
poll 监听 pollfd 数组,轮询检查 轮询效率低 无 fd 数量上限
epoll 红黑树 + 就绪链表,事件驱动 仅 Linux 支持 无 fd 上限、无需轮询、高性能

epoll 核心考点

  • 水平触发(LT,默认):只要 fd 有数据 / 可写,就持续通知;
  • 边缘触发(ET):仅当 fd 状态变化时通知(需配合非阻塞 socket,一次性读完数据);
  • 常用 API:epoll_create()epoll_ctl()(添加 / 修改 / 删除事件)、epoll_wait()(等待事件)。

3. 常见编程问题(面试常考)

(1)TCP 粘包 / 拆包(高频编程题)
  • 原因:TCP 是面向字节流,无数据边界,底层会根据 MTU 拆分 / 合并数据;
  • 解决方案 (代码级必实现):
    1. 固定长度:每次发送固定字节数,接收方按固定长度读取;
    2. 分隔符:如\n,接收方按分隔符分割数据;
    3. 消息头 + 消息体:头中包含数据长度,接收方先读长度,再读对应字节数(最常用)。
(2)Socket 超时设置
  • 场景:connect()/recv()/send()默认阻塞,需设置超时;

  • 方法:setsockopt()设置SO_RCVTIMEO/SO_SNDTIMEO

    cpp

    运行

    复制代码
    struct timeval timeout;
    timeout.tv_sec = 5;  // 5秒超时
    timeout.tv_usec = 0;
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
(3)并发处理方式
  • 多进程:fork(),父子进程共享 fd(需关闭多余 fd),处理僵尸进程(waitpid());
  • 多线程:pthread_create(),注意线程安全(如 fd 操作加锁);
  • IO 多路复用:epoll(高性能,单进程处理上万连接)。

四、高频面试题(问答 + 编程)

  1. 解释 TCP 三次握手 / 四次挥手的过程和意义;
  2. TIME_WAIT 的作用和解决过多的方法;
  3. 实现 TCP 回显服务器(要求处理粘包);
  4. select/poll/epoll 的区别,epoll LT/ET 的区别;
  5. TCP 粘包的原因和解决方案;
  6. 如何设置 Socket 的超时时间;
  7. SO_REUSEADDR 的作用;
  8. TCP 拥塞控制的流程(慢启动、拥塞避免);
  9. 为什么 UDP 适合直播 / 游戏,TCP 适合文件传输;
  10. 用 epoll 实现一个简单的高并发 TCP 服务器。

总结

  1. 核心基础:掌握 TCP/UDP 区别、TCP 三次握手 / 四次挥手、TIME_WAIT、可靠性机制;
  2. 编程实践:熟练掌握 C/C++ Socket 核心流程(socket/bind/listen/accept/connect)、字节序转换、IO 多路复用(尤其是 epoll);
  3. 高频问题:TCP 粘包处理、Socket 超时设置、SO_REUSEADDR、epoll LT/ET 是面试中最常考察的代码级考点。
相关推荐
Vect__3 小时前
基于线程池从零实现TCP计算器网络服务
c++·网络协议·tcp/ip
wenzhangli78 小时前
OoderAgent SDK(0.6.6) UDP通讯与协议测试深度解析
网络·网络协议·udp
naruto_lnq8 小时前
分布式系统安全通信
开发语言·c++·算法
安科士andxe8 小时前
60km 远距离通信新选择:AndXe SFP-155M 单模单纤光模块深度测评
网络·信息与通信
酥暮沐9 小时前
iscsi部署网络存储
linux·网络·存储·iscsi
darkb1rd9 小时前
四、PHP文件包含漏洞深度解析
网络·安全·php
迎仔10 小时前
02-网络硬件设备详解:从大喇叭到算力工厂的进化
网络·智能路由器
CSDN_RTKLIB10 小时前
【四个场景测试】源文件编码UTF-8 BOM
c++
嘿起屁儿整10 小时前
面试点(网络层面)
前端·网络
serve the people10 小时前
python环境搭建 (十二) pydantic和pydantic-settings类型验证与解析
java·网络·python