3. Python Socket 模块入门:socket() 函数、AF_INET 与 SOCK_DGRAM、创建第一个 UDP Socket

好,咱们正式开始动手了。前两章我们把 UDP 的理论基础打了一遍,现在该轮到 Python 上场了。这一章,我会带你认识 Python 里最核心的网络编程模块——socket。说白了,它就是操作系统网络接口在 Python 里的「遥控器」。

我个人习惯,学任何新东西都先看它的构造函数。因为只要搞懂了怎么创建对象,后面的一切操作都围绕这个对象展开。Socket 编程也不例外,一切从 socket.socket() 开始。

3.1 socket() 函数:创建通信的「门」

想象一下,你要跟远方的朋友通信。你得先有一扇门,对吧?这扇门就是 Socket。Python 里创建这扇门的函数就是 socket.socket()。

它的完整签名长这样:

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

别被这么多参数吓到。绝大多数情况下,你只需要关心前两个:family 和 type。后面两个参数,proto 默认 0 就行,fileno 是高级用法,咱们暂时用不上。

核心要点:socket() 函数就是向操作系统申请一个网络通信资源。申请成功后,返回一个 Socket 对象。后续的发送、接收、关闭,全都要通过这个对象来完成。

3.2 AF_INET:选择「地址家族」

family 参数,也叫地址家族。它决定了你的 Socket 使用哪种网络协议栈。

常用的就两种:

常量 含义 适用场景
AF_INET IPv4 协议 绝大多数互联网通信
AF_INET6 IPv6 协议 下一代互联网,地址更长

咱们做 UDP 通信,99% 的情况都用 AF_INET。为什么?因为目前互联网上跑的流量,IPv4 还是绝对主力。我在项目中遇到过几次 IPv6 的需求,基本都是运营商内部网络或者某些特定物联网场景。对于初学者,先死磕 IPv4 就够了。

小提示:AF 是 "Address Family" 的缩写。有些老代码里你会看到 PF_INET,它跟 AF_INET 在 Linux 上基本等价。但为了可读性,我建议统一用 AF_INET。

3.3 SOCK_DGRAM:指定「数据报」类型

type 参数,决定了 Socket 的工作模式。咱们做 UDP,必须用 SOCK_DGRAM。

DGRAM 是 "Datagram" 的缩写,就是数据报的意思。它对应的是 UDP 的无连接、不可靠、面向报文的特性。

跟它相对的,是 SOCK_STREAM,对应 TCP。你看名字就知道,Stream 是流式的,像水管一样,数据源源不断。而 DGRAM 是离散的,像寄快递,每个包裹都是独立的。

常量 对应协议 特点
SOCK_DGRAM UDP 无连接、不可靠、保留消息边界
SOCK_STREAM TCP 面向连接、可靠、字节流

嗯,这里要注意。很多新手会搞混「不可靠」这个词。UDP 的不可靠,不是说它一定会丢包,而是说它不保证不丢包。实际上在局域网里,UDP 丢包率极低。我做过一个内部视频传输系统,跑了几个月,丢包率不到 0.01%。但如果你做的是金融交易系统,那还是老老实实用 TCP 吧。

3.4 创建你的第一个 UDP Socket

理论说完了,咱们直接上代码。创建 UDP Socket 就一行:

import socket

# 创建一个 UDP Socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print("UDP Socket 创建成功!")
print("类型:", udp_socket.type)
print("家族:", udp_socket.family)

# 用完记得关闭
udp_socket.close()

运行这段代码,你会看到控制台输出:

UDP Socket 创建成功!
类型: 2
家族: 2

为什么打印出来是 2?因为 AF_INET 和 SOCK_DGRAM 在 Python 内部就是整数常量。你可以在 Python 交互环境里验证一下:

>>> import socket
>>> socket.AF_INET
2
>>> socket.SOCK_DGRAM
2

巧合的是,这两个常量在大多数系统上都是 2。但这只是实现细节,你千万别在代码里写死数字 2,一定要用常量名。为什么?因为可读性啊。你三个月后回来看代码,看到 AF_INET 一眼就懂,看到 2 还得猜半天。

警告:创建 Socket 后一定要记得关闭!Socket 是操作系统资源,不关闭会导致资源泄漏。特别是在循环里创建 Socket 的场景,忘记关闭的话,很快会把系统的文件描述符耗尽。我曾经排查过一个线上问题,服务跑了三天后突然无法创建新连接,最后发现就是 Socket 没释放。

3.5 用 with 语句管理 Socket 生命周期

手动调用 close() 容易忘。Python 提供了更优雅的方式——上下文管理器。Socket 对象支持 with 语句,退出代码块时自动关闭。

import socket

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket:
    print("UDP Socket 已创建,自动管理生命周期")
    # 在这里进行你的网络操作
    # 不需要手动 close()
    print("Socket 会在退出 with 块时自动关闭")

# 到这里,Socket 已经关闭了

我个人强烈建议你养成用 with 的习惯。它不仅能避免资源泄漏,还能让代码更简洁。你看,少写一行 close(),少一个犯错的机会。

3.6 本章知识体系总览

为了让你对本章内容有个整体印象,我画了一张图。它把 socket() 函数、AF_INET、SOCK_DGRAM 以及创建流程串在了一起。

UDP Socket 创建流程与核心概念 socket.socket() family=AF_INET type=SOCK_DGRAM UDP Socket 对象 后续操作:sendto() / recvfrom() / close() 核心原则:创建 → 使用 → 关闭。推荐使用 with 语句自动管理生命周期。 AF_INET 指定 IPv4,SOCK_DGRAM 指定 UDP 数据报模式。

这张图把整个流程串起来了。你从 socket.socket() 出发,选择 AF_INET 和 SOCK_DGRAM,得到 UDP Socket 对象,然后就可以进行收发操作了。最后别忘了关闭。

3.7 常见错误与避坑指南

讲几个我实际踩过的坑,你遇到了能少走弯路。

  • 参数顺序写反:有人会把 AF_INET 和 SOCK_DGRAM 的顺序搞混。记住,第一个参数是地址家族,第二个是类型。我见过一个同事,把两个参数写反了,结果创建出来的是 TCP Socket,调试了半天才发现。
  • 忘记 import socket:这个错误太常见了。你写 socket.socket() 之前,一定要先 import socket。Python 不会自动帮你导入。
  • 在 Windows 上测试:Windows 的 Socket 实现跟 Linux 有些细微差别。比如 Windows 上关闭 Socket 后,端口不会立即释放,会进入 TIME_WAIT 状态。如果你在 Windows 上做实验,发现端口被占用,等个一两分钟再试就好了。

调试技巧:如果你不确定当前 Socket 的状态,可以用 print(udp_socket.fileno()) 查看文件描述符。返回 -1 表示 Socket 已经关闭。这个技巧在我排查「Socket 是否被意外关闭」时帮了大忙。

3.8 本章小结

这一章我们做了三件事:认识了 socket() 函数,理解了 AF_INET 和 SOCK_DGRAM 的含义,亲手创建了第一个 UDP Socket。别看代码只有几行,这是你踏入 UDP 编程世界的第一步。