Linux Socket 编程
看清 socket 调用内核路径、TCP 状态迁移条件、epoll 触发差异
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
Linux Socket 编程
看清 socket 调用内核路径、TCP 状态迁移条件、epoll 触发差异
什么是 Socket
上一节我们知道要写程序让两台机器对话。这一节解决第一个问题:进程之间怎么互相找到对方?答案就是网络上的一个「端点」——Socket。
插座定位房间,Socket 定位进程;插上话机才能通话
Socket 通信模型
自上而下五层:用户进程、Socket 接口、内核协议栈、TCP/IP 网络、对端镜像。数据沿箭头方向穿越。
UDP 是什么
上一页我们讲了 Socket 通信模型,要真正把字节从一头送到另一头还得选协议。UDP 就是其中一种——它根本不握手,发完就走,不问对方收没收到。
没回执、没签收、丢了不补——UDP 也不确认、不重传、不排序
UDP vs TCP 核心区别
同为传输层协议,但「建连接」「保可靠」「管流量」的哲学截然相反——混用会埋坑。
- 三次握手建连接,四次挥手断开——先 accept() 再通信
- 可靠:超时重传、序号去重、ACK 确认一应俱全
- 字节流无边界,read() 一次可能横跨多次 send
- 滑动窗口限速 + 拥塞算法(CUBIC/BBR)自适应网络
- 无连接,sendto() 直接发,不握手不挥手
- 不可靠:丢包、乱序、重复全交应用层处理
- 保留消息边界,一次 recvfrom() 对应一次 sendto()
- 无流量、无拥塞控制,发多快由应用自己决定
UDP 通信时序图
UDP 无连接:两端各自建 socket,sendto 直发、recvfrom 直收,沿箭头看数据走向。
UDP 服务端代码
用最简回显服务,把 socket→bind→recvfrom→sendto→close 一行一行跑通。
高亮行正是服务端 5 个核心系统调用:建端点、绑端口、收数据、回数据、关连接。
UDP 客户端代码
四步走完 UDP 客户端:socket→sendto→recvfrom→close
socket 建通道、sendto 主动发、recvfrom 阻塞收、close 释放,覆盖 UDP 客户端完整生命周期。
UDP 代码执行流程
一次 UDP 交互中,服务端与客户端的关键系统调用按什么顺序触发。
关键 API 详解
上页代码跑起来了,但 sendto 那一长串参数是否还有点恍惚?fd、buf、len、flags、addr、alen——每个位置到底在问什么?出错时返回值又该怎么看?这页把它们逐个拆开。
fd=快递员、buf=包裹、len=重量、flags=加急、addr=收件地址、alen=地址栏长度
UDP 调试基础
代码写完了,怎么快速验证它能收发?最轻量的工具是 netcat——一行命令起服务端、一行命令发数据,比再写个测试程序省事得多。
对讲机按下就发、收到就响,没有'建立通话'步骤;nc -u 一样,不握手直接收发
Wireshark 抓包分析
用 tshark 命令行工具抓取我们 UDP 服务(端口 9999)的流量,按端口/IP/流过滤。
tshark 有两层过滤:抓包用 BPF 语法 (-f) 减少磁盘写入;回放用 Wireshark 显示语法 (-Y) 精确定位。follow,udp 可把无连接的 UDP 包重组为流视图。
UDP 常见错误与解决
Connection refused/Port unreachable 处理
TCP 是什么
上页说 UDP 像扔信封——发出去就不管了。TCP 不一样:发之前要先「握手」,收到后要「签收」,按序号拼回去,少一封就重发。
每封都要签收回执、丢了重寄、序号乱了重排
TCP 三次握手
SYN→SYN+ACK→ACK 的完整时序
TCP 四次挥手
读法:从左到右看消息流动,客户端先发 FIN,服务端分开发 ACK 和 FIN,最后客户端 ACK。
TCP 状态转换图
前两页我们跟完了三次握手、四次挥手——那只走了状态图上的一条主线。完整的连接生命周期里,TCP 一共要穿过 11 个状态,每个状态都对应着一段特定的协议行为。
每个站点是一个状态,列车到站(收到报文)就换乘下一站;客户端与服务端是两条独立的线路
TCP 服务端代码
最小可运行的 TCP 回显服务端,覆盖 socket→bind→listen→accept→read/write→close 全流程。
六行对应 TCP 服务端六个阶段:SOCK_STREAM 锁定 TCP 协议;listen 把主动 socket 转为被动;accept 阻塞等三次握手完成;cfd 是新连接的专用描述符。
TCP 客户端代码
一个最小可运行的 TCP 客户端,跑通 socket→connect→read/write→close 五步生命周期。
connect() 是 TCP 客户端特有的入口,三次握手在此完成;read/write 走全双工字节流;close 触发四次挥手。对比 UDP 客户端,多出的就是 connect 这一步。
TCP 连接建立流程
三次握手看似协议层动作,实际由 socket API 在内核里串起来。
listen 与 accept 详解
TCP 服务端 socket() 拿到的是主动套接字,要接客户端必须先 listen() 转成被动监听,再由 accept() 从内核队列里把连接取出来——这是服务端并发的根。
listen=前台开工挂牌限排队;半连接=手续没办完的住客;accept=叫号发专属房卡
connect 阻塞与超时
connect 的行为与错误处理
多客户端并发处理
fork/线程池实现并发服务器
TCP 调试基础工具
TCP 连接建立后,怎么证明它真的「活着」?握手的数据落在哪个 socket 里?现在该请出调试工具——netstat 和 ss,把内核里的连接表「投影」到屏幕上。
LISTEN=前台待客、ESTABLISHED=客人已入住、TIME_WAIT=客人刚走正在结账
Wireshark TCP 抓包分析
真实抓包输出:从 SYN 到数据传输逐包追踪三次握手过程
对比 Seq/Ack 递增规律:每次 ACK 等于对方 Seq+Len,三次握手后 Seq 从 0 起步,数据包每段递增 75。
TCP 连接异常排查
上一节我们用 Wireshark 抓到了完整握手。但线上常常拿不到握手——连接直接失败,错误码有 refused、timeout、reset 三种,背后原因和排查路径完全不同。
refused=屋里没人(无监听);timeout=敲门没声音(被丢包);reset=开门被赶走(对端主动 RST)
TCP 半连接与全连接队列
上一页讲到 listen() 创建监听 socket、accept() 取出连接。但三次握手发生在内核里,应用感知不到。内核为此维护着两个隐藏队列——握手就在它们之间流转。
取号区=半连接;已入座=全连接;服务员过来=accept()
Socket 选项详解
SO_REUSEADDR/SO_KEEPALIVE 等常用选项
阻塞 vs 非阻塞 I/O
前面 TCP/UDP 代码里,recv() 没数据就卡住等——这是阻塞 I/O 的默认行为。想让进程「不等」或「边等边干别的」,得切到非阻塞模式。
阻塞=站在取餐口不走;非阻塞=拿号坐回去,被叫号时再取
Socket 错误处理
上一节区分了阻塞/非阻塞模式,但 recv/send 不管哪种模式都可能因信号或网络异常返回 -1。errno 就是系统告诉你「为什么失败」的唯一线索。
EINTR=厨师接电话回来继续;EAGAIN=菜没好请等;EPIPE=客人走还上菜;RST=客人撤单走人
Socket 核心知识自测
对 UDP socket 调用 connect() 后,下列行为描述正确的是?
Socket 编程要点总结
- ✓TCP 暴露无消息边界的字节流,UDP 保留独立数据报边界。
- ✓TCP 以连接、确认、排序、流量与拥塞控制换取可靠交付。
- ✓UDP 默认不保证送达、顺序、时限,也不承诺只收一次。
- ✓完整性优先选 TCP;可容忍丢包且追求低时延选 UDP。
- ✓需要可靠 UDP,应补齐重传、排序、拥塞控制和应用层校验。
课后思考
先合上屏幕自己想一分钟,再点开参考答案对思路。三道题分别对应回顾、应用、边界三个层次,看看你最擅长哪一层。
参考答案TCP 需保证双方收发能力正常且序号同步,两次握手只能确认一方的发送和对端的接收,无法防止已失效的旧请求突然重传到服务端造成错连。UDP 无连接概念,自然不需要。
参考答案选 UDP——视频会议容忍少量丢包但不能容忍延迟。需要自己处理:丢包重传(仅限关键帧)、乱序重排、抖动缓冲、流量控制。TCP 的重传和拥塞控制反而会放大延迟。
参考答案完成握手的连接进入内核的全连接队列(accept 队列)。队列满后,新连接请求被丢弃或返回 RST。半连接队列同理有 backlog 限制。客户端 connect 会超时或收到 RST。