杰瑞科技汇

Python socket stream 如何实现高效数据传输?

Of course! Let's dive deep into Python's socket streams. This is a fundamental concept for building network applications.

Python socket stream 如何实现高效数据传输?-图1
(图片来源网络,侵删)

What is a Socket Stream?

A socket is an endpoint for sending or receiving data across a computer network. When we talk about a stream socket, we're referring to a specific type of socket that provides:

  • Connection-Oriented: A reliable connection must be established between two endpoints (client and server) before data can be sent. Think of it like making a phone call—you must establish a connection first.
  • Reliable Delivery: The underlying protocol (almost always TCP) ensures that all data sent arrives at the destination in the correct order, without errors or duplication.
  • Byte Stream: It treats data as a continuous stream of bytes. There are no inherent boundaries or messages. If you send 10 bytes, then 20 bytes, the other side will read 10 bytes, then 20 bytes (or some other combination, depending on how it reads). It's up to your application to define where one message ends and another begins.

The primary alternative is a datagram socket (UDP), which is connectionless, unreliable, and sends discrete packets. For this guide, we'll focus exclusively on stream sockets (TCP).


Key Concepts for Stream Sockets

  1. socket.socket(family, type, proto): The constructor for a socket object.

    • family: Usually socket.AF_INET for IPv4 (e.g., 168.1.1) or socket.AF_INET6 for IPv6.
    • type: For streams, this is socket.SOCK_STREAM.
    • proto: Usually left as 0, as the OS will choose the correct protocol (TCP for SOCK_STREAM).
  2. Server-Side Methods:

    Python socket stream 如何实现高效数据传输?-图2
    (图片来源网络,侵删)
    • s.bind(address): Binds the socket to a specific address and port. The address is a tuple (hostname, port). For a server listening on all available interfaces, use or '0.0.0.0' for the hostname.
    • s.listen(backlog): Enables the server to accept connections. backlog is the number of unaccepted connections that the system will allow before refusing new ones.
    • s.accept(): Blocks execution and waits for an incoming connection. When a client connects, it returns a new socket object representing the connection, and the client's address.
      • Crucial Point: The original listening socket (s) is used to accept new connections. The new socket object returned by accept() is used to communicate with that specific client.
  3. Client-Side Methods:

    • s.connect(address): Actively initiates a connection to the server at the given address. This is a blocking call; it will wait until the connection is established or an error occurs.
  4. Communication Methods (for both client and server connection sockets):

    • s.send(bytes): Sends data over the socket. bytes must be a bytes-like object. It returns the number of bytes sent. It's possible that send() sends fewer bytes than you asked for! You usually need to handle this in a loop.
    • s.recv(bufsize): Receives data from the socket, up to bufsize bytes. This is a blocking call; it waits until data is available. It returns a bytes object. If the connection is closed by the other side, recv() will return an empty bytes object (b'').
    • s.sendall(bytes): A convenient helper that sends all the data in bytes. It continues to send from the buffer until all data has been sent or an error occurs. This is generally what you want for sending complete messages.
    • s.close(): Closes the socket, releasing the resources. It's good practice to use this in a finally block.

Simple Echo Server and Client Example

This is the "Hello, World!" of socket programming. The server listens for a connection, receives a message from the client, and sends the exact same message back.

The Server (server.py)

# server.py
import socket
# Use 'with' statement to ensure the socket is closed automatically
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # Bind the socket to a specific address and port
    # '' means listen on all available network interfaces
    # 65432 is an arbitrary port number (must be > 1024)
    HOST = ''  # Symbolic name meaning all available interfaces
    PORT = 65432
    s.bind((HOST, PORT))
    # Enable the server to accept connections
    # backlog of 1 means one connection will be queued before new ones are refused
    s.listen()
    print(f"Server listening on {HOST}:{PORT}")
    # accept() blocks execution and waits for an incoming connection.
    # It returns a new socket object 'conn' to use for communication,
    # and the 'addr' of the client that connected.
    conn, addr = s.accept()
    # Use a 'with' statement for the connection socket as well
    with conn:
        print(f"Connected by {addr}")
        while True:
            # recv() blocks until data is received from the client
            # 1024 is the buffer size in bytes
            data = conn.recv(1024)
            if not data:
                # If recv() returns an empty object, the client has closed the connection
                break
            print(f"Received from {addr}: {data.decode('utf-8')}")
            # Send the received data back to the client (echo)
            conn.sendall(data)
            print(f"Echoed back to {addr}")

The Client (client.py)

# client.py
import socket
HOST = '127.0.0.1'  # The server's hostname or IP address
PORT = 65432        # The port used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # connect() is a blocking call that connects the client to the server
    s.connect((HOST, PORT))
    print(f"Connected to server at {HOST}:{PORT}")
    # Message to send to the server
    message = b"Hello, server! This is a test message."
    print(f"Sending: {message.decode('utf-8')}")
    # Send the message to the server
    s.sendall(message)
    # Look for the response
    data = s.recv(1024)
print(f"Received from server: {data.decode('utf-8')}")

How to Run It

  1. Save the code as server.py and client.py.

    Python socket stream 如何实现高效数据传输?-图3
    (图片来源网络,侵删)
  2. Open two terminal windows.

  3. In the first terminal, run the server:

    python server.py

    You will see: Server listening on 0.0.0.0:65432. The program will now be stuck on s.accept(), waiting for a client.

  4. In the second terminal, run the client:

    python client.py

    You will see the client's output:

    Connected to server at 127.0.0.1:65432
    Sending: Hello, server! This is a test message.
    Received from server: Hello, server! This is a test message.
  5. Look back at the server terminal. You will see:

    Server listening on 0.0.0.0:65432
    Connected by ('127.0.0.1', 54321)  # Port number will vary
    Received from ('127.0.0.1', 54321): Hello, server! This is a test message.
    Echoed back to ('127.0.0.1', 54321)

    The server will then go back to waiting for another connection (while True loop).


Important Considerations and Best Practices

Blocking vs. Non-Blocking Sockets

By default, sockets are blocking. This means that accept(), connect(), and recv() will pause the program's execution until they complete. This is simple for beginners but can be inefficient for handling many connections.

For more advanced applications, you can make sockets non-blocking using s.setblocking(False). In this mode, these methods will raise an BlockingIOError if they can't complete immediately. Your application then needs to use a loop (often with time.sleep()) to try again later. This is the basis for more scalable architectures.

Threading for Handling Multiple Clients

A single-threaded server can only handle one client at a time because accept() and recv() are blocking. If you want your server to handle multiple clients simultaneously, you need to use threads.

When a new client connects in s.accept(), you can spawn a new thread to handle that client's communication, while the main thread goes back to listening for more connections.

# Conceptual
分享:
扫描分享到社交APP
上一篇
下一篇