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

Python socket 模块函数的应用

2021-04-145.0k 阅读

Python socket 模块简介

在计算机网络编程中,socket(套接字)是一个关键概念。它为应用程序提供了一种通过网络进行通信的方式。Python 中的 socket 模块提供了对底层 socket 接口的访问,使得开发者能够轻松地创建网络应用,无论是客户端还是服务器端。

socket 模块是基于 BSD(Berkeley Software Distribution)套接字接口的 Python 实现。它支持多种网络协议,如 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议),同时也适用于不同的网络地址族,比如 IPv4(AF_INET)和 IPv6(AF_INET6)。

socket 模块的安装

在大多数 Python 环境中,socket 模块是内置的,无需额外安装。Python 标准库自带了这个模块,只要你安装了 Python,就可以直接使用它。你可以在 Python 解释器中通过简单的导入操作来验证:

try:
    import socket
    print("socket 模块可用")
except ImportError:
    print("socket 模块未找到")

socket 模块中的重要函数

socket.socket() 函数

  • 作用:创建一个新的 socket 对象。这是使用 socket 模块进行网络编程的第一步,通过该函数我们可以指定要使用的地址族和套接字类型。
  • 语法socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
    • family:指定地址族。常见的值有 AF_INET(用于 IPv4 地址)和 AF_INET6(用于 IPv6 地址)。如果不指定,默认使用 AF_INET
    • type:指定套接字类型。常见的类型有 SOCK_STREAM(用于 TCP 协议,提供可靠的、面向连接的通信)和 SOCK_DGRAM(用于 UDP 协议,提供不可靠的、无连接的通信)。默认值为 SOCK_STREAM
    • proto:通常设为 0,用于指定协议类型。对于大多数情况,默认值就足够了。
    • fileno:如果指定了 fileno,则使用该文件描述符对应的 socket,而不是创建一个新的 socket。这个参数很少使用。

以下是创建不同类型 socket 的示例代码:

import socket

# 创建一个 IPv4 TCP socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建一个 IPv4 UDP socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DUDP)

# 创建一个 IPv6 TCP socket
ipv6_tcp_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

socket.bind() 函数

  • 作用:将 socket 绑定到一个地址。在服务器端编程中,这一步至关重要,它告诉系统该 socket 要监听哪个网络接口和端口号。
  • 语法socket.bind(address)
    • address:对于 IPv4 地址族,address 是一个包含 IP 地址和端口号的元组,例如 ('127.0.0.1', 8888)。对于 IPv6 地址族,address 是一个包含 IPv6 地址、端口号、流信息和作用域 ID 的元组,例如 ('2001:db8::1', 8888, 0, 0)

以下是服务器端绑定 socket 的示例代码:

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
server_socket.bind(server_address)

socket.listen() 函数

  • 作用:使服务器 socket 进入监听状态,准备接受客户端的连接请求。它会在指定的地址和端口上监听传入的连接。
  • 语法socket.listen(backlog)
    • backlog:指定在拒绝连接之前,操作系统可以挂起的最大连接数量。通常设为一个合理的值,如 5 到 10。

以下是服务器端设置监听的示例代码:

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
server_socket.bind(server_address)
server_socket.listen(5)
print("服务器正在监听 127.0.0.1:8888")

socket.accept() 函数

  • 作用:在服务器端,该函数用于接受客户端的连接请求。它是一个阻塞调用,即程序会在此处等待,直到有客户端连接进来。当有连接到达时,它会返回一个新的 socket 对象(用于与客户端通信)和客户端的地址。
  • 语法socket.accept()
    • 返回值:一个包含两个元素的元组 (conn, address),其中 conn 是新的 socket 对象,用于与客户端进行通信;address 是客户端的地址。

以下是完整的服务器端接受客户端连接的示例代码:

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
server_socket.bind(server_address)
server_socket.listen(5)
print("服务器正在监听 127.0.0.1:8888")

while True:
    conn, client_address = server_socket.accept()
    print(f"接受来自 {client_address} 的连接")
    conn.close()

socket.connect() 函数

  • 作用:在客户端,该函数用于连接到服务器。它尝试与指定地址和端口的服务器建立连接。
  • 语法socket.connect(address)
    • address:与 bind() 函数中的 address 格式相同,对于 IPv4 是 (ip_address, port) 元组,对于 IPv6 是 (ipv6_address, port, flowinfo, scopeid) 元组。

