完整教程:揭秘网络通信:端口号与TCP/UDP详解(二)
再谈端口号
端⼝号(Port)标识了⼀个主机上进⾏通信的不同的应⽤程序;

在TCP/IP协议中,⽤源IP,源端⼝号,⽬的IP,⽬的端⼝号,协议号这样⼀个五元组来标识⼀个 通信(可以通过netstat-n查看);

端⼝号范围划分:
- 0-1023:知名端⼝号,HTTP,FTP,SSH等这些⼴为使⽤的应⽤层协议,他们的端⼝号都是固定的.
- 1024-65535:操作系统动态分配的端⼝号.客⼾端程序的端⼝号,就是由操作系统从这个范围分配 的.
认识知名端⼝号有些服务器是⾮常常⽤的,为了使⽤⽅便,⼈们约定⼀些常⽤的服务器,都是⽤以下这些固定的端⼝号:
- ssh服务器,使⽤22端⼝
- ftp服务器,使⽤21端⼝
- telnet服务器,使⽤23端口
- http服务器,使⽤80端⼝
- https服务器,使⽤443端口
我们⾃⼰写⼀个程序使⽤端⼝号时,要避开这些知名端⼝号.
UDP协议
UDP协议端格式:

- 16位UDP⻓度,表⽰整个数据报(UDP⾸部+UDP资料)的最⼤⻓度
- 如果校验和出错,就会直接丢弃
UDP的特点
UDP传输的过程类似于寄信.
- ⽆连接:知道对端的IP和端⼝号就直接进⾏传输,不需要建⽴连接
- 不可靠:没有确认机制,没有重传机制;假如因为⽹络故障该段⽆法发到对⽅,UDP协议层也不会给应 ⽤层返回任何错误信息;
- ⾯向数据报:不能够灵活的控制读写数据的次数和数量; 理解UDP的"不可靠"
⾯向数据报
应⽤层交给UDP多⻓的报⽂,UDP原样发送,既不会拆分,也不会合并
⽤UDP传输100个字节的数据:
- 如果发送端调⽤⼀次sendto,发送100个字节,那么接收端也必须调⽤对应的⼀次recvfrom,接收100 个字节;⽽不能循环调⽤10次recvfrom,每次接收10个字节;
UDP使⽤注意事项
64K(涵盖UDP⾸部).就是我们注意到,UDP协议⾸部中有⼀个16位的最⼤⻓度.也就是说⼀个UDP能传输的信息最⼤⻓度
如果我们得传输的数据超过64K,就要求在应⽤层⼿动的分包,多次发送,并在接收端⼿动拼装;基于UDP的应⽤层协议
- NFS:⽹络⽂件系统
- TFTP:方便⽂件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(⽤于⽆盘设备启动)
- DNS:域名解析协议 当然,也包括你⾃⼰写UDP代码时⾃定义的应⽤层协议;
TCP协议
TCP全称为"传输控制协议.⼈如其名,要对数据的传输进⾏⼀个详细 的控制;
TCP协议段格式

