Python Socket编程:创建Socket、绑定端口、监听连接、收发数据

Socket编程,说白了就是让两台机器能互相说话。我刚开始学网络编程时,总觉得这东西很神秘——数据怎么就从一台电脑飞到另一台电脑了呢?后来亲手写了几行代码,发现其实没那么玄乎。

今天咱们就聊聊Python里最基础的Socket编程。我会带着你一步步走完整个流程:创建Socket、绑定端口、监听连接、收发数据。嗯,这些都是基本功,但也是后面所有网络应用的基石。

什么是Socket?

Socket可以理解成一个「通信管道」的两端。一端在你这台机器上,另一端在远程服务器上。数据从一端塞进去,从另一端取出来。

我在项目中遇到过不少新手,把Socket想得太复杂。其实你就把它当成一个文件描述符——能读能写,只不过读写的是网络数据而已。

核心概念:Socket是应用层与传输层之间的接口。它屏蔽了底层网络协议的复杂性,让我们可以用简单的read/write操作来收发数据。

Socket的类型

Python的socket模块支持多种类型,但最常用的就两种:

类型 说明 典型场景
socket.SOCK_STREAM 面向连接的TCP HTTP、FTP、SSH
socket.SOCK_DGRAM 无连接的UDP DNS、视频流、游戏

我个人习惯先从TCP开始学,因为它有连接、有确认、有重传,出错时容易排查。UDP虽然快,但调试起来比较头疼——丢包了你都不知道是代码问题还是网络问题。

创建Socket

创建Socket就一行代码:

import socket

# 创建一个TCP Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

AF_INET表示使用IPv4地址。如果你要处理IPv6,就用AF_INET6。不过说实话,我做了这么多年爬虫,99%的情况都是IPv4。

小技巧:创建Socket时可以用with语句来自动关闭,省得忘了调用close()。

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # 使用s
    pass  # 退出with块时自动关闭

绑定端口

创建好Socket后,需要给它绑定一个地址和端口。这就好比你家门牌号——别人要找到你,得知道地址和端口号。

# 绑定到本地所有网卡的8080端口
s.bind(('0.0.0.0', 8080))

这里'0.0.0.0'表示监听所有网络接口。如果你只想监听本机,可以用'127.0.0.1'。我曾经犯过一个低级错误——绑定了'127.0.0.1',然后远程机器死活连不上,折腾了半天才发现是绑错了地址。

注意:端口号范围是0-65535,但0-1023是系统保留端口,需要root权限才能绑定。开发时建议用1024以上的端口,比如8080、8888、9000这些。

监听连接

绑定端口后,Socket进入监听状态,等待客户端来连接。

# 开始监听,最大等待队列长度为5
s.listen(5)
print('服务器启动,等待连接...')

listen()的参数表示最大等待连接数。为什么是5?其实这个数字取决于你的应用场景。高并发服务可能需要设大一些,比如128或1024。但要注意,这个值不是越大越好——它只是等待队列的长度,不是同时处理连接的数量。

接受连接

监听之后,用accept()来接受客户端的连接请求。这个函数会阻塞,直到有客户端连上来。

# 接受一个连接
conn, addr = s.accept()
print(f'客户端 {addr} 已连接')

accept()返回两个值:conn是新的Socket对象,专门用来和这个客户端通信;addr是客户端的地址信息。

这里有个关键点:原来的s还在继续监听,而conn负责处理具体的数据收发。我刚开始学的时候,以为accept()之后原来的Socket就不能用了,其实不是——它俩各司其职。

收发数据

连接建立后,就可以用send()和recv()来收发数据了。

# 接收数据,最多接收1024字节
data = conn.recv(1024)
print(f'收到数据: {data.decode()}')

# 发送数据
conn.send('收到,谢谢!'.encode())

recv()的参数是缓冲区大小。设得太小会导致多次接收,设得太大又浪费内存。1024字节是个比较稳妥的默认值。我在项目中处理大文件时,会设成4096或8192,然后循环接收。

重要提醒:recv()返回的是字节数据,需要用decode()转成字符串。send()需要传入字节数据,用encode()转码。Python 3里字符串和字节是严格区分的,别搞混了。

完整示例:一个简单的回声服务器

把上面的步骤串起来,就是一个完整的TCP服务器:

import socket

def echo_server():
    # 1. 创建Socket
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        # 2. 绑定端口
        s.bind(('0.0.0.0', 8888))
        # 3. 监听
        s.listen(5)
        print('回声服务器已启动,端口: 8888')
        
        while True:
            # 4. 接受连接
            conn, addr = s.accept()
            with conn:
                print(f'客户端 {addr} 已连接')
                while True:
                    # 5. 接收数据
                    data = conn.recv(1024)
                    if not data:
                        break  # 客户端关闭连接
                    # 6. 发送数据(原样返回)
                    conn.send(data)
                print(f'客户端 {addr} 已断开')

if __name__ == '__main__':
    echo_server()

这个回声服务器会把客户端发来的数据原样返回。你可以用telnet或者自己写个客户端来测试:

# 客户端代码
import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(('127.0.0.1', 8888))
    s.send('你好,服务器!'.encode())
    data = s.recv(1024)
    print(f'收到回复: {data.decode()}')

Socket编程的核心流程

下面这张图展示了TCP Socket编程的完整流程,我把它画成了SVG,方便你理解:

TCP Socket编程流程 服务器端 1. socket() 创建Socket 2. bind() 绑定地址和端口 3. listen() 开始监听 4. accept() 接受连接 5. recv() / send() 收发数据 6. close() 关闭连接 客户端 1. socket() 创建Socket 2. connect() 连接服务器 3. send() / recv() 收发数据 4. close() 关闭连接 TCP三次握手 数据 数据

避坑指南

做Socket编程,有几个坑我踩过,分享给你:

  • 端口被占用:我曾经在调试时频繁重启服务器,结果端口被上一个进程占着没释放。解决方案是用SO_REUSEADDR选项:s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  • 粘包问题:TCP是流式协议,没有消息边界。如果你连续发送多条消息,接收方可能一次收到多条。解决办法是自定义协议,比如每条消息前面加4字节的长度字段。
  • 阻塞与超时:recv()默认是阻塞的,如果对方不发数据,程序就卡在那了。可以设置超时:s.settimeout(5.0),5秒没数据就抛出异常。
  • 半关闭状态:调用close()会立即关闭连接,可能导致数据丢失。优雅的做法是用shutdown()先关闭写方向,等对方收到数据后再关闭。

我的经验:写Socket程序时,一定要加异常处理。网络环境千变万化,连接断开、超时、对方拒绝连接……这些情况随时可能发生。用try/except包住关键代码,至少打印个日志,不然出问题了都不知道怎么回事。

总结

Socket编程的核心就四个步骤:创建、绑定、监听、收发。听起来简单,但每个环节都有细节要注意。我建议你亲手敲一遍代码,跑通回声服务器,再试着改一改——比如改成并发处理多个客户端,或者加上简单的协议解析。

记住,网络编程没有捷径。多写、多调试、多踩坑,慢慢就熟练了。

可可精品资料库