以下是客户端连接服务器的示例代码:

import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
client_socket.connect(server_address)
print("已连接到服务器 127.0.0.1:8888")
client_socket.close()

socket.connect_ex() 函数

  • 作用:与 connect() 函数类似,也是用于客户端连接服务器。但 connect_ex() 函数不会引发异常,而是返回错误码。这在需要更精细地控制连接错误时很有用。
  • 语法socket.connect_ex(address)
    • 返回值:如果连接成功,返回 0;如果连接失败,返回相应的错误码。

以下是使用 connect_ex() 函数的示例代码:

import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
result = client_socket.connect_ex(server_address)
if result == 0:
    print("已连接到服务器 127.0.0.1:8888")
else:
    print(f"连接失败,错误码: {result}")
client_socket.close()

socket.send() 函数

  • 作用:用于通过 socket 发送数据。在 TCP 套接字中,它会将数据发送到连接的另一端。数据必须是字节类型(bytes)。
  • 语法socket.send(bytes)
    • bytes:要发送的字节数据。

以下是服务器端向客户端发送数据的示例代码:

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
server_socket.bind(server_address)
server_socket.listen(5)
print("服务器正在监听 127.0.0.1:8888")

while True:
    conn, client_address = server_socket.accept()
    print(f"接受来自 {client_address} 的连接")
    data_to_send = b"Hello, client!"
    conn.send(data_to_send)
    conn.close()

socket.sendall() 函数

  • 作用:同样用于通过 socket 发送数据,但与 send() 不同的是,sendall() 会尝试发送所有数据,直到所有数据都被发送或者发生错误。在网络不稳定的情况下,sendall()send() 更可靠。
  • 语法socket.sendall(bytes)
    • bytes:要发送的字节数据。

以下是使用 sendall() 函数的示例代码:

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
server_socket.bind(server_address)
server_socket.listen(5)
print("服务器正在监听 127.0.0.1:8888")

while True:
    conn, client_address = server_socket.accept()
    print(f"接受来自 {client_address} 的连接")
    data_to_send = b"Hello, client! This is a longer message."
    conn.sendall(data_to_send)
    conn.close()

socket.recv() 函数

  • 作用:用于从 socket 接收数据。在 TCP 套接字中,它会从连接的另一端接收数据。
  • 语法socket.recv(bufsize, flags=0)
    • bufsize:指定一次最多接收多少字节的数据。通常设置为一个合理的大小,如 1024 字节。
    • flags:通常设为 0,用于指定接收数据的方式。

以下是客户端接收服务器数据的示例代码:

import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
client_socket.connect(server_address)

data = client_socket.recv(1024)
print(f"接收到的数据: {data.decode('utf - 8')}")
client_socket.close()

socket.sendto() 函数

  • 作用:用于 UDP 套接字发送数据。与 TCP 不同,UDP 是无连接的,因此 sendto() 函数需要指定目标地址。
  • 语法socket.sendto(bytes, address)
    • bytes:要发送的字节数据。
    • address:目标地址,格式为 (ip_address, port) 元组。

以下是 UDP 客户端发送数据的示例代码:

import socket

udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
server_address = ('127.0.0.1', 9999)
data_to_send = b"Hello, UDP server!"
udp_client_socket.sendto(data_to_send, server_address)
udp_client_socket.close()

socket.recvfrom() 函数

  • 作用:用于 UDP 套接字接收数据。它不仅会接收数据,还会返回发送方的地址。
  • 语法socket.recvfrom(bufsize, flags=0)
    • bufsize:指定一次最多接收多少字节的数据。
    • flags:通常设为 0。

以下是 UDP 服务器接收数据的示例代码:

import socket

udp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
server_address = ('127.0.0.1', 9999)
udp_server_socket.bind(server_address)

data, client_address = udp_server_socket.recvfrom(1024)
print(f"从 {client_address} 接收到的数据: {data.decode('utf - 8')}")
udp_server_socket.close()

socket.close() 函数

  • 作用:关闭 socket,释放相关资源。在完成网络通信后,无论是客户端还是服务器端,都应该调用该函数关闭 socket。
  • 语法socket.close()

以下是关闭 socket 的示例代码:

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 进行一些操作,如绑定、监听、接受连接等
server_socket.close()

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 8888))
# 进行一些操作,如发送和接收数据
client_socket.close()

