如何使用 Python Socket 发送和接收消息

PythonBeginner
立即练习

介绍

Python sockets 提供了强大的网络通信工具,让你能够在不同的系统之间发送和接收消息。在本教程中,我们将引导你使用 Python sockets 建立连接、传输数据和接收响应。在本次实验(Lab)结束时,你将构建一个客户端和服务器应用程序,它们可以通过网络相互通信。

这些实践知识构成了开发更复杂的网络应用程序的基础,从聊天程序到分布式系统。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 中级 级别的实验,完成率为 71%。获得了学习者 100% 的好评率。

理解 Socket 基础知识并创建你的第一个 Socket

让我们从理解什么是 sockets 以及它们在 Python 中的工作方式开始。然后,我们将创建我们的第一个 socket,看看它是如何初始化的。

什么是 Python Sockets?

Sockets 是通过网络发送和接收数据的端点。它们提供了一个编程接口,用于网络通信,允许应用程序交换信息,而不管它们的位置如何。

在网络编程中,我们通常使用客户端 - 服务器模型:

  • 服务器等待传入的连接并处理请求
  • 客户端通过连接到服务器来启动通信

Python 内置的 socket 模块使得使用 sockets 变得很容易,而无需了解网络协议的所有复杂细节。

Socket 类型

两种最常见的 socket 类型是:

  • TCP (传输控制协议):提供可靠的、有序的数据传递
  • UDP (用户数据报协议):提供更快但不可靠的数据传输

对于本次实验(Lab),我们将重点关注 TCP sockets,这是应用程序需要可靠通信时最常用的类型。

创建你的第一个 Socket

让我们创建一个简单的 Python 脚本来初始化一个 socket。打开 WebIDE 并按照以下步骤操作:

  1. 首先,让我们为我们的 socket 编程项目创建一个目录:
mkdir -p ~/project/socket_lab
cd ~/project/socket_lab
  1. 现在,创建一个名为 socket_basics.py 的新 Python 文件:

在 WebIDE 中,单击“新建文件”按钮或使用“文件”菜单并选择“新建文件”,然后在 socket_lab 目录中将其命名为 socket_basics.py

  1. 添加以下代码来演示如何创建一个 socket:
import socket

## Creating a socket
print("Creating a new socket...")
my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created: {my_socket}")

## Explaining the parameters:
print("\nSocket parameters explained:")
print("AF_INET: Using IPv4 addressing")
print("SOCK_STREAM: Using TCP protocol for reliable data transmission")

## Getting available socket methods
print("\nSome methods available on socket objects:")
methods = [method for method in dir(my_socket) if not method.startswith('_')]
print(', '.join(methods[:10]) + '...')  ## Showing first 10 methods

## Closing the socket
my_socket.close()
print("\nSocket closed")
  1. 保存文件。

  2. 运行脚本以查看输出:

python3 ~/project/socket_lab/socket_basics.py

你应该看到类似于这样的输出:

Creating a new socket...
Socket created: <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>

Socket parameters explained:
AF_INET: Using IPv4 addressing
SOCK_STREAM: Using TCP protocol for reliable data transmission

Some methods available on socket objects:
accept, bind, close, connect, connect_ex, detach, fileno, getpeername, getsockname, getsockopt...

Socket closed

此输出显示我们已成功创建了一个 socket 对象并探索了它的一些属性。在接下来的部分中,我们将使用 sockets 构建一个服务器和客户端应用程序,它们可以相互通信。

关键 Socket 函数

在继续之前,让我们了解一些我们将要使用的关键 socket 函数:

  • socket() - 创建一个新的 socket 对象
  • bind() - 将 socket 与特定的网络接口和端口关联起来
  • listen() - 允许服务器接受连接
  • accept() - 接受来自客户端的连接
  • connect() - 连接到远程地址
  • send() - 将数据发送到已连接的 socket
  • recv() - 从已连接的 socket 接收数据
  • close() - 关闭 socket

