杰瑞科技汇

Python如何实现Unix socket通信?

Of course! Here's a comprehensive guide to using Unix domain sockets (UDS) in Python, covering both the server and client sides, with detailed explanations and examples.

Python如何实现Unix socket通信?-图1
(图片来源网络,侵删)

What are Unix Domain Sockets?

Unix Domain Sockets (UDS) are a method of inter-process communication (IPC) used by programs running on the same host machine.

Key Characteristics:

  • IPC Only: They cannot be used for network communication between different machines.
  • File-Based: Communication happens through a special file, called a "socket file," located in the filesystem (e.g., /tmp/my_socket.sock). This file acts as an endpoint for communication.
  • High Performance: UDS are generally faster than TCP/IP sockets for local communication because they bypass the network stack (no need for IP/TCp headers, routing, etc.).
  • Simple API: The Python API for UDS is very similar to the one for TCP/IP sockets (socket.socket), making it easy to learn if you already know network programming.

Core Concepts

  • Server: A program that listens for incoming connections on a specific socket file.
  • Client: A program that connects to the server's socket file to send and receive data.
  • Socket File: The "address" of the server. It's a regular file on disk, but the OS treats it specially. Important: If a server crashes or is killed, the socket file might not be deleted. You may need to manually remove it (rm /tmp/my_socket.sock) before the server can start again.
  • Connection-Oriented: Like TCP, UDS uses a connection-based model (socket.SOCK_STREAM). The client connects, and a stable, two-way communication channel is established.

Example 1: A Simple Echo Server and Client

This is the "Hello, World!" of socket programming. The server receives a message from the client and sends it back.

The Server (server.py)

The server's job is to:

Python如何实现Unix socket通信?-图2
(图片来源网络,侵删)
  1. Create a socket.
  2. Bind it to a specific file path.
  3. Listen for incoming connections.
  4. Accept a connection.
  5. Receive data, process it (echo it), and send it back.
  6. Clean up.
# server.py
import socket
import os
# Define the socket file path
SOCKET_FILE = "/tmp/my_socket.sock"
# Remove the socket file if it already exists
try:
    os.unlink(SOCKET_FILE)
except OSError as e:
    if os.path.exists(SOCKET_FILE):
        raise e
# Create a Unix domain socket
# AF_UNIX for Unix domain sockets
# SOCK_STREAM for a reliable, connection-oriented stream
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# Bind the socket to a file
server_socket.bind(SOCKET_FILE)
# Listen for incoming connections (backlog of 1)
server_socket.listen(1)
print(f"Server listening on {SOCKET_FILE}...")
# Accept a connection
connection, client_address = server_socket.accept()
print(f"Connection from {client_address}")
try:
    # Receive data from the client (up to 1024 bytes)
    data = connection.recv(1024)
    print(f"Received: {data.decode('utf-8')}")
    # Echo the data back to the client
    connection.sendall(data)
    print("Data echoed back to client.")
finally:
    # Clean up the connection
    connection.close()
    print("Connection closed.")
# Clean up the socket file
os.unlink(SOCKET_FILE)
print("Server shut down.")

The Client (client.py)

The client's job is to:

  1. Create a socket.
  2. Connect to the server's socket file.
  3. Send data.
  4. Receive the echoed data.
  5. Clean up.
# client.py
import socket
# Define the socket file path (must match the server)
SOCKET_FILE = "/tmp/my_socket.sock"
# Create a Unix domain socket
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# Connect to the server
print(f"Connecting to {SOCKET_FILE}...")
try:
    client_socket.connect(SOCKET_FILE)
except socket.error as e:
    print(f"Could not connect to the server: {e}")
    exit(1)
try:
    # Message to send
    message = b"Hello from client!"
    print(f"Sending: {message.decode('utf-8')}")
    # Send the message to the server
    client_socket.sendall(message)
    # Receive the echoed data from the server
    data = client_socket.recv(1024)
    print(f"Received from server: {data.decode('utf-8')}")
finally:
    # Clean up the connection
    client_socket.close()
    print("Connection closed.")

How to Run It

  1. Start the server:

    python3 server.py

    The server will print: Server listening on /tmp/my_socket.sock... and wait.

  2. In a new terminal, start the client:

    Python如何实现Unix socket通信?-图3
    (图片来源网络,侵删)
    python3 client.py

Expected Output:

Server Terminal:

