Python 套接字对象内置方法的使用
一、Python 套接字简介
在计算机网络编程中,套接字(Socket)是一种用于进程间通信(IPC)的机制,它允许不同主机或同一主机上的不同进程进行数据交换。Python 通过 socket
模块提供了对套接字编程的支持。套接字对象拥有一系列内置方法,这些方法是实现网络通信的关键,它们涵盖了创建连接、发送和接收数据以及管理套接字状态等多个方面。
(一)套接字家族
在 Python 的 socket
模块中,主要有两种套接字家族:
- AF_INET:用于 IPv4 网络。这是目前最常用的套接字家族,大多数互联网应用都基于 IPv4 进行通信。例如,当我们要与一个网站服务器建立连接时,通常会使用 AF_INET 家族的套接字。
- AF_INET6:用于 IPv6 网络。随着 IPv4 地址的逐渐耗尽,IPv6 变得越来越重要。AF_INET6 套接字家族为支持 IPv6 网络通信提供了必要的接口。
(二)套接字类型
除了套接字家族,还有不同的套接字类型,常见的有:
- SOCK_STREAM:基于 TCP(传输控制协议)的流套接字。TCP 是一种可靠的、面向连接的协议,它保证数据的有序传输和完整性。常用于需要可靠数据传输的应用,如文件传输、网页浏览等。
- SOCK_DGRAM:基于 UDP(用户数据报协议)的数据报套接字。UDP 是一种不可靠的、无连接的协议,它不保证数据的有序到达和完整性,但具有传输速度快、开销小的特点。常用于实时性要求较高但对数据准确性要求相对较低的应用,如音频和视频流传输。
二、创建套接字对象
在使用套接字对象的内置方法之前,首先需要创建一个套接字对象。这通过 socket.socket()
函数来实现,该函数接受两个参数:套接字家族和套接字类型。
(一)创建 IPv4 TCP 套接字
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
在上述代码中,我们使用 socket.socket(socket.AF_INET, socket.SOCK_STREAM)
创建了一个 IPv4 TCP 套接字对象 sock
。
(二)创建 IPv4 UDP 套接字
import socket
# 创建一个 IPv4 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
这里通过 socket.socket(socket.AF_INET, socket.SOCK_DUDP)
创建了一个 IPv4 UDP 套接字对象。
(三)创建 IPv6 TCP 套接字
import socket
# 创建一个 IPv6 TCP 套接字
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
使用 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
则创建了一个 IPv6 TCP 套接字。
三、服务器端套接字方法
(一)bind() 方法
- 功能:
bind()
方法用于将套接字绑定到一个地址。在服务器端编程中,这一步是必不可少的,它告诉操作系统该套接字将在哪个 IP 地址和端口上监听连接。 - 语法:
socket.bind(address)
,其中address
是一个元组,对于 IPv4 套接字,格式为(host, port)
,host
是主机名或 IP 地址,port
是端口号;对于 IPv6 套接字,格式为(host, port, flowinfo, scopeid)
,不过在大多数常见情况下,后两个参数可以省略。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定到本地地址 127.0.0.1 的 8888 端口
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
在这个例子中,我们将 IPv4 TCP 套接字绑定到本地回环地址 127.0.0.1
的 8888
端口。如果绑定成功,该套接字就准备好监听这个地址和端口上的连接了。
(二)listen() 方法
- 功能:
listen()
方法使套接字处于监听状态,准备接受客户端的连接请求。它设置了等待连接队列的最大长度。 - 语法:
socket.listen(backlog)
,backlog
是等待连接队列的最大长度。当有多个客户端同时尝试连接服务器时,未被立即处理的连接请求会被放入这个队列中。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
# 设置监听,最大等待连接数为 5
sock.listen(5)
这里我们将等待连接队列的最大长度设置为 5
,表示最多可以有 5
个未处理的连接请求在队列中等待。
(三)accept() 方法
- 功能:
accept()
方法用于接受客户端的连接。它是一个阻塞方法,即当没有客户端连接时,程序会在此处暂停,直到有客户端连接到来。当有连接到达时,它返回一个新的套接字对象,用于与客户端进行通信,同时返回客户端的地址。 - 语法:
client_socket, client_address = socket.accept()
,client_socket
是新的套接字对象,用于与客户端通信;client_address
是客户端的地址。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
sock.listen(5)
print('等待客户端连接...')
# 接受客户端连接
client_socket, client_address = sock.accept()
print(f'客户端 {client_address} 已连接')
# 关闭与客户端的连接
client_socket.close()
# 关闭服务器套接字
sock.close()
在这个示例中,服务器套接字在接受客户端连接后,打印出客户端的地址,然后关闭与客户端的连接以及服务器套接字。
四、客户端套接字方法
(一)connect() 方法
- 功能:
connect()
方法用于客户端连接到服务器。客户端通过指定服务器的地址和端口,使用这个方法尝试与服务器建立连接。 - 语法:
socket.connect(address)
,address
是服务器的地址,格式与服务器端bind()
方法中的地址格式相同。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
# 连接到服务器
sock.connect(server_address)
print('已连接到服务器')
# 关闭套接字
sock.close()
此代码中,客户端套接字尝试连接到本地服务器的 127.0.0.1:8888
地址,如果连接成功则打印提示信息,最后关闭套接字。
(二)connect_ex() 方法
- 功能:
connect_ex()
方法与connect()
方法类似,也是用于客户端连接到服务器,但它在连接失败时不会引发异常,而是返回一个错误码。这使得客户端可以更灵活地处理连接失败的情况。 - 语法:
err_code = socket.connect_ex(address)
,err_code
是连接操作的错误码,如果连接成功,err_code
为0
。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
# 尝试连接到服务器并获取错误码
err_code = sock.connect_ex(server_address)
if err_code == 0:
print('已连接到服务器')
else:
print(f'连接失败,错误码: {err_code}')
# 关闭套接字
sock.close()
在这个示例中,客户端通过 connect_ex()
方法尝试连接服务器,并根据返回的错误码判断连接是否成功。
五、数据发送与接收方法
(一)send() 方法
- 功能:
send()
方法用于通过套接字发送数据。它通常用于面向连接的套接字(如 TCP 套接字),将数据发送到已连接的对方。 - 语法:
bytes_sent = socket.send(data)
,data
是要发送的数据,必须是字节类型(bytes
);bytes_sent
是实际发送的字节数。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.connect(server_address)
message = 'Hello, Server!'.encode('utf - 8')
# 发送数据
bytes_sent = sock.send(message)
print(f'发送了 {bytes_sent} 字节数据')
# 关闭套接字
sock.close()
在这段代码中,客户端将字符串 Hello, Server!
编码为字节类型后,通过 send()
方法发送给服务器,并打印实际发送的字节数。
(二)sendall() 方法
- 功能:
sendall()
方法也用于发送数据,但它会尝试发送所有的数据,直到所有数据都被成功发送或者出现错误。这对于确保数据完整发送非常有用,特别是在发送较大数据量时。 - 语法:
socket.sendall(data)
,data
同样是字节类型的数据。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.connect(server_address)
message = 'This is a large amount of data to be sent'.encode('utf - 8')
# 发送所有数据
sock.sendall(message)
print('数据已全部发送')
# 关闭套接字
sock.close()
此示例中,客户端使用 sendall()
方法发送一段较长的数据,并在发送完成后打印提示信息。
(三)recv() 方法
- 功能:
recv()
方法用于从套接字接收数据。它是一个阻塞方法,会等待直到有数据到达或者连接关闭。 - 语法:
data = socket.recv(bufsize)
,bufsize
是指定接收缓冲区的大小,以字节为单位;data
是接收到的数据,为字节类型。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
sock.listen(5)
print('等待客户端连接...')
client_socket, client_address = sock.accept()
print(f'客户端 {client_address} 已连接')
# 接收数据,缓冲区大小为 1024 字节
data = client_socket.recv(1024)
print(f'接收到数据: {data.decode("utf - 8")}')
# 关闭与客户端的连接
client_socket.close()
# 关闭服务器套接字
sock.close()
在这个服务器端代码示例中,服务器在接受客户端连接后,使用 recv()
方法接收客户端发送的数据,并将其解码后打印出来。
(四)sendto() 方法
- 功能:
sendto()
方法主要用于无连接的套接字(如 UDP 套接字),它将数据发送到指定的地址。 - 语法:
bytes_sent = socket.sendto(data, address)
,data
是要发送的字节类型数据,address
是目标地址。 - 示例:
import socket
# 创建一个 IPv4 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
server_address = ('127.0.0.1', 9999)
message = 'Hello, UDP Server!'.encode('utf - 8')
# 发送数据到指定地址
bytes_sent = sock.sendto(message, server_address)
print(f'发送了 {bytes_sent} 字节数据')
# 关闭套接字
sock.close()
这里客户端通过 UDP 套接字使用 sendto()
方法向指定的服务器地址发送数据。
(五)recvfrom() 方法
- 功能:
recvfrom()
方法用于从无连接的套接字(如 UDP 套接字)接收数据,并返回发送方的地址。 - 语法:
data, address = socket.recvfrom(bufsize)
,bufsize
是接收缓冲区大小,data
是接收到的字节类型数据,address
是发送方的地址。 - 示例:
import socket
# 创建一个 IPv4 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
server_address = ('127.0.0.1', 9999)
sock.bind(server_address)
print('等待接收数据...')
# 接收数据,缓冲区大小为 1024 字节
data, client_address = sock.recvfrom(1024)
print(f'从 {client_address} 接收到数据: {data.decode("utf - 8")}')
# 关闭套接字
sock.close()
此服务器端代码使用 UDP 套接字通过 recvfrom()
方法接收客户端发送的数据,并打印出发送方的地址和数据。
六、套接字选项设置方法
(一)setsockopt() 方法
- 功能:
setsockopt()
方法用于设置套接字选项。通过这个方法,可以调整套接字的各种行为,如重用地址、设置超时等。 - 语法:
socket.setsockopt(level, optname, value)
,level
表示选项的级别,常见的有SOL_SOCKET
(通用套接字选项)、IPPROTO_TCP
(TCP 特定选项)、IPPROTO_IP
(IP 特定选项)等;optname
是具体的选项名称;value
是选项的值。 - 示例:
- 重用地址:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置重用地址选项
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
sock.listen(5)
在这个示例中,通过 setsockopt()
方法设置了 SO_REUSEADDR
选项,使得套接字在程序关闭后可以立即重用地址,避免了 Address already in use
的错误。
- 设置超时:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置接收超时为 5 秒
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 5)
server_address = ('127.0.0.1', 8888)
sock.connect(server_address)
try:
data = sock.recv(1024)
print(f'接收到数据: {data.decode("utf - 8")}')
except socket.timeout:
print('接收超时')
# 关闭套接字
sock.close()
这里设置了接收超时选项 SO_RCVTIMEO
,如果在 5 秒内没有接收到数据,recv()
方法将引发 socket.timeout
异常。
(二)getsockopt() 方法
- 功能:
getsockopt()
方法用于获取套接字选项的值。通过这个方法,可以查询当前套接字的各种设置。 - 语法:
value = socket.getsockopt(level, optname)
,level
和optname
与setsockopt()
方法中的含义相同,value
是获取到的选项值。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置重用地址选项
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 获取重用地址选项的值
reuse_addr_value = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
print(f'重用地址选项的值: {reuse_addr_value}')
# 关闭套接字
sock.close()
在这个示例中,先设置了重用地址选项,然后通过 getsockopt()
方法获取该选项的值并打印出来。
七、其他常用方法
(一)fileno() 方法
- 功能:
fileno()
方法返回套接字的文件描述符。文件描述符是操作系统用于标识打开文件或套接字的整数。在某些情况下,如与其他需要文件描述符的系统调用或库函数集成时,这个方法很有用。 - 语法:
fd = socket.fileno()
,fd
是返回的文件描述符。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
sock.listen(5)
# 获取套接字的文件描述符
fd = sock.fileno()
print(f'套接字的文件描述符: {fd}')
# 关闭套接字
sock.close()
此代码创建一个服务器套接字后,获取并打印其文件描述符。
(二)close() 方法
- 功能:
close()
方法用于关闭套接字。当不再需要使用套接字时,应调用此方法释放资源,关闭连接。 - 语法:
socket.close()
- 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
sock.listen(5)
print('等待客户端连接...')
client_socket, client_address = sock.accept()
print(f'客户端 {client_address} 已连接')
# 关闭与客户端的连接
client_socket.close()
# 关闭服务器套接字
sock.close()
在这个服务器端代码示例中,在与客户端通信完成后,分别关闭与客户端的连接套接字和服务器套接字。
(三)shutdown() 方法
- 功能:
shutdown()
方法用于关闭套接字的某个方向上的通信。它可以选择关闭发送、接收或者双向通信。这在需要优雅地关闭连接,确保所有数据都被发送或接收完毕时很有用。 - 语法:
socket.shutdown(how)
,how
取值可以是socket.SHUT_RD
(关闭接收)、socket.SHUT_WR
(关闭发送)、socket.SHUT_RDWR
(关闭发送和接收)。 - 示例:
import socket
# 创建一个 IPv4 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
sock.bind(server_address)
sock.listen(5)
print('等待客户端连接...')
client_socket, client_address = sock.accept()
print(f'客户端 {client_address} 已连接')
# 关闭发送方向,允许继续接收数据
client_socket.shutdown(socket.SHUT_WR)
# 接收数据,缓冲区大小为 1024 字节
data = client_socket.recv(1024)
print(f'接收到数据: {data.decode("utf - 8")}')
# 关闭与客户端的连接
client_socket.close()
# 关闭服务器套接字
sock.close()
在这个示例中,服务器在接受客户端连接后,关闭了向客户端发送数据的通道,但仍可以接收客户端发送的数据。
通过深入理解和熟练运用这些 Python 套接字对象的内置方法,开发者能够实现各种复杂的网络应用,从简单的客户端 - 服务器通信到大规模的分布式系统。不同的方法在不同的场景下发挥着关键作用,掌握它们是进行高效网络编程的基础。无论是开发 Web 应用、网络爬虫还是实时通信系统,套接字编程都是必不可少的技能。