在下一步中,我们将使用这些函数创建一个服务器。

创建一个简单的 Socket 服务器

现在我们了解了 sockets 的基础知识,让我们创建一个简单的服务器,它监听连接并从客户端接收消息。

Socket 服务器的工作原理

一个 socket 服务器遵循以下一般步骤:

  1. 创建一个 socket
  2. 将其绑定到地址和端口
  3. 监听传入的连接
  4. 接受客户端连接
  5. 接收和处理数据
  6. 如果需要,发送响应
  7. 关闭连接

让我们在 Python 中实现这种模式。

创建服务器

  1. socket_lab 目录中创建一个名为 server.py 的新 Python 文件:

在 WebIDE 中,单击“新建文件”按钮或使用“文件”菜单并选择“新建文件”,然后在 socket_lab 目录中将其命名为 server.py

  1. 添加以下代码以创建一个基本服务器:
import socket

def start_server():
    ## Server configuration
    host = '127.0.0.1'  ## localhost
    port = 12345        ## arbitrary non-privileged port

    ## Create socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    ## Set socket option to reuse address (helps avoid "Address already in use" errors)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    ## Bind socket to address and port
    server_socket.bind((host, port))

    ## Listen for connections (queue up to 5 connection requests)
    server_socket.listen(5)

    print(f"Server started on {host}:{port}")
    print("Waiting for client connection...")

    try:
        ## Accept connection
        client_socket, client_address = server_socket.accept()
        print(f"Connection established with {client_address}")

        ## Receive data from client
        data = client_socket.recv(1024)  ## receive up to 1024 bytes
        print(f"Message received: {data.decode()}")

        ## Send response to client
        response = "Message received by server"
        client_socket.send(response.encode())

        ## Close client connection
        client_socket.close()

    except KeyboardInterrupt:
        print("\nServer shutting down...")
    finally:
        ## Close server socket
        server_socket.close()
        print("Server socket closed")

if __name__ == "__main__":
    start_server()
  1. 保存文件。

理解服务器代码

让我们分解一下服务器的关键组件:

  • Socket 创建:我们使用 socket.socket() 创建一个 TCP socket,使用 AF_INET (IPv4) 和 SOCK_STREAM (TCP 协议)。

  • Socket 选项:我们设置一个选项来重用地址,这有助于防止在关闭服务器后快速重启服务器时出现“地址已在使用中”的错误。

  • 绑定:我们将 socket 绑定到地址 127.0.0.1 (localhost) 和端口 12345。这告诉操作系统我们希望在此特定的网络位置接收连接。

  • 监听listen(5) 调用告诉 socket 在拒绝新连接之前,最多排队 5 个连接请求。

  • 接受连接accept() 方法会阻塞(等待),直到客户端连接,然后返回一个新的 socket 对象,用于与该客户端通信,以及客户端的地址。

  • 接收数据:我们使用 recv(1024) 从客户端接收最多 1024 字节的数据。数据以字节形式出现,因此我们使用 decode() 将其转换为字符串。

  • 发送数据:我们使用 send() 方法将响应发送回客户端。我们 encode() 字符串将其转换为字节,然后再发送。

  • 关闭:最后,我们关闭客户端 socket 和服务器 socket 以释放资源。

测试服务器

让我们运行我们的服务器,看看它的实际效果。由于我们还没有创建客户端,所以它还不会做太多事情,但我们可以验证它是否正确启动:

python3 ~/project/socket_lab/server.py

你应该看到这样的输出:

Server started on 127.0.0.1:12345
Waiting for client connection...

服务器现在正在等待客户端连接。由于我们还没有客户端,请按 Ctrl+C 停止服务器:

^C
Server shutting down...
Server socket closed

在下一步中,我们将创建一个客户端来连接到我们的服务器。

构建一个客户端以连接到服务器

现在我们有了服务器,我们需要创建一个可以连接到它并发送消息的客户端。让我们构建一个简单的客户端应用程序。

