Socket(套接字) 是操作系统提供给 进程 使用网络协议栈的端点抽象。应用程序通过 socket 绑定本地地址、连接远端、收发数据,而具体的 TCP、UDP、缓冲区和协议状态由内核维护。
核心问题
网络协议栈在内核里,应用进程不能直接操作网卡包队列。Socket 解决的是“进程如何用统一的文件/句柄接口访问网络通信端点”的问题。
它不是协议本身,也不是端口本身。Socket 更像一个内核对象加一个应用可持有的句柄:进程通过文件描述符调用 read/write/send/recv,内核根据 socket 的协议类型和状态决定如何收发数据。
核心机制
创建 socket 时通常要指定三个维度:
socket(domain, type, protocol)| 参数 | 例子 | 含义 |
|---|---|---|
| domain | AF_INET, AF_INET6, AF_UNIX | 地址族:IPv4、IPv6、Unix Domain Socket |
| type | SOCK_STREAM, SOCK_DGRAM | 通信语义:字节流或数据报 |
| protocol | TCP, UDP 或 0 | 具体协议,常让内核按 type 推断 |
一个网络 socket 常见状态包括:
| 字段 | 含义 |
|---|---|
| local address | 本地 IP 地址 |
| local port | 本地 端口 |
| peer address | 对端 IP 地址,TCP 连接后通常确定 |
| peer port | 对端端口 |
| protocol | TCP、UDP 或其他协议 |
| buffers | 内核发送/接收缓冲区 |
| state | TCP 的 LISTEN、ESTABLISHED、TIME_WAIT 等状态,或 UDP 的绑定状态 |
TCP 服务端典型流程是:
listen_fd = socket(AF_INET, SOCK_STREAM, 0)
bind(listen_fd, local_ip, local_port)
listen(listen_fd)
conn_fd = accept(listen_fd)
read(conn_fd)
write(conn_fd)
close(conn_fd)这里有两个不同 socket:
listen_fd是监听 socket,只负责接收新连接。conn_fd是accept()返回的已连接 socket,对应某个具体客户端四元组。
TCP 客户端典型流程是:
fd = socket(AF_INET, SOCK_STREAM, 0)
connect(fd, server_ip, server_port)
write(fd)
read(fd)
close(fd)UDP 服务端没有 listen() 和 accept():
fd = socket(AF_INET, SOCK_DGRAM, 0)
bind(fd, local_ip, local_port)
recvfrom(fd)
sendto(fd, peer_ip, peer_port)因为 UDP 没有连接握手,内核通常按每个数据报携带的源地址和源端口区分发送方。
工程用途
- 网络服务端:Web 服务器、数据库、消息队列通过监听 socket 接受连接。
- 网络客户端:浏览器、CLI、SDK 用 socket 连接服务端并收发字节流或数据报。
- 本机 IPC:Unix Domain Socket 可用于同一台机器上的进程通信,常见于 Docker、数据库和守护进程。
- 高并发 I/O:事件循环通过
select、poll、epoll、kqueue等机制监控大量 socket 是否可读可写。
观察指标包括文件描述符数量、监听 backlog、accept 队列、连接状态分布、读写缓冲区大小、阻塞/非阻塞模式、超时设置和错误码。
边界与常见坑
- socket 不是连接的同义词:UDP socket 可以没有连接;TCP 监听 socket 也不是某个客户端连接。
- socket 不是端口:多个 socket 可能与同一端口有关,但端口只是传输层编号,socket 是内核通信对象。
- 一次写不等于一次读:TCP socket 暴露字节流,应用层要自己定义消息边界。
- 非阻塞 I/O 要处理部分读写:
send()写入的字节数可能小于请求字节数,recv()也可能只读到部分数据。 - 关闭不等于对端立刻知道:TCP close、shutdown、FIN、RST 和 TIME_WAIT 都有不同语义。
- 文件描述符泄漏会拖垮服务:忘记 close、连接空闲不清理、accept 太慢都会耗尽资源。