Socket 创建与配置:socket() 函数详解、地址族与套接字类型、协议参数选择

写网络程序,第一件事就是创建 Socket。这就像你要打电话,得先找一部电话机。Python 里创建 Socket 就靠 socket() 这个函数。别看它简单,参数选不对,后面全是坑。

我个人习惯,写任何网络代码前,先把这三个参数想清楚:地址族、套接字类型、协议。今天咱们就把它们掰开揉碎了讲。

socket() 函数签名

先看原型:

socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

前三个参数是核心。fileno 是文件描述符,日常开发基本用不到,我做了这么多年项目,也就用过一两次。

返回值是一个 Socket 对象。嗯,这里要注意:如果创建失败,会抛出 OSError。我见过不少新手不处理这个异常,程序一跑就崩。

地址族(Address Family)

地址族决定了 Socket 用什么「地址格式」。说白了,就是告诉系统:我要跟谁通信,对方的地址长什么样。

常量 值 说明
AF_INET 2 IPv4,最常用,比如 192.168.1.1
AF_INET6 23 IPv6,比如 ::1
AF_UNIX 1 本地进程间通信,用文件路径
AF_PACKET 17 底层网络接口,抓包用

我建议初学者先死磕 AF_INET。为什么?因为 90% 的网络编程场景都是 IPv4。你想想看,写个 Web 服务、连个数据库、做个聊天室,哪个不是 IPv4?

有一次我在项目中遇到一个诡异的问题:客户的内网环境只支持 IPv6,但代码里写死了 AF_INET。结果部署上去,Socket 创建成功,连接死活连不上。排查了半天才发现是地址族不匹配。从那以后,我写代码都会加个配置项,让运维人员可以切换地址族。

套接字类型(Socket Type)

套接字类型决定了数据传输的方式。这是整个 Socket 编程里最核心的概念之一。

常量 值 说明
SOCK_STREAM 1 面向连接的可靠字节流(TCP)
SOCK_DGRAM 2 无连接的数据报(UDP)
SOCK_RAW 3 原始套接字,直接操作 IP 层

SOCK_STREAM 就像打电话。你先拨号(连接),然后双方建立一条通道,数据按顺序到达,不丢包。适合 HTTP、FTP、SSH 这些需要可靠传输的场景。

SOCK_DGRAM 就像寄明信片。你写好地址扔邮筒里,对方能不能收到、什么时候收到,你管不了。适合视频直播、DNS 查询、游戏状态同步这些可以容忍少量丢包的场景。

我个人习惯:能用 TCP 就别用 UDP。除非你明确知道自己在做什么,并且愿意处理丢包、乱序、重传这些麻烦事。我曾经接手过一个老项目,用 UDP 做文件传输,结果自己实现了一套可靠传输协议,代码量比 TCP 还大,性能还差。何必呢?

避坑指南:SOCK_RAW 需要 root 权限才能创建。我曾经在开发机上跑得好好的,部署到生产环境就报权限错误,排查了半天才发现是用户权限不够。生产环境千万别用 raw socket,除非你有充分的理由。

协议参数选择

第三个参数 proto 通常填 0,让系统自动选择。但有些场景需要手动指定。

协议常量 值 说明
IPPROTO_TCP 6 TCP 协议
IPPROTO_UDP 17 UDP 协议
IPPROTO_ICMP 1 ICMP 协议(ping 用)
IPPROTO_RAW 255 原始 IP 协议

为什么有时候要手动指定?举个例子:你想创建一个 ICMP 的原始套接字来做 ping 工具。这时候 type=SOCK_RAW,proto=IPPROTO_ICMP。如果不指定协议,系统不知道你要操作哪个上层协议。

但日常开发中,proto=0 就够了。系统会根据 type 自动匹配:SOCK_STREAM 配 TCP,SOCK_DGRAM 配 UDP。

常见组合方式

我整理了一张表,把最常用的组合列出来:

用途 family type proto
TCP 客户端/服务端 AF_INET SOCK_STREAM 0
UDP 客户端/服务端 AF_INET SOCK_DGRAM 0
本地 TCP 通信 AF_UNIX SOCK_STREAM 0
ping 工具 AF_INET SOCK_RAW IPPROTO_ICMP
抓包工具 AF_PACKET SOCK_RAW htons(ETH_P_ALL)
小技巧:Python 的 socket 模块里这些常量都有定义。写代码时用 socket.AF_INET 而不是直接写数字 2,可读性会好很多。我见过有人写 socket(2, 1, 0),三个月后自己都看不懂。

代码示例:创建不同类型的 Socket

import socket

# 创建 TCP Socket(最常用)
tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建 UDP Socket
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 创建 IPv6 TCP Socket
tcp6_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

# 创建本地 UNIX Socket
unix_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

# 创建 ICMP 原始 Socket(需要 root 权限)
icmp_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

print("所有 Socket 创建成功")

运行这段代码,你会看到「所有 Socket 创建成功」。但注意,ICMP 那个如果没有 root 权限会报错。我建议你开个虚拟机或者容器来测试 raw socket,别在宿主机上乱搞。

核心逻辑流程图

下面这张图展示了 Socket 创建时的决策路径。我画这张图的时候,特意把最常见的路径标粗了:

Socket 创建决策流程 开始创建 Socket 选择地址族 (family) AF_INET (IPv4) AF_INET6 (IPv6) AF_UNIX (本地) 选择套接字类型 (type) SOCK_STREAM (TCP) SOCK_DGRAM (UDP) SOCK_RAW (原始) Socket 创建完成 proto=0 时系统自动匹配协议

从图里你能看到,创建 Socket 就是一条「先选地址族,再选类型」的路径。协议参数通常交给系统处理,除非你有特殊需求。

常见错误与避坑

  • 地址族不匹配:用 AF_INET 创建的 Socket 不能连接 IPv6 地址。我曾经犯过这个错,调试了整整一个下午。
  • 类型与协议冲突:SOCK_STREAM 配 IPPROTO_UDP 会报错。系统会告诉你「协议不支持」。
  • 忘记处理异常:socket() 可能因为权限、资源不足等原因失败。一定要用 try/except 包起来。
  • 重复创建:每次通信都创建新 Socket,用完不关闭。这会导致文件描述符泄漏。我建议用 with 语句自动管理生命周期。
核心要点:socket(family, type, proto) 三个参数决定了 Socket 的「身份」。日常开发用 socket(AF_INET, SOCK_STREAM) 就够了。记住:地址族管「地址格式」,类型管「传输方式」,协议管「具体协议」。选对了,后面就顺了。