Socket 客户端的工作原理

一个 socket 客户端遵循以下一般步骤:

  1. 创建一个 socket
  2. 连接到服务器的地址和端口
  3. 发送数据
  4. 接收响应
  5. 关闭连接

创建客户端

  1. socket_lab 目录中创建一个名为 client.py 的新 Python 文件:

在 WebIDE 中,单击“新建文件”按钮或使用“文件”菜单并选择“新建文件”,然后在 socket_lab 目录中将其命名为 client.py

  1. 添加以下代码以创建一个基本客户端:
import socket

def start_client():
    ## Server information to connect to
    host = '127.0.0.1'  ## localhost - same as server
    port = 12345        ## same port as server

    try:
        ## Create socket
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        ## Connect to server
        print(f"Connecting to server at {host}:{port}...")
        client_socket.connect((host, port))
        print("Connected to server")

        ## Send a message
        message = "Hello from the client!"
        print(f"Sending message: {message}")
        client_socket.send(message.encode())

        ## Receive response
        response = client_socket.recv(1024)
        print(f"Response from server: {response.decode()}")

    except ConnectionRefusedError:
        print("Connection failed. Make sure the server is running.")
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        ## Close socket
        client_socket.close()
        print("Connection closed")

if __name__ == "__main__":
    start_client()
  1. 保存文件。

理解客户端代码

让我们检查一下客户端代码的关键部分:

  • Socket 创建:与服务器类似,我们使用 socket.socket() 创建一个 TCP socket。

  • 连接:客户端使用 connect() 建立与服务器在指定主机和端口上的连接,而不是绑定和监听。

  • 发送数据:我们使用 send() 方法发送消息,确保将字符串编码为字节。

  • 接收数据:我们使用 recv(1024) 接收服务器的响应,并将其解码回字符串。

  • 错误处理:我们包括错误处理以捕获常见问题,例如服务器不可用 (ConnectionRefusedError)。

  • 关闭:完成后,我们关闭 socket 以释放资源。

一起测试客户端和服务器

现在让我们一起测试我们的客户端和服务器。我们需要在单独的终端窗口中运行它们。

  1. 首先,启动服务器:
python3 ~/project/socket_lab/server.py

你应该看到:

Server started on 127.0.0.1:12345
Waiting for client connection...
  1. 在 WebIDE 中打开一个新的终端(通过单击终端面板中的“+”按钮),然后运行客户端:
python3 ~/project/socket_lab/client.py

你应该在客户端终端中看到类似这样的输出:

Connecting to server at 127.0.0.1:12345...
Connected to server
Sending message: Hello from the client!
Response from server: Message received by server
Connection closed

而在服务器终端中,你应该看到:

Connection established with ('127.0.0.1', 55234)  ## The port number may differ
Message received: Hello from the client!

在客户端断开连接后,服务器将停止,因为我们当前的实现只处理单个连接。如果服务器仍在运行,请在服务器终端中按 Ctrl+C 将其关闭。

这演示了使用 Python sockets 在客户端和服务器之间成功通信。客户端能够向服务器发送消息,并且服务器可以接收它并将响应发送回去。

构建一个持续的服务器和交互式客户端

我们当前的服务器 - 客户端实现仅在关闭之前处理单个消息交换。大多数实际应用需要保持连接并处理多条消息。让我们增强我们的代码以创建更具交互性的体验。

增强服务器

首先,让我们修改服务器以持续接受连接并处理来自每个客户端的多条消息。

  1. 在 WebIDE 中打开 server.py 文件,并将代码替换为:
import socket

def start_server():
    ## Server configuration
    host = '127.0.0.1'
    port = 12345

    ## Create socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        ## Bind and listen
        server_socket.bind((host, port))
        server_socket.listen(5)

        print(f"Server running on {host}:{port}")
        print("Press Ctrl+C to stop the server")

        while True:  ## Continuous server loop
            print("\nWaiting for a connection...")
            client_socket, client_address = server_socket.accept()
            print(f"Connected to client: {client_address}")

            ## Handle client communication
            handle_client(client_socket)

    except KeyboardInterrupt:
        print("\nServer is shutting down...")
    finally:
        server_socket.close()
        print("Server closed")

