Socket 与端口
进程间的网络通信需要一套统一的"门牌号"体系,这就是端口;而真正负责收发数据的那扇"门"则是 Socket(套接字)。理解 Socket 与端口,是读懂网络编程与一切后端架构的起点。
什么是端口
一台主机可以同时运行许多网络程序,端口的作用就是区分这些程序。端口是一个 0~65535 的整数,它必须搭配 IP 地址,才能唯一确定一个通信端点。
| 范围 | 说明 | 常见例子 |
|---|---|---|
| 0~1023 | 知名端口,通常需管理员权限才能绑定 | 22(SSH)、80(HTTP)、443(HTTPS) |
| 1024~49151 | 注册端口,供普通应用注册使用 | 3306(MySQL)、6379(Redis)、8080 |
| 49152~65535 | 动态/临时端口,客户端发起连接时由系统随机分配 | 客户端的源端口 |
简单说:IP 负责找到主机,端口负责找到主机上的进程。二者合起来称为套接字地址,例如 192.168.1.10:8080。
Socket 是什么
Socket 是操作系统提供的网络编程接口(API),把底层 TCP/UDP 协议的细节封装起来,让程序员像读写文件一样收发网络数据。可以把 Socket 想象成"电话机两端的话筒"。
TCP 服务端最典型的工作流程如下(可整体直接运行):
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 IPv4 的 TCP 套接字
server.bind(("0.0.0.0", 8080)) # 绑定 IP 与端口
server.listen(5) # 开始监听,内核排队最多 5 个连接
conn, addr = server.accept() # 阻塞等待,直到有客户端连入
print("客户端接入:", addr)
data = conn.recv(1024) # 接收最多 1024 字节
print("收到:", data.decode())
conn.send(b"hi") # 发送响应
conn.close() # 关闭连接
对应关系很直观:bind 相当于申请电话号码,listen 相当于开机待机,accept 相当于接听来电。
一次 TCP 通信的完整流程
- 服务端:创建 → 绑定 → 监听 → 接受 → 收发数据 → 关闭。
- 客户端:创建 → connect(触发三次握手)→ 收发数据 → 关闭。
客户端 服务端
| connect() |
|--------------------------------->| accept() 返回一个新连接
| 三次握手 |
|=========== 业务数据 =============>|
|<=========== 业务数据 ============ |
| close() |
注意:每 accept 一次,服务端会得到一个新的连接套接字专门服务该客户端;原来那个监听套接字仍留在原地,继续等待下一个连接。这也是高并发服务"一个监听、多路连接"的原理。
端口冲突与常见问题
- 端口被占用:启动服务时报
Address already in use,需要换端口或先结束占用进程。 - TIME_WAIT 堆积:主动关闭连接的一方会停留约 2MSL,大量短连接可能积压该状态。
- 防火墙拦截:端口通不代表可达,还需检查防火墙与云安全组规则。
// 以下片段需放入 main 函数中运行:快速验证端口是否可用
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("端口被占用或不可用:", err)
}
defer ln.Close()
log.Println("监听成功:", ln.Addr())
// 输出:监听成功: [::]:8080
面试常问
- 端口范围是多少?知名端口有哪些?——0~65535;SSH=22、HTTP=80、HTTPS=443 等。
- 同一端口能被多个进程同时监听吗?——默认不可以(Linux 开启 SO_REUSEPORT 除外)。
- 客户端连接服务器时用哪个端口?——服务器用固定端口,客户端由系统随机分配高位动态端口。
小结:端口负责"定位进程",Socket 负责"收发数据"。记牢服务端"创建-绑定-监听-接受"的流程,以及"IP+端口=唯一端点"这一核心思想,网络编程就正式入门了。