Server listening on /tmp/my_socket.sock...
Connection from /tmp/my_socket.sock
Received: Hello from client!
Data echoed back to client.
Connection closed.
Server shut down.

Client Terminal:

Connecting to /tmp/my_socket.sock...
Sending: Hello from client!
Received from server: Hello from client!
Connection closed.

Example 2: Handling Multiple Clients (with Threads)

A real-world server needs to handle multiple clients concurrently. We can use Python's threading module for this.

The Multi-Threaded Server (server_threaded.py)

This server will handle each client connection in a separate thread.

# server_threaded.py
import socket
import os
import threading
SOCKET_FILE = "/tmp/threaded_socket.sock"
def handle_client(connection, client_address):
    """Function to handle a single client connection."""
    print(f"[NEW CONNECTION] {client_address} connected.")
    try:
        while True:
            # Receive data from the client
            data = connection.recv(1024)
            if not data:
                # If recv returns an empty object, the client has closed the connection
                break
            print(f"[{client_address}] Received: {data.decode('utf-8')}")
            # Echo the data back
            connection.sendall(data)
            print(f"[{client_address}] Data echoed back.")
    except ConnectionResetError:
        print(f"[{client_address}] Client forcibly closed the connection.")
    finally:
        connection.close()
        print(f"[{client_address}] Connection closed.")
def main():
    # Clean up old socket file
    try:
        os.unlink(SOCKET_FILE)
    except OSError:
        if os.path.exists(SOCKET_FILE):
            raise
    server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    server_socket.bind(SOCKET_FILE)
    server_socket.listen(5)  # Increased backlog
    print(f"Server listening on {SOCKET_FILE}...")
    try:
        while True:
            # Accept a new connection
            conn, addr = server_socket.accept()
            # Create a new thread to handle the client
            thread = threading.Thread(target=handle_client, args=(conn, addr))
            thread.start()
            print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")
    except KeyboardInterrupt:
        print("\nServer is shutting down...")
    finally:
        server_socket.close()
        os.unlink(SOCKET_FILE)
        print("Server shut down.")
if __name__ == "__main__":
    main()

The Client (can reuse the first client.py)

You can use the same client.py from the first example.

How to Run It

  1. Start the threaded server:

    python3 server_threaded.py
  2. Open two or more new terminals and run the client in each:

    # Terminal 1
    python3 client.py
    # Terminal 2
    python3 client.py
    # Terminal 3
    python3 client.py

Expected Output (Server Terminal):

Server listening on /tmp/threaded_socket.sock...
[NEW CONNECTION] /tmp/threaded_socket.sock connected.
[/tmp/threaded_socket.sock] Received: Hello from client!
[/tmp/threaded_socket.sock] Data echoed back.
[ACTIVE CONNECTIONS] 1
[NEW CONNECTION] /tmp/threaded_socket.sock connected.
[/tmp/threaded_socket.sock] Received: Hello from client!
[/tmp/threaded_socket.sock] Data echoed back.
[ACTIVE CONNECTIONS] 2
[NEW CONNECTION] /tmp/threaded_socket.sock connected.
[/tmp/threaded_socket.sock] Received: Hello from client!
[/tmp/threaded_socket.sock] Data echoed back.
[ACTIVE CONNECTIONS] 3

You'll see the server handling each client in its own thread without blocking the others.


Key Differences from TCP/IP Sockets

Feature Unix Domain Socket (UDS) TCP/IP Socket
Address Family socket.AF_UNIX socket.AF_INET (IPv4) or socket.AF_INET6 (IPv6)
Address Format A string representing a file path (e.g., "/tmp/my.sock") A tuple (e.g., ("127.0.0.1", 8080))
Communication Scope Same host machine only Any machine on the network
Performance Faster (no network stack overhead) Slower (due to network stack)
Security Filesystem permissions (read/write/execute) apply to the socket file. Network-level firewalls and port security.
Error Handling FileNotFoundError if the socket file doesn't exist. ConnectionRefusedError if no one is listening on the port.

When to use UDS:

  • Communication between services on the same machine (e.g., a web server and a cache service).
  • When performance is critical and the communication is local.
  • When you want to leverage filesystem permissions for access control.

When to use TCP/IP:

  • Communication between different machines.
  • When you need to communicate over the internet.
  • When you are building a general-purpose network service.
分享:
扫描分享到社交APP
上一篇
下一篇