def handle_client(client_socket):
    try:
        while True:  ## Keep receiving messages until client disconnects
            ## Receive data
            data = client_socket.recv(1024)

            ## If no data, client has disconnected
            if not data:
                break

            received_message = data.decode()
            print(f"Received: {received_message}")

            ## Process the message (in this case, just echo it back with a prefix)
            response = f"Server received: {received_message}"
            client_socket.send(response.encode())

    except Exception as e:
        print(f"Error handling client: {e}")
    finally:
        ## Close client socket
        client_socket.close()
        print("Client connection closed")

if __name__ == "__main__":
    start_server()
  1. 保存文件。

增强客户端

现在,让我们创建一个交互式客户端,允许用户发送多条消息。

  1. 在 WebIDE 中打开 client.py 文件,并将代码替换为:
import socket

def start_client():
    ## Server information
    host = '127.0.0.1'
    port = 12345

    try:
        ## Create socket and connect
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        print(f"Connecting to server at {host}:{port}...")
        client_socket.connect((host, port))
        print("Connected to server!")

        ## Interactive message sending
        while True:
            ## Get message from user
            message = input("\nEnter message to send (or 'quit' to exit): ")

            ## Check if user wants to quit
            if message.lower() == 'quit':
                print("Closing connection...")
                break

            ## Send message
            client_socket.send(message.encode())

            ## Receive response
            response = client_socket.recv(1024)
            print(f"Response from server: {response.decode()}")

    except ConnectionRefusedError:
        print("Connection failed. Make sure the server is running.")
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        ## Close connection
        try:
            client_socket.close()
        except:
            pass
        print("Disconnected from server")

if __name__ == "__main__":
    start_client()
  1. 保存文件。

理解增强后的代码

服务器增强:

  • 我们添加了一个外部 while True 循环以持续接受新的客户端连接
  • 我们创建了一个单独的 handle_client 函数来管理与每个客户端的通信
  • 客户端处理函数有自己的循环来接收来自同一客户端的多条消息
  • 我们检查空数据 (if not data:),这表示客户端已断开连接

客户端增强:

  • 我们添加了一个 while True 循环以允许发送多条消息
  • 我们提示用户输入并将其发送到服务器
  • 用户可以键入“quit”以退出循环并关闭连接
  • 在发送每条消息后,我们等待并显示服务器的响应

测试增强后的应用程序

让我们测试我们增强后的服务器和客户端:

  1. 在终端中启动增强后的服务器:
python3 ~/project/socket_lab/server.py

你应该看到:

Server running on 127.0.0.1:12345
Press Ctrl+C to stop the server

Waiting for a connection...
  1. 在一个新的终端中,运行增强后的客户端:
python3 ~/project/socket_lab/client.py

你应该看到:

Connecting to server at 127.0.0.1:12345...
Connected to server!

Enter message to send (or 'quit' to exit):
  1. 键入一条消息并按 Enter 键:
Enter message to send (or 'quit' to exit): Hello, server!
Response from server: Server received: Hello, server!

Enter message to send (or 'quit' to exit):
  1. 尝试发送更多消息。在服务器终端中,你应该看到每条消息都被接收到:
Connected to client: ('127.0.0.1', 59042)
Received: Hello, server!
Received: This is another message
  1. 完成后,在客户端中键入“quit”:
Enter message to send (or 'quit' to exit): quit
Closing connection...
Disconnected from server
  1. 在服务器终端中,你应该看到:
Client connection closed

Waiting for a connection...
  1. 服务器继续运行并准备好接受新的连接。你可以启动另一个客户端或按 Ctrl+C 停止服务器。