socket 模块在实际应用中的案例

简单的 TCP 聊天程序

  1. 服务器端代码
import socket


def start_server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_address = ('127.0.0.1', 8888)
    server_socket.bind(server_address)
    server_socket.listen(5)
    print("服务器正在监听 127.0.0.1:8888")

    while True:
        conn, client_address = server_socket.accept()
        print(f"接受来自 {client_address} 的连接")
        while True:
            try:
                data = conn.recv(1024)
                if not data:
                    break
                print(f"客户端说: {data.decode('utf - 8')}")
                response = input("请输入回复内容: ").encode('utf - 8')
                conn.send(response)
            except Exception as e:
                print(f"发生错误: {e}")
                break
        conn.close()


if __name__ == "__main__":
    start_server()
  1. 客户端代码
import socket


def start_client():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_address = ('127.0.0.1', 8888)
    client_socket.connect(server_address)

    while True:
        message = input("请输入要发送的消息: ").encode('utf - 8')
        client_socket.send(message)
        data = client_socket.recv(1024)
        if not data:
            break
        print(f"服务器回复: {data.decode('utf - 8')}")
    client_socket.close()


if __name__ == "__main__":
    start_client()

UDP 文件传输模拟

  1. 发送端代码
import socket
import os


def send_file_udp():
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
    server_address = ('127.0.0.1', 9999)
    file_path = "example.txt"
    if not os.path.exists(file_path):
        print(f"文件 {file_path} 不存在")
        return

    with open(file_path, 'rb') as file:
        while True:
            data = file.read(1024)
            if not data:
                break
            udp_socket.sendto(data, server_address)
    # 发送结束标志
    end_marker = b"END_OF_FILE"
    udp_socket.sendto(end_marker, server_address)
    udp_socket.close()


if __name__ == "__main__":
    send_file_udp()
  1. 接收端代码
import socket
import os


def receive_file_udp():
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DUDP)
    server_address = ('127.0.0.1', 9999)
    udp_socket.bind(server_address)
    received_data = b""
    while True:
        data, client_address = udp_socket.recvfrom(1024)
        if data == b"END_OF_FILE":
            break
        received_data += data
    with open("received_example.txt", 'wb') as file:
        file.write(received_data)
    udp_socket.close()


if __name__ == "__main__":
    receive_file_udp()

socket 模块使用中的注意事项

  1. 错误处理:在使用 socket 模块的函数时,可能会发生各种错误,如连接超时、地址被占用等。应该始终使用适当的异常处理机制来捕获和处理这些错误,以提高程序的稳定性。例如,在使用 connect() 函数时,可以捕获 ConnectionRefusedError 异常来处理服务器拒绝连接的情况。
import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8888)
try:
    client_socket.connect(server_address)
except ConnectionRefusedError:
    print("连接被拒绝,服务器可能未运行或端口不正确")
client_socket.close()
  1. 字节编码:在发送和接收数据时,要注意数据的编码和解码。send()sendto() 函数要求数据是字节类型(bytes),而 recv()recvfrom() 函数返回的也是字节类型数据。如果要处理文本数据,需要在发送前进行编码(如 str.encode('utf - 8')),在接收后进行解码(如 bytes.decode('utf - 8'))。
  2. 资源管理:确保在完成网络通信后,及时关闭 socket。未关闭的 socket 可能会导致资源泄漏,影响系统性能。使用 try - finally 语句可以确保即使在发生异常时,socket 也能被正确关闭。
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    server_address = ('127.0.0.1', 8888)
    server_socket.bind(server_address)
    server_socket.listen(5)
    # 进行一些操作
finally:
    server_socket.close()
  1. 网络延迟和丢包:在实际网络环境中,网络延迟和丢包是不可避免的。对于 TCP 协议,它本身提供了可靠性机制来处理这些问题,但对于 UDP 协议,开发者需要自己实现一些机制来处理丢包和重传等问题。例如,可以使用定时器来检测数据包是否丢失,并进行重传。

通过深入理解和熟练运用 socket 模块中的函数,开发者可以创建出各种功能强大的网络应用程序,无论是简单的客户端 - 服务器通信,还是复杂的分布式系统。同时,注意使用过程中的各种细节和注意事项,能够使网络应用更加稳定和可靠。