1. TCP协议基础:从协议栈到Socket
各位同学好,我是老张。做网络开发十几年了,今天咱们来聊聊TCP协议的基础。说实话,TCP这东西,你越用越觉得它设计得精妙。但很多新手一上来就被各种概念砸晕了——协议栈、报文段、端口、Socket……嗯,别急,咱们一个一个拆开讲。
1.1 TCP/IP协议栈概述
先问个问题:当你在浏览器里输入一个网址,数据是怎么从你的电脑跑到服务器上的?
答案就是TCP/IP协议栈。它其实是一组协议的集合,从上到下分四层。我个人习惯把这四层比作一个快递系统:
- 应用层 —— 你填写的快递单(HTTP、FTP、SMTP)
- 传输层 —— 快递公司的分拣中心(TCP、UDP)
- 网络层 —— 运输路线规划(IP)
- 网络接口层 —— 实际送货的卡车(以太网、Wi-Fi)
每一层只关心自己的事,下层为上层提供服务。这种分层设计的好处是——你改了下层的硬件,上层的应用代码不用动。我在项目中遇到过好几次,客户说“换个网卡,程序就崩了”,结果一查,是驱动层没处理好,跟应用层代码半毛钱关系没有。
核心要点:TCP/IP协议栈是互联网通信的骨架。TCP工作在传输层,负责在两个进程之间建立可靠的、面向连接的通信通道。
这里有个容易混淆的点:很多人以为TCP/IP就是TCP和IP两个协议。其实不是,它指的是整个协议簇。TCP和IP只是其中最出名的两个。
1.2 TCP报文段首部格式
好,咱们深入一层。TCP传输数据时,会把数据切成一段一段的,每一段前面加一个头部——这就是TCP报文段首部。说白了,就像寄快递时填的快递单,收件人、发件人、包裹重量、备注……全写在这张单子上。
TCP首部固定部分是20字节,后面还可以跟选项。我刚开始学的时候,觉得20字节没什么,后来算了一笔账:一个互联网核心路由器每秒处理几百万个报文段,光首部开销就够喝一壶的。所以设计协议时,每一比特都得精打细算。
来看一下TCP首部的核心字段:
| 字段 | 长度 | 作用 |
|---|---|---|
| 源端口 / 目的端口 | 各16位 | 标识发送方和接收方的进程 |
| 序号(SEQ) | 32位 | 本报文段数据的第一个字节的序号 |
| 确认号(ACK) | 32位 | 期望收到的下一个字节的序号 |
| 数据偏移 | 4位 | 首部长度,单位是4字节 |
| 标志位 | 6位 | URG、ACK、PSH、RST、SYN、FIN |
| 窗口大小 | 16位 | 接收方还能收多少数据(流量控制) |
| 校验和 | 16位 | 检测首部和数据是否损坏 |
| 紧急指针 | 16位 | 配合URG标志位使用 |
避坑指南:我曾经在调试一个视频流服务时,发现画面偶尔卡顿。抓包一看,TCP窗口大小被设成了0。原因是接收方的应用层没及时读取数据,接收缓冲区满了。TCP的流量控制机制直接让发送方停了。所以,应用层读取速度跟不上,TCP再可靠也没用。
标志位这块,我重点说一下。6个标志位里,SYN、ACK、FIN是三次握手和四次挥手的主角。RST用于异常断开——有一次我写端口扫描工具,对方返回RST,说明端口没开。PSH和URG用得少,但URG紧急指针在Telnet这类交互式协议里还有用。
序号和确认号是TCP可靠传输的基石。你想想看,如果接收方收到乱序的报文段,怎么知道该排在哪?全靠序号。确认号告诉发送方“你发到第1000字节了,下一个我要1001”。这就是TCP的累积确认机制。
1.3 TCP端口与Socket概念
聊完报文段,咱们说说端口和Socket。这两个概念,我见过太多人搞混了。
端口是一个16位的数字,范围0~65535。它的作用是标识一台主机上的某个进程。说白了,IP地址找到了你的电脑,端口找到了你电脑上的哪个程序。
端口分三类:
- 知名端口(0~1023):HTTP用80,HTTPS用443,SSH用22。这些是约定俗成的,你非要用80跑别的服务也行,但会被人骂。
- 注册端口(1024~49151):给应用程序用的,比如MySQL的3306,Redis的6379。
- 动态/私有端口(49152~65535):客户端临时用,系统自动分配。
我记得有一次帮客户排查问题,他们的服务端口设成了6666,结果被安全策略拦截了。一问才知道,6666在某些系统里被标记为高风险端口。所以,选端口时别太随意,避开那些有“历史包袱”的号码。
Socket是什么?它是IP地址和端口号的组合。一个完整的Socket由四元组唯一确定:
(源IP, 源端口, 目的IP, 目的端口)
这四元组唯一标识一条TCP连接。你想想看,一台服务器上跑着Web服务(80端口),同时有成千上万个客户端连上来。服务器怎么区分谁是谁?靠的就是这个四元组。每个客户端的源IP和源端口都不一样,所以即使目的IP和目的端口相同,也能区分开。
注意:很多人以为Socket就是端口,其实不是。Socket是编程接口的抽象,它封装了IP和端口。在Linux里,Socket是一个文件描述符。你创建Socket、绑定端口、监听连接、收发数据,全是通过这个文件描述符操作的。
下面这张图展示了TCP/IP协议栈各层与Socket的关系:
从这张图能看出来,Socket位于应用层和传输层之间。应用层程序通过Socket接口,调用传输层的TCP或UDP服务。你写网络程序时,操作的就是这个Socket——绑定端口、监听、连接、收发数据,全是通过它完成的。
实战经验:我早期做高并发服务器时,犯过一个低级错误——每个新连接都创建一个新线程,结果线程数一多,上下文切换开销直接把CPU干到100%。后来改用epoll + 非阻塞Socket,单线程处理上万连接轻轻松松。所以,理解Socket只是第一步,怎么用好它才是关键。
最后总结一下本章的核心:
- TCP/IP协议栈是分层设计的,TCP工作在传输层
- TCP报文段首部20字节,序号、确认号、标志位、窗口大小是关键字段
- 端口标识进程,Socket = IP + 端口,四元组唯一标识一条TCP连接