MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python 套接字对象内置方法的使用

2021-09-283.7k 阅读

一、Python 套接字简介

在计算机网络编程中,套接字(Socket)是一种用于进程间通信(IPC)的机制,它允许不同主机或同一主机上的不同进程进行数据交换。Python 通过 socket 模块提供了对套接字编程的支持。套接字对象拥有一系列内置方法,这些方法是实现网络通信的关键,它们涵盖了创建连接、发送和接收数据以及管理套接字状态等多个方面。

(一)套接字家族

在 Python 的 socket 模块中,主要有两种套接字家族:

  1. AF_INET:用于 IPv4 网络。这是目前最常用的套接字家族,大多数互联网应用都基于 IPv4 进行通信。例如,当我们要与一个网站服务器建立连接时,通常会使用 AF_INET 家族的套接字。
  2. AF_INET6:用于 IPv6 网络。随着 IPv4 地址的逐渐耗尽,IPv6 变得越来越重要。AF_INET6 套接字家族为支持 IPv6 网络通信提供了必要的接口。

(二)套接字类型

除了套接字家族,还有不同的套接字类型,常见的有:

  1. SOCK_STREAM:基于 TCP(传输控制协议)的流套接字。TCP 是一种可靠的、面向连接的协议,它保证数据的有序传输和完整性。常用于需要可靠数据传输的应用,如文件传输、网页浏览等。
  2. 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() 方法

  1. 功能bind() 方法用于将套接字绑定到一个地址。在服务器端编程中,这一步是必不可少的,它告诉操作系统该套接字将在哪个 IP 地址和端口上监听连接。
  2. 语法socket.bind(address),其中 address 是一个元组,对于 IPv4 套接字,格式为 (host, port)host 是主机名或 IP 地址,port 是端口号;对于 IPv6 套接字,格式为 (host, port, flowinfo, scopeid),不过在大多数常见情况下,后两个参数可以省略。
  3. 示例
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.18888 端口。如果绑定成功,该套接字就准备好监听这个地址和端口上的连接了。

(二)listen() 方法

  1. 功能listen() 方法使套接字处于监听状态,准备接受客户端的连接请求。它设置了等待连接队列的最大长度。
  2. 语法socket.listen(backlog)backlog 是等待连接队列的最大长度。当有多个客户端同时尝试连接服务器时,未被立即处理的连接请求会被放入这个队列中。
  3. 示例
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() 方法

  1. 功能accept() 方法用于接受客户端的连接。它是一个阻塞方法,即当没有客户端连接时,程序会在此处暂停,直到有客户端连接到来。当有连接到达时,它返回一个新的套接字对象,用于与客户端进行通信,同时返回客户端的地址。
  2. 语法client_socket, client_address = socket.accept()client_socket 是新的套接字对象,用于与客户端通信;client_address 是客户端的地址。
  3. 示例
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() 方法

  1. 功能connect() 方法用于客户端连接到服务器。客户端通过指定服务器的地址和端口,使用这个方法尝试与服务器建立连接。
  2. 语法socket.connect(address)address 是服务器的地址,格式与服务器端 bind() 方法中的地址格式相同。
  3. 示例
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() 方法

  1. 功能connect_ex() 方法与 connect() 方法类似,也是用于客户端连接到服务器,但它在连接失败时不会引发异常,而是返回一个错误码。这使得客户端可以更灵活地处理连接失败的情况。
  2. 语法err_code = socket.connect_ex(address)err_code 是连接操作的错误码,如果连接成功,err_code0
  3. 示例
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() 方法

  1. 功能send() 方法用于通过套接字发送数据。它通常用于面向连接的套接字(如 TCP 套接字),将数据发送到已连接的对方。
  2. 语法bytes_sent = socket.send(data)data 是要发送的数据,必须是字节类型(bytes);bytes_sent 是实际发送的字节数。
  3. 示例
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() 方法

  1. 功能sendall() 方法也用于发送数据,但它会尝试发送所有的数据,直到所有数据都被成功发送或者出现错误。这对于确保数据完整发送非常有用,特别是在发送较大数据量时。
  2. 语法socket.sendall(data)data 同样是字节类型的数据。
  3. 示例
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() 方法

  1. 功能recv() 方法用于从套接字接收数据。它是一个阻塞方法,会等待直到有数据到达或者连接关闭。
  2. 语法data = socket.recv(bufsize)bufsize 是指定接收缓冲区的大小,以字节为单位;data 是接收到的数据,为字节类型。
  3. 示例
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() 方法

  1. 功能sendto() 方法主要用于无连接的套接字(如 UDP 套接字),它将数据发送到指定的地址。
  2. 语法bytes_sent = socket.sendto(data, address)data 是要发送的字节类型数据,address 是目标地址。
  3. 示例
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() 方法

  1. 功能recvfrom() 方法用于从无连接的套接字(如 UDP 套接字)接收数据,并返回发送方的地址。
  2. 语法data, address = socket.recvfrom(bufsize)bufsize 是接收缓冲区大小,data 是接收到的字节类型数据,address 是发送方的地址。
  3. 示例
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() 方法

  1. 功能setsockopt() 方法用于设置套接字选项。通过这个方法,可以调整套接字的各种行为,如重用地址、设置超时等。
  2. 语法socket.setsockopt(level, optname, value)level 表示选项的级别,常见的有 SOL_SOCKET(通用套接字选项)、IPPROTO_TCP(TCP 特定选项)、IPPROTO_IP(IP 特定选项)等;optname 是具体的选项名称;value 是选项的值。
  3. 示例
    • 重用地址
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() 方法

  1. 功能getsockopt() 方法用于获取套接字选项的值。通过这个方法,可以查询当前套接字的各种设置。
  2. 语法value = socket.getsockopt(level, optname)leveloptnamesetsockopt() 方法中的含义相同,value 是获取到的选项值。
  3. 示例
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() 方法

  1. 功能fileno() 方法返回套接字的文件描述符。文件描述符是操作系统用于标识打开文件或套接字的整数。在某些情况下,如与其他需要文件描述符的系统调用或库函数集成时,这个方法很有用。
  2. 语法fd = socket.fileno()fd 是返回的文件描述符。
  3. 示例
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() 方法

  1. 功能close() 方法用于关闭套接字。当不再需要使用套接字时,应调用此方法释放资源,关闭连接。
  2. 语法socket.close()
  3. 示例
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() 方法

  1. 功能shutdown() 方法用于关闭套接字的某个方向上的通信。它可以选择关闭发送、接收或者双向通信。这在需要优雅地关闭连接,确保所有数据都被发送或接收完毕时很有用。
  2. 语法socket.shutdown(how)how 取值可以是 socket.SHUT_RD(关闭接收)、socket.SHUT_WR(关闭发送)、socket.SHUT_RDWR(关闭发送和接收)。
  3. 示例
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 应用、网络爬虫还是实时通信系统,套接字编程都是必不可少的技能。