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

Python socket 模块的高级用法

2023-11-201.9k 阅读

1. Python socket 模块概述

Socket 是计算机网络中进程间通信(IPC)的一种机制,它允许不同主机上的进程进行通信,就像是不同房间的人通过电话交流一样。在 Python 中,socket 模块提供了对底层 socket 功能的访问,使开发者能够轻松地创建网络应用程序。

socket 模块最初来源于 Berkeley 套接字接口,它是一种标准的 Unix 网络编程接口,后来被移植到其他操作系统上。Python 的 socket 模块对这一接口进行了封装,提供了更易于使用的 Python 风格的 API。

通过 socket 模块,我们可以创建不同类型的 socket,主要包括两种:

  • TCP socket:面向连接的、可靠的字节流协议。它就像是打电话,双方需要先建立连接,然后才能稳定地传输数据,数据的顺序和完整性能够得到保证。
  • UDP socket:无连接的、不可靠的数据报协议。类似于寄信,不需要事先建立连接,直接把数据发送出去,但不能保证数据一定能到达,也不能保证数据的顺序。

2. 创建基本的 socket 连接

在深入探讨高级用法之前,让我们先看看如何创建一个基本的 socket 连接。

2.1 TCP 服务器端

import socket

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

# 绑定到指定地址和端口
server_address = ('localhost', 8888)
server_socket.bind(server_address)

# 开始监听连接
server_socket.listen(1)
print('等待客户端连接...')

while True:
    # 接受客户端连接
    client_socket, client_address = server_socket.accept()
    print('客户端已连接:', client_address)

    try:
        # 接收数据
        data = client_socket.recv(1024)
        print('收到数据:', data.decode('utf-8'))

        # 发送响应
        response = '数据已收到'.encode('utf-8')
        client_socket.sendall(response)
    finally:
        # 关闭客户端 socket
        client_socket.close()

2.2 TCP 客户端

import socket

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

# 连接到服务器
server_address = ('localhost', 8888)
client_socket.connect(server_address)

try:
    # 发送数据
    message = '你好,服务器!'.encode('utf-8')
    client_socket.sendall(message)

    # 接收响应
    data = client_socket.recv(1024)
    print('收到服务器响应:', data.decode('utf-8'))
finally:
    # 关闭 socket
    client_socket.close()

2.3 UDP 服务器端

import socket

# 创建一个 UDP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 绑定到指定地址和端口
server_address = ('localhost', 8888)
server_socket.bind(server_address)

print('等待接收数据...')
while True:
    # 接收数据和客户端地址
    data, client_address = server_socket.recvfrom(1024)
    print('收到来自 {} 的数据: {}'.format(client_address, data.decode('utf-8')))

    # 发送响应
    response = '数据已收到'.encode('utf-8')
    server_socket.sendto(response, client_address)

2.4 UDP 客户端

import socket

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

# 服务器地址
server_address = ('localhost', 8888)

message = '你好,服务器!'.encode('utf-8')
try:
    # 发送数据
    client_socket.sendto(message, server_address)

    # 接收响应
    data, server = client_socket.recvfrom(1024)
    print('收到服务器响应:', data.decode('utf-8'))
finally:
    # 关闭 socket
    client_socket.close()

3. 高级用法之多连接处理

在实际应用中,服务器往往需要同时处理多个客户端的连接。这就需要使用多线程或异步编程来实现。

3.1 使用多线程处理多个 TCP 连接

import socket
import threading


def handle_client(client_socket, client_address):
    print('客户端已连接:', client_address)
    try:
        # 接收数据
        data = client_socket.recv(1024)
        print('收到数据:', data.decode('utf-8'))

        # 发送响应
        response = '数据已收到'.encode('utf-8')
        client_socket.sendall(response)
    finally:
        # 关闭客户端 socket
        client_socket.close()


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

# 绑定到指定地址和端口
server_address = ('localhost', 8888)
server_socket.bind(server_address)

# 开始监听连接
server_socket.listen(5)
print('等待客户端连接...')

while True:
    # 接受客户端连接
    client_socket, client_address = server_socket.accept()
    # 为每个客户端创建一个新线程
    client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
    client_thread.start()

3.2 使用异步 I/O 处理多个连接(asyncio 库)

import asyncio


async def handle_connection(reader, writer):
    data = await reader.read(1024)
    message = data.decode('utf-8')
    addr = writer.get_extra_info('peername')
    print(f"收到来自 {addr} 的数据: {message}")

    response = '数据已收到'.encode('utf-8')
    writer.write(response)
    await writer.drain()

    writer.close()


async def main():
    server = await asyncio.start_server(handle_connection, 'localhost', 8888)

    addr = server.sockets[0].getsockname()
    print(f"在 {addr} 启动服务器")

    async with server:
        await server.serve_forever()


if __name__ == "__main__":
    asyncio.run(main())

4. socket 选项

socket 模块提供了一系列选项,可以用来调整 socket 的行为。这些选项可以通过 setsockopt 方法来设置,通过 getsockopt 方法来获取。

4.1 SO_REUSEADDR 选项