通过这些增强,我们使用 Python sockets 创建了一个更现实和交互式的客户端 - 服务器通信系统。

多个客户端和错误处理

在实际应用中,服务器通常需要同时处理多个客户端并妥善管理各种错误情况。让我们在考虑这些因素的情况下改进我们的实现。

理解并发客户端处理

有几种方法可以并发处理多个客户端:

  1. 线程(Threading):为每个客户端连接创建一个新线程
  2. 基于进程(Process-based):为每个客户端生成一个新进程
  3. 异步 I/O(Asynchronous I/O):将非阻塞 I/O 与事件循环一起使用

对于这个实验,我们将实现一个基于线程的方法,它相对容易理解和实现。

增强服务器以支持多个客户端

让我们修改服务器以使用线程处理多个客户端:

  1. 在 WebIDE 中打开 server.py 文件,并将代码替换为:
import socket
import threading

def handle_client(client_socket, client_address):
    """Handle communication with a single client"""
    try:
        print(f"[NEW CONNECTION] {client_address} connected.")

        while True:
            ## Receive client data
            try:
                data = client_socket.recv(1024)
                if not data:
                    break  ## Client disconnected

                message = data.decode()
                print(f"[{client_address}] {message}")

                ## Send response
                response = f"Message '{message}' received successfully"
                client_socket.send(response.encode())

            except ConnectionResetError:
                print(f"[{client_address}] Connection reset by client")
                break

    except Exception as e:
        print(f"[ERROR] {e}")

    finally:
        ## Clean up when client disconnects
        client_socket.close()
        print(f"[DISCONNECTED] {client_address} disconnected")

def start_server():
    """Start the server and listen for connections"""
    ## Server configuration
    host = '127.0.0.1'
    port = 12345

    ## Create socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    ## Set socket option to reuse address
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        ## Bind to host and port
        server_socket.bind((host, port))

        ## Listen for connections
        server_socket.listen(5)
        print(f"[STARTING] Server is listening on {host}:{port}")

        while True:
            ## Accept client connection
            client_socket, client_address = server_socket.accept()

            ## Create a new thread to handle the client
            client_thread = threading.Thread(
                target=handle_client,
                args=(client_socket, client_address)
            )
            client_thread.daemon = True  ## Thread will close when main program exits
            client_thread.start()

            ## Display active connections
            print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")

    except KeyboardInterrupt:
        print("\n[SHUTTING DOWN] Server is shutting down...")
    except Exception as e:
        print(f"[ERROR] {e}")
    finally:
        server_socket.close()
        print("[CLOSED] Server socket closed")

if __name__ == "__main__":
    start_server()
  1. 保存文件。

使用错误处理改进客户端

我们还应该使用更好的错误处理来增强我们的客户端:

  1. 在 WebIDE 中打开 client.py 文件,并将代码替换为:
import socket
import sys
import time

def start_client():
    """Start a client that connects to the server"""
    ## Server information
    host = '127.0.0.1'
    port = 12345

    ## Create socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    ## Set a timeout for connection attempts (5 seconds)
    client_socket.settimeout(5)

    try:
        ## Connect to server
        print(f"[CONNECTING] Connecting to server at {host}:{port}...")
        client_socket.connect((host, port))

        ## Reset timeout to none for regular communication
        client_socket.settimeout(None)

        print("[CONNECTED] Connected to server")

        ## Communication loop
        while True:
            ## Get user input
            message = input("\nEnter message (or 'quit' to exit): ")

            if message.lower() == 'quit':
                print("[CLOSING] Closing connection by request...")
                break

            try:
                ## Send message
                client_socket.send(message.encode())

                ## Wait for response
                response = client_socket.recv(1024)
                print(f"[RESPONSE] {response.decode()}")

            except ConnectionResetError:
                print("[ERROR] Connection was reset by the server")
                break
            except ConnectionAbortedError:
                print("[ERROR] Connection was aborted")
                break
            except Exception as e:
                print(f"[ERROR] {e}")
                break

    except socket.timeout:
        print("[TIMEOUT] Connection attempt timed out. Is the server running?")
    except ConnectionRefusedError:
        print("[REFUSED] Connection refused. Make sure the server is running.")
    except KeyboardInterrupt:
        print("\n[INTERRUPT] Client shutting down...")
    except Exception as e:
        print(f"[ERROR] {e}")

    finally:
        ## Close socket
        try:
            client_socket.close()
            print("[DISCONNECTED] Disconnected from server")
        except:
            pass

