一文带你深入了解TCP socket 和 TCP可靠性
一文带你深入了解TCP socket 和 TCP可靠性文章目录一文带你深入了解TCP socket 和 TCP可靠性一、详细解答图中的问题1. listen 参数的意义2. accept 是在哪一步3. accept 只是从队列取元素4. 没有 accept 还能连接吗5. 没有 listen 还能连接吗6. 自连接与哈希表7. 客户端 close 后的流程二、相关面试题总结1. 基础概念类2. 连接队列类3. 异常场景类4. 性能与调优类5. 源码与原理类三、总结图意Socket 编程各个步骤的作用详解一、服务端 Socket 步骤1. socket() —— 创建套接字2. bind() —— 绑定地址和端口3. listen() —— 监听连接4. accept() —— 接受连接5. recv()/send() —— 读写数据6. close() —— 关闭连接二、客户端 Socket 步骤1. socket() —— 创建套接字2. connect() —— 连接服务器3. send()/recv() —— 读写数据4. close() —— 关闭连接三、完整流程图四、常见面试问题1. accept 发生在三次握手的哪个阶段2. 如果 backlog 设置太小会怎样3. bind 不指定端口会怎样4. 一个端口可以建立多个连接吗5. close 和 shutdown 的区别TCP可靠性1. 确认应答2. 超时重传3. 序列号与去重4. 校验和5. 流量控制6. 拥塞控制总结这张图涵盖了TCP 连接建立与断开的核心过程以及一些socket 编程的常见考点。下面我来逐一详细解答图中提出的问题并总结相关的面试题。一、详细解答图中的问题1.listen参数的意义函数原型int listen(int sockfd, int backlog);backlog的作用指定内核为该 socket 排队的最大连接数。它影响两个队列的大小未完成连接队列incomplete connection queue存储处于SYN_RCVD状态的连接收到了 SYN还未完成三次握手。已完成连接队列completed connection queue存储处于ESTABLISHED状态的连接完成了三次握手但还未被accept取走。backlog的历史含义变化早期系统表示两个队列总和的最大值。现在Linux通常指已完成连接队列的最大长度。注意如果队列满了新的连接请求可能会被忽略或拒绝取决于系统实现。2.accept是在哪一步accept发生在三次握手完成之后。流程客户端调用connect发送 SYN。服务器内核响应 SYNACK。客户端发送 ACK三次握手完成连接进入已完成连接队列。服务器调用accept从已完成连接队列中取出一个连接返回一个新的 socket 描述符用于通信。结论accept不参与握手只是从队列中取一个已经建立好的连接。3.accept只是从队列取元素是的。当三次握手完成后连接已经建立状态为ESTABLISHED但还在内核的已完成连接队列中。accept的作用从已完成队列中取出一个连接项。为该连接创建一个新的 socket 描述符。将连接交给用户进程使用。4. 没有accept还能连接吗能。即使服务器不调用accept只要调用了listen内核仍然可以完成三次握手并将连接放入已完成连接队列。但如果队列满了后续的连接可能会失败。结论accept是用户态取走连接的操作不调用它不影响握手过程。5. 没有listen还能连接吗不能。如果服务器只创建了 socket但没有调用listen该 socket 是一个主动 socket。此时客户端发起connect由于服务器没有监听端口内核会返回 RST 包连接失败。例外如果是客户端主动连接服务器当然不需要listen但作为服务端必须调用listen才能接受连接。6. 自连接与哈希表自连接指的是客户端和服务器在同一台机器上且连接到自己。例如客户端连接127.0.0.1:port而该端口正好被同一个进程的另一个 socket 监听。原因TCP 连接的唯一标识是四元组源 IP、源端口、目的 IP、目的端口。如果客户端选择了与服务器相同的 IP 和端口就可能出现“自己连自己”的现象。哈希表内核中通过哈希表来管理所有连接struct sock。查找连接时如收到数据包通过四元组哈希快速定位对应的 socket。这也是为什么端口可以重用、多个连接可以共享一个监听端口的原因。7. 客户端 close 后的流程客户端调用close发送 FIN 给服务器进入FIN_WAIT_1状态。服务器收到 FIN内核返回 EOF 给用户进程read返回 0。服务器可能调用close发送 FIN。四次挥手完成客户端最终进入TIME_WAIT状态等待 2MSL。注意事项如果服务器没有及时read数据客户端关闭后服务器仍在发送数据会导致对方发送 RST。如果服务器没有调用close连接会一直处于CLOSE_WAIT状态。二、相关面试题总结1. 基础概念类TCP 三次握手发生在哪一步答发生在connect和accept之间由内核自动完成。listen的 backlog 参数的作用答指定已完成连接队列的大小影响并发连接数。accept返回的是新 socket 吗答是的用于和客户端通信。2. 连接队列类如果 backlog 设置太小会有什么问题答已完成连接队列容易满新连接可能被拒绝或丢失。如何查看当前连接队列大小答ss -lnt查看Send-Q和Recv-Q。连接队列满了怎么办答客户端可能收到 ECONNREFUSED 或连接超时。3. 异常场景类如果服务器只 listen不 accept客户端能连接成功吗答能完成三次握手但连接停留在已完成队列中。如果客户端 connect 后立即 close服务器会怎样答服务器 accept 后会收到 EOFread 返回 0。什么是 CLOSE_WAIT 和 TIME_WAIT答CLOSE_WAIT被动关闭方收到 FIN 后未调用 close。TIME_WAIT主动关闭方发送最后一个 ACK 后等待 2MSL。4. 性能与调优类高并发下如何优化 accept 性能答使用多线程/多进程、epoll 边缘触发、设置非阻塞、调整 backlog。什么是惊群效应如何解决答多个进程/线程同时等待 accept连接到来时全部唤醒只有一个成功。解决SO_REUSEPORT 或使用 epoll 的 EPOLLEXCLUSIVE。5. 源码与原理类Linux 内核中如何管理连接答通过哈希表连接四元组和队列监听队列、accept 队列。如何实现端口重用答设置SO_REUSEADDR或SO_REUSEPORT内核通过哈希表区分不同连接。三、总结图意这张图的核心思想是连接建立的过程是内核完成的用户态只负责发起connect和取走accept。队列是缓冲机制让内核能异步处理连接请求。异常场景和状态变化是面试中考察对 TCP 协议栈理解的重点。如果你需要更深入的源码分析或代码示例我也可以帮你补充。Socket 编程各个步骤的作用详解我将从服务端和客户端两个角度详细讲解每一步的作用、底层原理和常见问题。一、服务端 Socket 步骤1.socket()—— 创建套接字intsockfdsocket(AF_INET,SOCK_STREAM,0);作用在操作系统内核中创建一个网络通信端点返回一个文件描述符后续所有操作都通过它参数含义AF_INETIPv4 协议族SOCK_STREAMTCP 流式套接字0自动选择协议TCP底层原理内核分配 socket 结构体初始化发送缓冲区、接收缓冲区分配文件描述符并关联到 socket2.bind()—— 绑定地址和端口structsockaddr_inaddr;addr.sin_familyAF_INET;addr.sin_addr.s_addrINADDR_ANY;// 监听所有网卡addr.sin_porthtons(8080);// 端口号bind(sockfd,(structsockaddr*)addr,sizeof(addr));作用将 socket 绑定到特定的 IP 地址和端口让操作系统知道发往这个端口的数据交给这个 socket为什么需要 bind如果不 bind内核会自动分配一个随机端口客户端适用服务端必须绑定知名端口如 80、443客户端才能找到它常见问题Address already in use端口被占用可用SO_REUSEADDR解决绑定 0.0.0.0INADDR_ANY表示监听所有网卡3.listen()—— 监听连接listen(sockfd,10);// 10 是 backlog 大小作用将 socket 从主动模式转为被动模式告诉内核可以接收连接请求了创建两个队列未完成连接队列SYN_RCVD 状态已完成连接队列ESTABLISHED 状态backlog 参数表示已完成连接队列的最大长度超过此长度的连接会被拒绝或忽略底层原理内核开始监听 TCP 端口的 SYN 报文三次握手完成后连接放入已完成队列4.accept()—— 接受连接structsockaddr_inclient_addr;socklen_tlensizeof(client_addr);intclient_fdaccept(sockfd,(structsockaddr*)client_addr,len);作用从已完成连接队列中取出一个连接创建新的 socket 文件描述符用于和客户端通信返回新 socket原 socket 继续监听为什么返回新 socket监听 socket只负责接收新连接新 socket专门负责和这个客户端通信这样监听 socket 可以继续 accept 其他连接阻塞特性默认是阻塞的如果没有新连接进程会睡眠等待可以设置为非阻塞fcntl或O_NONBLOCK5.recv()/send()—— 读写数据charbuffer[1024];intnrecv(client_fd,buffer,sizeof(buffer),0);send(client_fd,OK,2,0);recv() 作用从内核接收缓冲区拷贝数据到用户空间返回值0实际收到的字节数0对方关闭了连接FIN0出错如连接重置send() 作用将用户数据拷贝到内核发送缓冲区真正的发送由内核 TCP 协议栈完成返回值成功返回发送的字节数可能小于请求的长度失败返回 -1底层原理TCP 是流式协议recv/send 可以分多次读取/发送发送缓冲区满时send 会阻塞或返回部分数据6.close()—— 关闭连接close(client_fd);// 关闭通信 socketclose(sockfd);// 关闭监听 socket作用减少 socket 的引用计数当引用计数为 0 时触发四次挥手释放内核资源注意close 后 socket 不能再读写如果有多个进程/线程共享 socket需要所有 close 才会真正关闭可以使用shutdown()实现半关闭二、客户端 Socket 步骤1.socket()—— 创建套接字intsockfdsocket(AF_INET,SOCK_STREAM,0);同服务端创建一个 socket 文件描述符。2.connect()—— 连接服务器structsockaddr_inserver_addr;server_addr.sin_familyAF_INET;server_addr.sin_porthtons(8080);inet_pton(AF_INET,192.168.1.100,server_addr.sin_addr);connect(sockfd,(structsockaddr*)server_addr,sizeof(server_addr));作用向服务器发起 TCP 三次握手指定服务器的 IP 和端口底层原理客户端发送 SYN 报文等待服务器 SYNACK发送 ACK完成握手返回值0连接成功-1连接失败如服务器没开、网络不通自动绑定如果之前没有 bindconnect 会自动 bind 一个随机端口3.send()/recv()—— 读写数据send(sockfd,GET / HTTP/1.1\r\n,16,0);recv(sockfd,buffer,sizeof(buffer),0);同服务端通过 socket 发送和接收数据。4.close()—— 关闭连接close(sockfd);同服务端释放资源触发四次挥手。三、完整流程图服务端 客户端 socket() ────────────────────────────── socket() ↓ ↓ bind() ↓ ↓ ↓ listen() ↓ ↓ ↓ accept() (阻塞等待) ←─────────────── connect() (发起连接) ↓ ↓ ↓←─────────── 三次握手 ────────────────→↓ ↓ ↓ recv() ←──────────────────────────────── send() ↓ ↓ send() ────────────────────────────────→ recv() ↓ ↓ close() ←─────────── 四次挥手 ─────────── close()四、常见面试问题1. accept 发生在三次握手的哪个阶段答案三次握手完成后连接放入已完成队列accept 只是从队列中取出。所以accept 在三次握手之后。2. 如果 backlog 设置太小会怎样高并发下已完成队列满新连接会被拒绝客户端收到 ECONNREFUSED 或连接超时可以通过调整系统参数net.core.somaxconn增大3. bind 不指定端口会怎样内核自动分配一个临时端口通常 1024-65535这是客户端的行为服务端必须指定端口4. 一个端口可以建立多个连接吗可以。TCP 连接由四元组唯一确定(源IP, 源端口, 目标IP, 目标端口)同一个服务端端口可以对应多个客户端连接。5. close 和 shutdown 的区别closeshutdown关闭整个 socket可选择关闭读/写/读写有引用计数无引用计数立即返回后台挥手立即发送 FIN不能再读写根据关闭方向限制如果你需要我画出Socket 状态变迁图或内核数据结构图也可以告诉我。TCP可靠性你提到的这几点非常核心。下面我来详细拆解TCP可靠性的具体实现机制这对面试和深入理解网络原理都很有帮助。1. 确认应答这是可靠性的基础。接收方收到数据后会回复一个ACK确认报文告诉发送方“我收到数据了”。如果发送方没收到ACK就知道数据可能需要重传。2. 超时重传发送方发完数据会启动一个定时器。如果超时没收到ACK就认为数据丢失然后重新发送。这个超时时间RTO是动态计算的基于网络往返时间RTT太短会导致重传太长会降低效率。3. 序列号与去重每个字节都有唯一序号接收方用它把乱序的数据排回正确顺序。同时如果收到重复数据比如因为ACK丢失导致的重传序列号也能让接收方识别并丢弃重复包。4. 校验和发送方计算数据校验和并存入报头接收方重新计算对比。如果数据在传输中被篡改校验和不匹配这个包就会被直接丢弃等待发送方超时重传。5. 流量控制由接收方主导避免发送太快撑爆接收方缓存。接收方在ACK里带上自己的窗口大小告诉发送方“我还能收多少数据你别发太多。”发送方就根据这个调整速度这就是滑动窗口机制。6. 拥塞控制由发送方主动探测避免网络“堵车”。主要包含四个核心算法慢启动连接刚建立时发送方先试探性地发少量数据。每收到一个ACK就把发送窗口翻倍窗口大小指数级增长快速找到网络吞吐量的上限。拥塞避免当窗口增长到阈值ssthresh后增长速度由“翻倍”改为“1”的线性增长避免一下子把网络堵死。快重传如果收到3个相同的ACK比如接收方缺了某个包后续包都触发这个ACK发送方不等超时立即重传疑似丢失的包效率更高。快恢复配合快重传。发生丢包后不直接降到最低点而是把拥塞窗口减半并进入拥塞避免状态相比超时重传性能更好。总结一下如果说确认应答超时重传序列号校验和是保证“数据能到且是对的”那么流量控制和拥塞控制就是保证“传输过程又快又稳”。TCP的可靠性就是通过这套确认、重传、校验、限流的组合拳实现的。如果你还想了解这些机制在实际网络故障如延迟高、丢包中是如何具体表现的我可以继续为你讲解。总结这篇文章是作者搜集大量面经和资料这里出来的。感谢你的支持作者wkm是一名中国矿业大学(北京) 大一的新生希望得到你的关注如果可以的话记得一键三联