- 源/⽬的端⼝号:表⽰材料是从哪个进程来,到哪个进程去;
- 32位序号/32位确认号:
- 给 “拆成多段的信息” 标顺序,避免接收方拿到后乱序
- 接收方告诉发送方 “我已经收到哪部分了,你接下来该发哪部分”,保证数据不丢。
- 15* 4=60就是4位TCP报头⻓度:表⽰该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最⼤⻓度
6位标志位:
- 否有效就是URG:紧急指针
- ACK:确认号是否有效
- PSH:提⽰接收端应⽤程序⽴刻从TCP缓冲区把材料读⾛
- RST:对⽅要求重新建⽴连接;我们把携带RST标识的称为复位报⽂段
- SYN:请求建⽴连接;大家把携带SYN标识的称为同步报⽂段
- FIN:通知对⽅,本端要关闭了,我们称携带FIN标识的为结束报⽂段
16位窗⼝⼤⼩:
- 16位校验和:发送端填充,CRC校验.接收端校验不通过,则认为数据有障碍.此处的检验和不光包含 TCP⾸部,也包含TCP数据部分.
- 紧急资料;就是16位紧急指针:标识哪部分资料
- 40字节头部选项:补充一些 “可选功能”,让传输更灵活
用班级传纸条举例子理解TCP
1. 16 位源端口号 → 纸条上写 “发件人:你的座位号(比如 3 排 2 座)”
- 类比:纸条上标清 “谁发的”,避免同桌收到后不知道该回复给谁。
- 大白话作用:告诉接收方 “这个素材是哪个应用发的”(比如你电脑上的微信、浏览器)。
- 例子:你用电脑上的微信(3 排 2 座)给朋友发消息,微信会 “抢” 一个临时座位号(比如端口号 6789)标在 “纸条备注” 里。朋友的微信服务器收到后,想给你回消息,就按这个 “座位号” 找,才能精准传到你电脑的微信上,而不是传到你的浏览器或 QQ 里。
2. 16 位目的端口号 → 纸条上写 “收件人:同桌的座位号(4 排 2 座)”
- 类比:纸条上标清 “要给谁”,避免你递纸条时传错人(比如传到 3 排 3 座的同学那)。
- 大白话作用:告诉传输过程 “数据要发给对方的哪个应用”(比如服务器上的微信服务、网页服务)。
- 例子:你用浏览器访问 B 站,会在 “纸条备注” 里写 B 站服务器的 “固定座位号”——80(HTTP 服务专用)或 443(HTTPS 服务专用)。就像你知道同桌固定坐 4 排 2 座,递纸条时直接往那传,不会错;要是是发微信消息,就写微信服务器的 “固定座位号”(比如某定制端口)。
3. 32 位序号 → 纸条上写 “这是第 2 张,总共 3 张”
- 类比:如果你的消息太长,一张纸条写不下(比如要跟同桌说清楚奶茶的口味、甜度、加料),得拆成 3 张传。每张纸条上标 “第 1 张”“第 2 张”“第 3 张”,方便同桌按顺序拼起来看。
- 大白话作用:给 “拆成多段的数据” 标顺序,避免接收方拿到后乱序(比如先收到第 3 张,再收到第 1 张)。
- 例子:你发一条 1500 字的长消息,TCP 会拆成 3 段(500 字 / 段)。第一段标 “序号 1”,第二段标 “序号 501”(从第 501 个字开始),第三段标 “序号 1001”。同桌(接收方)收到后,按 “1→501→1001” 的顺序拼,就能还原成完整的 1500 字消息,不会读成 “加料→甜度→口味” 的乱序。
4. 32 位确认序号 → 同桌回你 “我收到第 2 张了,快给我第 3 张”
- 类比:你传完第 1 张纸条,同桌得告诉你 “我收到了,继续传第 2 张”;传完第 2 张,同桌再回 “收到第 2 张,要第 3 张”—— 避免你重复传或漏传。
- 大白话作用:接收方告诉发送方 “我已经收到哪部分了,你接下来该发哪部分”,保证数据不丢。
- 例子 “1-500 我都收到了,你该发 501 开始的第二段了”。如果同桌没回这个确认,你就知道 “第一段可能丢了”,会重新传一遍,直到收到确认。就是:同桌收到你标 “序号 1-500” 的第一段消息后,会回一个 “确认序号 501” 的纸条,意思
5. 4 位首部长度 → 纸条上写 “备注部分占 1 行,消息从第 2 行开始”
- 类比:你在纸条上写的备注(发件人、收件人、序号)可能占 1 行,也可能占 2 行(比如备注多),得告诉同桌 “备注到哪行结束,真正的消息从哪行开始”,避免同桌把备注当消息读。
- 大白话作用:告诉接收方 “TCP 首部(备注)有多长”,这样能精准找到 “数据(核心消息)” 的开始位置。
- 例子:如果首部长度是 5(单位是 4 字节,总长度 20 字节),就相当于 “备注占 20 个字符”,接收方会跳过前 20 个字符,从第 21 个字符开始读,这才是真正要传的 “下课后买奶茶” 这类消息。
6. 保留 (6 位) → 纸条上留的 “空白备注区”
- 类比:你在纸条上特意留了几格空白,暂时没写东西,想着 “以后万一有新备注要加,就用这几格”。
- 大白话作用:TCP 协议预留的 “备用位置”,现在没用,全填 0,方便以后升级协议时加新效果(比如以后要加 “消息优先级”,就可以用这里的位置)。
7. 控制位(6 位)→ 纸条上的 “特殊指令”(比如 “加急看”“看完要回复”)
这 6 位相当于 6 种不同的 “小指令”,每种指令对应一个需求,咱们挑最常用的 3 个类比:
- SYN(同步位):纸条上写 “先确认下,你能收到我消息不?”—— 用来 “建立连接”。例子:你第一次跟新同桌传纸条,先递一张只写 “能收到不?” 的纸条(SYN=1),同桌回 “能收到!”(SYN=1+ACK=1),相当于你们俩 “建立了传纸条的连接”,之后再传消息就不用确认了。
- ACK(确认位):纸条上写 “我收到你上一张了”—— 用来 “确认收到素材”。例子:同桌收到你 “下课后买奶茶” 的纸条,回一张写 “收到,没困难” 的纸条(ACK=1),你就知道消息没丢。
- FIN(终止位):纸条上写 “我没消息要发了,不用等我了”—— 用来 “关闭连接”。例子:你跟同桌说完所有奶茶相关的事,最后递一张写 “没别的了,下课见” 的纸条(FIN=1),同桌回 “好,下课见”(FIN=1+ACK=1),相当于 “传纸条的连接关了”,之后不会再传纸条了。
8. 16 位窗口大小 → 同桌回你 “我现在手里只有 2 张空纸条,你一次最多传 2 张”
- 类比:同桌手里的空纸条有限(比如只剩 2 张),如果一次给你传 5 张,他没地方写回复,就会跟你说 “一次最多传 2 张,等我用完再要”—— 避免他 “忙不过来”。
- 大白话作用:接收方告诉发送方 “我现在能接收多少数据”,避免发送方发太快,导致接收方缓存满了丢数据(流量控制)。
- 例子:同桌(接收方)说 “窗口大小 = 2”(比如能接收 2 段资料),你就一次最多传 2 段消息(比如第 1+2 段),等他处理完、回复 “窗口大小 = 2”,再传第 3+4 段,不会一次传 5 段让他 “没地方放”。
9. 16 位检验和 → 纸条上写 “消息结果一个字是‘茶’,你核对下”
- 类比:你怕纸条传的时候被人改内容(比如把 “奶茶” 改成 “咖啡”),就加个 “核对标记”,让同桌收到后确认 “最后一个字是不是‘茶’”,不是就说明被改了。
- 大白话作用:校验 “首部 + 数据” 有没有被篡改或传错(比如网络波动导致字节变了)。
- 例子:你发 “下课后一起去买奶茶”(10 个字),算一个 “校验值”(比如 “10”)写在备注里。同桌收到后,数一下字数也是 10,就知道内容没被改;假设数出来是 9,就知道 “传错了,你重发一遍”。
急事,先看该”就是10. 16 位紧急指针 → 纸条上写 “这段里‘快帮我带一杯’
- 类比:你传的纸条里既有 “下课后买奶茶”,又有 “快帮我带一杯,我现在渴”,就标一句 “前面‘快帮我带’是急事,先看”,让同桌优先处理急事。
- 大白话作用:当有 “紧急数据”(比如键盘快捷键、紧急消息)时,告诉接收方 “这部分要优先处理,别等整个消息拆完”。
- 例子:你在消息里写 “下课后买奶茶,加珍珠;对了,快帮我带一杯,我现在渴”,同时标 “紧急指针 = 20”(假设 “快帮我带” 从第 20 个字符开始),同桌收到后会先看 “快帮我带一杯”,再看其他内容。
11. 选项 → 纸条上额外加的 “特殊要求”(比如 “字写大一点,我看不清”)
- 类比每次都有。就是:你知道同桌近视,就在纸条备注里加一句 “字写大一点”,属于 “额外的传输要求”,不
- 大白话作用:补充一些 “可选功能”,让传输更灵活(比如协商 “一次最多传多少数据”“要不要加时间戳防重”)。
- 例子 “MSS 选项”,协商最大分段大小),之后你们传的每段消息都不超过 50 字,避免某段太长传丢。就是:第一次传纸条时,你加备注 “我一次最多写 50 个字,你也别超过 50”(这就
12. 数据 → 纸条上的核心消息(“下课后一起去买奶茶,要三分糖加珍珠”)
- 类比你真正想跟同桌说的内容。就是:所有备注(发件人、序号、指令)都是为了 “让这句话准确传到同桌那”,这句话才
- 大白话作用:承载 “应用层要传的东西”,比如你用浏览器发的 “打开 B 站首页” 请求、微信发的 “在吗” 消息、下载材料的 “文件内容”,这些都是 TCP 里的 “数据” 字段,是整个传输的核心目标。
- 例子:你用浏览器访问 B 站,浏览器会把 “我要打开 B 站首页” 这个 HTTP 请求(应用层数据)交给 TCP,TCP 给它加好 “备注”(首部字段)后发出去,B 站服务器收到后,会把 “B 站首页的 HTML 代码”(应用层数据)作为 TCP 的 “内容” 字段,加好备注回传给你,你浏览器拿到后就能表现首页了。
总结一下:TCP 首部 =“传纸条的备注 + 规则”,内容 =“要跟同桌说的核心话”
你给同桌传纸条时,所有 “备注”(谁发的、发给谁、要不要确认、一次传几张)都是为了让 “核心话” 准确、不丢、不乱地传到对方那;TCP 也是一样,所有首部字段都是为了让 “应用层数据”(网页请求、聊天消息)可靠地从一个应用传到另一个应用,这些首部字段就像 “传输的管家”,确保数据不会传错、传丢、传乱。
Java 中的 IO 多路复用
IO 多路复用是一种高效的网络 IO 模型,核心思想是利用一个线程管理多个 IO 通道,避免传统阻塞 IO 中 “一个连接一个线程” 的资源浪费,尤其能解决非阻塞 IO 中轮询的低效问题。
在 Java 中,IO 多路复用的实现依赖于NIO(Non-blocking IO) 中的 Selector(选择器),其工作原理如下:
- 注册通道:将多个
SocketChannel(TCP 通道)或DatagramChannel(UDP 通道)注册到Selector上,并指定关注的事件(如连接就绪OP_CONNECT、读就绪OP_READ、写就绪OP_WRITE)。 - 轮询就绪事件:
Selector调用select()方法阻塞等待,直到至少一个通道的事件就绪,返回就绪的通道数量。 - 处理事件:通过
selectedKeys()获取就绪通道的事件集合,遍历并处理(如读取数据、发送响应),处理完成后移除事件标记,避免重复处理。
优势:单线程即可处理大量并发连接(理论上可达数万),适合高并发场景(如服务器、网关)。示例场景:实现一个支持同时处理 1000 个客户端连接的聊天服务器,用 Selector 统一管理所有客户端的 SocketChannel,当某个客户端发送消息时,Selector 感知到 “读就绪” 事件,再处理该客户端的数据。
- “先来先处理,公平排队”;就是事件处理
- 单线程处理,但速度极快,能同时支撑大量连接;
- 个别连接崩溃不会影响整体,处理逻辑里删掉它就行。
假设你开了一家奶茶店(相当于服务器),顾客(相当于客户端)来点单(相当于发送网络请求)。
- 传统做法:每来一个顾客,你就专门派一个服务员全程盯着他 —— 他点单时服务员等着,他慢慢选口味时服务员也等着,他拿到奶茶走了,这个服务员才空闲下来。
- 问题:倘若一下子来了 100 个顾客,你就得雇 100 个服务员,哪怕他们大部分时间都在 “闲着等顾客”,也得花钱养着,太浪费(对应传统网络编程中 “一个连接一个线程”,线程闲置时浪费内存和 CPU)。
IO 多路复用:一个 “总管” 盯所有顾客
现在换个高效的办法:只雇 1 个总管(相当于 Selector),让他同时盯着店里所有顾客,谁有动作了再处理。
过程:
- 总管站在店中央,所有顾客来了之后都先告诉总管:“我一会儿要点单 / 取奶茶,你留意着我啊”(相当于 “通道注册到 Selector 并声明关注的事件”)。
- 总管没事干的时候就眯着眼休息(
select()方法阻塞等待),不浪费精力。 - 突然有个顾客举手:“我要点单!”(相当于某个客户端有数据要发送,触发 “读事件”),总管立刻醒过来,走到该顾客面前处理点单(处理该客户端的请求)。
- 处理完这个顾客,总管又回去盯着其他人,看看下一个有动作的是谁(循环处理就绪的事件)。
好处:1 个总管就能搞定所有顾客,哪怕同时来 1000 个,也不用雇 1000 个服务员,省了大量成本(对应单线程管理大量连接,大幅降低资源消耗)。
应用层自定义协议
应用层协议是客户端与服务端之间约定的数据传输规则(如格式、含义、交互流程)。HTTP、FTP 等是通用协议,但在特定场景下,程序员必须自定义协议以满足业务需求。
1. 何时需自定义协议?
当通用协议无法满足以下需求时,需自定义:
- 轻量级需求:通用协议(如 HTTP)有冗余头信息,对性能敏感的场景(如游戏实时通信、物联网设备交互)需精简协议。
- 例:游戏中玩家位置同步,每秒需传输大量坐标数据,HTTP 的头信息会浪费带宽,需自定义二进制协议。
- 特定业务逻辑:通用协议无法表达业务专属数据(如订单状态、设备指令)。
- 例:智能家居中,控制空调的 “温度调节”“模式切换” 等指令,需自定义协议规定指令格式。
- 安全性需求:通用协议的格式公开,需自定义加密或混淆的协议防止信息被解析。
- 高效序列化:通用协议(如 JSON)序列化效率低,对速度要求高的场景(如高频交易)需自定义二进制协议。
2. 自定义协议的核心要求
协议设计需明确传输信息和组织格式,确保双方能正确解析,核心要求如下:
(1)明确传输的信息(“传什么”)
根据业务需求确定必须包含的字段,例如:
- 通信标识:区分请求 / 响应(如
type=1表示请求,type=2表示响应)。 - 业务数据:核心内容(如用户 ID、订单号、设备状态)。
- 控制信息:资料长度(应对粘包)、校验码(验证完整性)、版本号(兼容升级)。
示例:一个简单的即时通信协议,需传输:
- 发送者 ID(
from)、接收者 ID(to)、消息内容(content)、消息类型(msgType:文本 / 图片)、时间戳(timestamp)。
(2)约定信息的组织格式(“怎么传”)
需定义数据的序列化格式和边界标识,常见格式:
文本格式(易读性好,适合简单场景):用分隔符(如
|、&)分割字段,或用 JSON/XML 结构化表示。- 例:
from=1001|to=1002|msgType=text|content=hello|timestamp=1620000000 - 问题:文本解析效率低,且分隔符可能与内容冲突(需转义)。
- 例:
二进制格式(高效、紧凑,适合高性能场景):按固定字节长度或偏移量定义字段,用二进制直接存储(如整数用 4 字节,字符串用 “长度 + 内容”)。
- 例:一个二进制协议格式(按顺序):
- 版本号(1 字节):
0x01(表示版本 1.0) - 消息类型(1 字节):
0x01(文本消息) - 发送者 ID 长度(2 字节):
0x0004(表示 4 个字符) - 发送者 ID(4 字节):
1001(ASCII 编码) - 消息长度(4 字节):
0x00000005(表示 5 字节内容) - 消息内容(5 字节):
hello
- 版本号(1 字节):
- 例:一个二进制协议格式(按顺序):
(3)解决关键技术问题
- 粘包 / 拆包:TCP 是流式传输,多个数据包可能粘连或被拆分,需通过 “长度字段”(如消息前加 4 字节表示总长度)或 “特殊分隔符”(如
\r\n)标记包边界。- 例:二进制协议中,先读取前 4 字节的 “总长度”,再按长度读取完整消息。
- 兼容性:预留版本号字段,当协议升级时(如新增字段),旧版本客户端 / 服务端可忽略新增字段,避免崩溃。
- 完整性:添加校验码(如 CRC、MD5),接收方验证资料是否被篡改或损坏。
3. 自定义协议示例
场景:物联网设备(如传感器)向服务器上报温湿度数据。
传输信息:设备 ID、温度、湿度、上报时间、数据状态(正常 / 异常)。
协议格式(二进制):
| 字段 | 字节长度 | 说明 |
|---|---|---|
| 版本号 | 1 | 固定为 0x01 |
| 设备 ID 长度 | 2 | 设备 ID 是字符串,长度不固定 |
| 设备 ID | 可变 | 如 sensor_001(8 字节) |
| 温度 | 2 | 用短整数表示(实际值 = 数值 / 10,如 255 表示 25.5℃) |
| 湿度 | 2 | 同上(如 600 表示 60.0%) |
| 上报时间 | 8 | 时间戳(long 类型,毫秒级) |
| 状态 | 1 | 0x00 正常,0x01 异常 |
| 校验码 | 4 | 前所有字段的 CRC32 校验值 |
解析逻辑:服务器按上述字节偏移量依次读取字段,验证校验码后解析为业务数据。

浙公网安备 33010602011771号