if __name__ == "__main__":
    start_client()
  1. 保存文件。

理解增强后的代码

服务器增强:

  • 我们添加了 threading 模块以并发处理多个客户端
  • 每个客户端连接现在都在一个单独的线程中处理
  • 我们通过更具体的异常捕获改进了错误处理
  • 我们显示了活动客户端连接的数量
  • 线程被设置为“守护进程(daemon)”,这意味着它们将在主程序退出时自动关闭

客户端增强:

  • 我们添加了一个连接超时,以防止服务器不可用时挂起
  • 我们通过针对不同网络错误的特定异常捕获改进了错误处理
  • 我们添加了更多描述性的状态消息,并具有清晰的格式

测试多客户端服务器

让我们测试我们改进后的应用程序:

  1. 启动服务器:
python3 ~/project/socket_lab/server.py

你应该看到:

[STARTING] Server is listening on 127.0.0.1:12345
  1. 在新终端中,启动一个客户端:
python3 ~/project/socket_lab/client.py
  1. 在第三个终端中启动另一个客户端:
python3 ~/project/socket_lab/client.py
  1. 在服务器终端中,你应该看到两个连接:
[NEW CONNECTION] ('127.0.0.1', 59124) connected.
[ACTIVE CONNECTIONS] 1
[NEW CONNECTION] ('127.0.0.1', 59126) connected.
[ACTIVE CONNECTIONS] 2
  1. 从两个客户端发送消息,并观察服务器接收它们:
[('127.0.0.1', 59124)] Hello from client 1
[('127.0.0.1', 59126)] Hello from client 2
  1. 完成后,在每个客户端中键入“quit”以断开连接,或在服务器终端中按 Ctrl+C 以关闭服务器。

处理服务器未运行

让我们也测试一下当服务器未运行时会发生什么:

  1. 确保服务器已停止(如果正在运行,请按 Ctrl+C)

  2. 尝试运行一个客户端:

python3 ~/project/socket_lab/client.py

你应该看到:

[CONNECTING] Connecting to server at 127.0.0.1:12345...
[REFUSED] Connection refused. Make sure the server is running.
[DISCONNECTED] Disconnected from server

客户端现在可以正常处理错误,通知用户服务器可能未运行。

通过这些增强,我们创建了一个健壮的客户端 - 服务器系统,该系统可以处理多个客户端和各种错误情况。这是开发更复杂的网络应用程序的坚实基础。

总结

祝贺你完成了这个关于 Python socket 编程的实验。你已经成功地学习了如何:

  1. 创建用于网络通信的 socket 对象
  2. 实现一个监听连接的 TCP 服务器
  3. 构建一个连接到服务器的客户端应用程序
  4. 在客户端和服务器之间发送和接收消息
  5. 增强你的服务器以并发处理多个客户端
  6. 在客户端和服务器中实现可靠的错误处理

这些技能构成了网络编程的基础,可以应用于构建各种网络应用程序,从简单的聊天程序到复杂的分布式系统。

为了继续你的学习之旅,请考虑探索:

  • 使用 SSL/TLS 实现安全的 socket 连接
  • 构建一个更复杂的协议来交换结构化数据
  • 为你的客户端应用程序创建一个 GUI
  • 使用异步 I/O 实现更高的性能,处理许多并发连接

Python 的 socket 编程功能使其成为网络应用程序开发的绝佳选择,它提供了其他少数语言可以比拟的简单性和强大功能的平衡。