SO_REUSEADDR 选项允许在 socket 关闭后,立即重用该地址。这在开发过程中非常有用,因为每次重启服务器时,不需要等待操作系统释放地址。

import socket

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

# 设置 SO_REUSEADDR 选项
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定到指定地址和端口
server_address = ('localhost', 8888)
server_socket.bind(server_address)

# 开始监听连接
server_socket.listen(1)
print('等待客户端连接...')

4.2 SO_KEEPALIVE 选项

SO_KEEPALIVE 选项用于启用 TCP 连接的保活机制。如果一段时间内没有数据传输,TCP 会自动发送保活消息,以检测连接是否仍然有效。

import socket

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

# 设置 SO_KEEPALIVE 选项
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

# 绑定到指定地址和端口
server_address = ('localhost', 8888)
server_socket.bind(server_address)

# 开始监听连接
server_socket.listen(1)
print('等待客户端连接...')

5. 超时设置

在网络编程中,设置超时是非常重要的,它可以防止程序在等待数据时无限期阻塞。

5.1 设置 socket 接收超时

import socket

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

# 连接到服务器
server_address = ('localhost', 8888)
client_socket.connect(server_address)

# 设置接收超时为 5 秒
client_socket.settimeout(5)

try:
    # 接收数据
    data = client_socket.recv(1024)
    print('收到数据:', data.decode('utf-8'))
except socket.timeout:
    print('接收数据超时')
finally:
    # 关闭 socket
    client_socket.close()

5.2 设置 socket 发送超时

import socket

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

# 连接到服务器
server_address = ('localhost', 8888)
client_socket.connect(server_address)

# 设置发送超时为 3 秒
client_socket.settimeout(3)

message = '你好,服务器!'.encode('utf-8')
try:
    # 发送数据
    client_socket.sendall(message)
    print('数据已发送')
except socket.timeout:
    print('发送数据超时')
finally:
    # 关闭 socket
    client_socket.close()

6. 高级协议应用

除了基本的 TCP 和 UDP 协议,socket 模块还可以用于实现更高级的协议,如 HTTP、FTP 等。

6.1 简单的 HTTP 服务器

import socket


def handle_http_request(client_socket):
    request = client_socket.recv(1024).decode('utf-8')
    print('收到 HTTP 请求:\n', request)

    response = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n'
    response += '<html><body><h1>你好,世界!</h1></body></html>'

    client_socket.sendall(response.encode('utf-8'))
    client_socket.close()


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

# 绑定到指定地址和端口
server_address = ('localhost', 8080)
server_socket.bind(server_address)

# 开始监听连接
server_socket.listen(1)
print('HTTP 服务器在 http://localhost:8080 上运行')

while True:
    # 接受客户端连接
    client_socket, client_address = server_socket.accept()
    handle_http_request(client_socket)

6.2 简单的 FTP 客户端

import socket


def ftp_client():
    server_address = ('localhost', 21)
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect(server_address)

    # 接收欢迎消息
    welcome_message = client_socket.recv(1024).decode('utf-8')
    print('欢迎消息:', welcome_message)

    # 发送用户名
    username = 'user'.encode('utf-8') + b'\r\n'
    client_socket.sendall(username)
    response = client_socket.recv(1024).decode('utf-8')
    print('用户名响应:', response)

    # 发送密码
    password = 'pass'.encode('utf-8') + b'\r\n'
    client_socket.sendall(password)
    response = client_socket.recv(1024).decode('utf-8')
    print('密码响应:', response)

    # 列出文件
    list_command = 'LIST\r\n'.encode('utf-8')
    client_socket.sendall(list_command)
    data = client_socket.recv(1024).decode('utf-8')
    print('文件列表:', data)

    client_socket.close()


if __name__ == "__main__":
    ftp_client()

7. 错误处理

在网络编程中,错误处理至关重要。socket 模块的操作可能会引发各种异常,如 socket.error。我们需要妥善处理这些异常,以确保程序的稳定性。

7.1 处理连接错误

import socket

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

server_address = ('nonexistenthost', 8888)
try:
    client_socket.connect(server_address)
except socket.gaierror as e:
    print('地址解析错误:', e)
except socket.error as e:
    print('连接错误:', e)
finally:
    client_socket.close()

7.2 处理接收和发送错误

import socket

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

server_address = ('localhost', 8888)
try:
    client_socket.connect(server_address)

    message = '你好,服务器!'.encode('utf-8')
    try:
        client_socket.sendall(message)
    except socket.error as e:
        print('发送错误:', e)

    try:
        data = client_socket.recv(1024)
        print('收到数据:', data.decode('utf-8'))
    except socket.error as e:
        print('接收错误:', e)
except socket.error as e:
    print('连接错误:', e)
finally:
    client_socket.close()

通过以上对 Python socket 模块高级用法的探讨,我们可以看到它在网络编程中的强大功能。无论是创建复杂的多连接服务器,还是实现自定义的网络协议,socket 模块都为我们提供了丰富的工具和方法。在实际应用中,我们需要根据具体需求,灵活运用这些知识,开发出高效、稳定的网络应用程序。