杰瑞科技汇

Python setblocking如何设置非阻塞模式?

Of course! Let's break down setblocking in Python. This is a fundamental concept for writing efficient network programs.

Python setblocking如何设置非阻塞模式?-图1
(图片来源网络,侵删)

The Core Idea: Blocking vs. Non-Blocking I/O

In simple terms, blocking is like waiting in a long line at a grocery store.

  • Blocking I/O: When your program performs an I/O operation (like reading from a socket or writing to a file), it stops and waits until the operation is complete. If the data isn't ready yet, the program just freezes and does nothing else. This is the default behavior for most I/O operations in Python.
  • Non-Blocking I/O: When you set a socket to non-blocking mode and perform an I/O operation, the operation doesn't wait. It tries to do its job and returns immediately with whatever result it can get immediately.

If the operation can't be completed right away (e.g., no data is available to be read), it raises a special exception, BlockingIOError. This tells your program, "I couldn't do it right now, try again later."

This "try again later" model is the key to building high-performance, concurrent applications.


The socket.setblocking() Method

The socket object in Python has a method called setblocking() to control this behavior.

Python setblocking如何设置非阻塞模式?-图2
(图片来源网络,侵删)

Method Signature

socket.setblocking(flag)
  • flag: A boolean value.
    • If flag is True (or any non-zero value), the socket is set to blocking mode. This is the default.
    • If flag is False (or zero), the socket is set to non-blocking mode.

How to Use It

Let's look at a simple example. We'll create a basic TCP server and a client to demonstrate the difference.

Scenario:

  1. A server starts and listens on a port.
  2. A client connects to the server.
  3. The server tries to read data from the client.

Example 1: Blocking Mode (The Default)

In this case, server_socket.setblocking(True) is implicit.

blocking_server.py

Python setblocking如何设置非阻塞模式?-图3
(图片来源网络,侵删)
import socket
HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
PORT = 65432        # Port to listen on (non-privileged ports are > 1023)
print("--- Blocking Server Starting ---")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
    server_socket.bind((HOST, PORT))
    server_socket.listen()
    print(f"Server listening on {HOST}:{PORT}")
    # This is a BLOCKING call. The program will freeze here until a client connects.
    print("Server is BLOCKED, waiting for a connection...")
    conn, addr = server_socket.accept()
    with conn:
        print(f"Connected by {addr}")
        # This is also a BLOCKING call. The program will freeze here
        # until data is received from the client.
        print("Server is BLOCKED, waiting for data...")
        data = conn.recv(1024)
        if not data:
            print("Client disconnected.")
        else:
            print(f"Received from client: {data.decode('utf-8')}")
print("--- Blocking Server Finished ---")

blocking_client.py

import socket
import time
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:
    s.connect((HOST, PORT))
    print("Client connected to server.")
    time.sleep(5) # Simulate some work
    s.sendall(b'Hello, blocking world!')
    print("Client sent message.")
print("--- Client Finished ---")

To Run:

  1. Open two terminal windows.
  2. In the first, run python blocking_server.py. The server will print "Server is BLOCKED, waiting for a connection..." and will hang there.
  3. In the second, run python blocking_client.py.
  4. You will see the server unblock, accept the connection, print "Server is BLOCKED, waiting for data...", and then hang again until the client's time.sleep(5) is over and the data is sent.

The server is completely idle during those 5 seconds.


Example 2: Non-Blocking Mode

Now, let's modify the server to use setblocking(False).

non_blocking_server.py

import socket
HOST = '127.0.0.1'
PORT = 65433
print("--- Non-Blocking Server Starting ---")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
    server_socket.bind((HOST, PORT))
    server_socket.listen()
    server_socket.setblocking(False) # THE KEY CHANGE: Set to non-blocking
    print(f"Server listening on {HOST}:{PORT} (non-blocking mode)")
    connections = []
    while True:
        try:
            # This call will NOT wait. It will raise BlockingIOError if no
            # connection is immediately available.
            print("Attempting to accept a connection...")
            conn, addr = server_socket.accept()
            connections.append(conn)
            print(f"Connected by {addr}")
        except BlockingIOError:
            # This is expected! It just means "no connection ready yet".
            print("No new connections. Doing other work...")
            # In a real app, you would do other tasks here.
            # For this demo, we'll just sleep to avoid a tight loop.
            # A better approach uses select(), poll(), or asyncio.
            import time
            time.sleep(1)
            continue
        # Now, let's handle the connected client
        try:
            # This call will also NOT wait. It will raise BlockingIOError
            # if no data is available to be read.
            print(f"Attempting to read from {addr}...")
            data = conn.recv(1024)
            if not data:
                print(f"Client {addr} disconnected.")
                conn.close()
                connections.remove(conn)
            else:
                print(f"Received from {addr}: {data.decode('utf-8')}")
                conn.sendall(b'Hello, non-blocking world!')
        except BlockingIOError:
            print(f"No data from {addr} yet. Will try again.")
            continue
print("--- Non-Blocking Server Finished ---")

non_blocking_client.py

import socket
import time
HOST = '127.0.0.1'
PORT = 65433
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    print("Client connected to server.")
    time.sleep(3) # Simulate some work
    s.sendall(b'Hello, non-blocking world!')
    print("Client sent message.")
print("--- Client Finished ---")

To Run:

  1. Open two terminal windows.
  2. In the first, run python non_blocking_server.py. You will see it repeatedly print "Attempting to accept a connection..." and "No new connections. Doing other work...". It never freezes!
  3. In the second, run python non_blocking_client.py.
  4. The server will detect the new connection, accept it, and then start trying to read. It will print "No data from ... yet. Will try again." until the client's time.sleep(3) is over.
  5. Once the data arrives, the server receives it, sends a reply, and continues its loop.

The server is now free to do other work (even if it's just a sleep in our simple example) instead of being blocked.


The Modern Alternative: select, poll, and asyncio

Manually writing a loop with try...except BlockingIOError works, but it's cumbersome and inefficient for many connections. It's called the "busy-waiting" problem, where the CPU is constantly checking "is it ready yet? is it ready yet?".

Modern Python provides better tools to manage this:

  1. select Module: A cross-platform way to monitor multiple sockets. You tell select which sockets you're interested in (for reading, writing, or errors), and it will block until at least one of them is ready. This is much more efficient than a manual loop.
  2. asyncio Module: The modern, high-level solution for concurrent I/O in Python. It uses an event loop and coroutines (async/await syntax) to handle thousands of connections efficiently on a single thread without the complexity of manual select loops. For new network programming, asyncio is highly recommended.

Summary

Feature socket.setblocking(True) (Default) socket.setblocking(False)
Behavior I/O calls (accept, recv, send) pause execution until the operation can be completed. I/O calls return immediately. If the operation can't be done right away, a BlockingIOError is raised.
Use Case Simple scripts, sequential programs, or when you expect I/O to be fast. Building high-performance servers that need to handle many connections simultaneously.
Pros Simple, easy to understand for basic cases. Allows a single thread to manage multiple I/O operations, preventing the program from freezing.
Cons Inefficient for concurrent tasks. The program can be idle for long periods. Requires a more complex loop structure (try...except) or the use of higher-level tools like select or asyncio to be effective.
分享:
扫描分享到社交APP
上一篇
下一篇