3. Python Socket编程入门

好,咱们正式开始动手写代码了。

前面两章聊了UDP的原理和广播地址,说白了都是理论。这一章,咱们直接进Python,把UDP Socket跑起来。我个人习惯是,学网络编程一定要先动手,哪怕只是打印个"Hello World",也比看十遍文档强。

3.1 socket模块:Python的网络基石

Python里做网络通信,绕不开的就是socket模块。它是操作系统网络接口的封装,你想想看,操作系统提供了底层的socket API,Python用这个模块帮你包装好,你直接调就行了。

导入方式很简单:

import socket

就这一行,你就拥有了创建网络连接的能力。我在项目中遇到过不少新手,一上来就装各种第三方库,其实标准库的socket模块已经能搞定90%的需求了。

3.2 创建UDP Socket:选对类型很重要

创建Socket的时候,需要指定两个参数:地址族和套接字类型。

  • 地址族:socket.AF_INET 表示IPv4,目前99%的场景都用这个。
  • 套接字类型:socket.SOCK_DGRAM 表示数据报套接字,也就是UDP。

代码长这样:

import socket

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

嗯,这里要注意,如果你不小心用了SOCK_STREAM,那就变成TCP了,后面收发数据的方式完全不一样。我曾经帮一个同事排查问题,他折腾了半天收不到数据,结果发现创建的是TCP Socket——方向错了,努力白费。

3.3 绑定端口:给Socket一个固定的门牌号

Socket创建好之后,它就像一个没有地址的信箱。你要用它收信,得先给它绑定一个端口。绑定端口用bind()方法:

udp_socket.bind(('0.0.0.0', 8888))

这里有两个参数:

  • IP地址:'0.0.0.0' 表示监听所有网络接口。如果你只想监听本机,可以用'127.0.0.1'。
  • 端口号:8888 是随便选的,只要不跟系统服务冲突就行。1024以下的端口需要管理员权限,建议用1024以上的。
避坑指南:我曾经在Windows上写代码,绑定端口后第二次运行报错"Address already in use"。这是因为操作系统还没释放端口。解决办法是加一行udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1),告诉系统端口可以重用。

3.4 发送数据:把消息扔到网络上

UDP发送数据用sendto()方法。它需要两个参数:数据本身和目标地址。

message = 'Hello, UDP!'
udp_socket.sendto(message.encode(), ('192.168.1.100', 8888))

注意几点:

  • 数据必须是字节流,字符串需要先encode()。
  • 目标地址是一个元组,包含IP和端口。
  • UDP不保证送达,发了就发了,没有回执。

说白了,sendto就像往邮筒里扔了一封信,你也不知道对方收没收到。我刚开始学的时候总觉得不靠谱,后来做项目多了才发现,很多场景下这种"尽力而为"的方式反而效率更高。

3.5 接收数据:等着消息上门

接收数据用recvfrom()方法。它需要指定一次最多接收多少字节:

data, addr = udp_socket.recvfrom(1024)
print(f'收到来自 {addr} 的消息:{data.decode()}')

recvfrom返回两个值:

  • data:接收到的字节数据。
  • addr:发送方的IP和端口。

这里有个坑:recvfrom是阻塞的。也就是说,如果没有数据到来,程序会停在这里一直等。为什么会这样?因为操作系统不知道你什么时候会收到数据,干脆让你等着。

小技巧:如果你不想让程序卡住,可以设置超时:udp_socket.settimeout(5.0)。这样5秒内没收到数据就会抛出socket.timeout异常,你可以自己处理。

3.6 完整示例:一个简单的UDP收发程序

把上面的知识点串起来,写一个完整的例子。这个程序既发又收,适合用来测试:

import socket

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

# 绑定端口
udp_socket.bind(('0.0.0.0', 8888))

# 发送数据
message = '你好,UDP!'
udp_socket.sendto(message.encode(), ('127.0.0.1', 8888))
print(f'已发送:{message}')

# 接收数据
data, addr = udp_socket.recvfrom(1024)
print(f'收到来自 {addr} 的消息:{data.decode()}')

# 关闭Socket
udp_socket.close()

这个例子是在本机自己给自己发消息。你运行后,会先打印"已发送",然后打印"收到来自 ('127.0.0.1', 8888) 的消息:你好,UDP!"。嗯,自己跟自己玩,虽然简单,但能验证整个流程是通的。

3.7 本章知识体系

下面这张图帮你理清UDP Socket编程的核心流程:

UDP Socket编程核心流程 1. 创建Socket socket.socket() 2. 绑定端口 bind() 3. 收发数据 sendto()/recvfrom() 发送方与接收方角色可以互换 发送方 ① 准备数据(字符串→字节) ② 调用 sendto() 发送 ③ 不等待确认,继续执行 ④ 可选择性关闭 Socket 接收方 ① 调用 recvfrom() 阻塞等待 ② 收到数据(字节→字符串) ③ 获取发送方地址 ④ 处理数据,继续监听 注意:UDP是无连接的,发送前不需要建立连接

核心要点回顾:

  • 创建UDP Socket用 socket.SOCK_DGRAM
  • 绑定端口用 bind(),接收方必须绑定
  • 发送用 sendto(),接收用 recvfrom()
  • 数据需要编码为字节流
  • recvfrom 是阻塞的,可以设置超时

好了,这一章的内容就到这里。你可以在自己的电脑上把代码敲一遍,感受一下UDP通信的整个过程。下一章我们会聊UDP广播的具体实现,到时候这些基础代码会直接派上用场。