杰瑞科技汇

Python socket fileno是什么?如何使用?

Of course! Let's dive deep into the fileno() method of Python's socket object.

Python socket fileno是什么?如何使用?-图1
(图片来源网络,侵删)

What is socket.fileno()?

In short, fileno() is a method that returns the underlying file descriptor (an integer) associated with the socket.

But what does that mean? Let's break it down.

The Abstraction: The socket Object

When you work with sockets in Python using the socket module, you're using a high-level, object-oriented interface. This is fantastic because it abstracts away the complexities of the underlying operating system. You call methods like socket(), bind(), listen(), accept(), send(), and recv(), and the Python library handles the system calls for you.

The Reality: The File Descriptor

Under the hood, the operating system doesn't know about Python objects. It manages network connections and files using a low-level concept called a file descriptor.

Python socket fileno是什么?如何使用?-图2
(图片来源网络,侵删)
  • A file descriptor is simply a non-negative integer (e.g., 3, 10, 42).
  • The operating system uses this integer as an index into an internal table (the "file descriptor table") to keep track of open files, network connections, pipes, etc.
  • When you open a file or create a socket, the OS gives you back one of these integers.

The Bridge: fileno()

The fileno() method is the bridge between the high-level Python world and the low-level OS world. It peels back the abstraction and says, "Hey, what is the actual integer that the OS is using for this socket?"

Simple Example:

import socket
# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Get the file descriptor
fd = s.fileno()
print(f"The socket object is: {s}")
print(f"The file descriptor (an integer) is: {fd}")
# You can see the type of the file descriptor
print(f"The type of the file descriptor is: {type(fd)}")
# Close the socket
s.close()
# After closing, the file descriptor is no longer valid for the OS
# to use for this socket, but the 'fd' variable still holds the old integer.
print(f"After closing, the fd variable is still: {fd}")

Output (the integer will vary):

The socket object is: <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6>
The file descriptor (an integer) is: 3
The type of the file descriptor is: <class 'int'>
After closing, the fd variable is still: 3

Why Would You Use fileno()?

Using fileno() means you are intentionally leaving the safe, high-level world of Python and entering the more powerful but dangerous world of system-level programming. You should only do this when the high-level Python API doesn't provide a specific feature you need.

Python socket fileno是什么?如何使用?-图3
(图片来源网络,侵删)

Here are the most common use cases:

Using select() for Multiplexing

This is the classic and most important use case. The select module allows you to monitor multiple file descriptors (sockets, files, pipes) to see if any of them are "ready" for reading, writing, or have an exceptional condition.

The select.select() function doesn't take Python socket objects; it takes a list of file descriptors.

Scenario: You want to run a simple server that can handle multiple clients without creating a thread for each one.

import socket
import select
# Create a listening socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('0.0.0.0', 12345))
server_socket.listen(5)
print("Server is listening on port 12345")
# A list to keep track of all connected sockets and their file descriptors
# We start with the server socket itself
inputs = [server_socket]
outputs = [] # We won't use 'outputs' in this simple example
while inputs:
    # Use select to wait for any socket in 'inputs' to be readable
    # The last parameter is a timeout in seconds. None means block indefinitely.
    readable, writable, exceptional = select.select(inputs, [], inputs)
    for s in readable:
        if s is server_socket:
            # This is the server socket, meaning a new connection is incoming
            client_socket, client_address = s.accept()
            print(f"Accepted connection from {client_address}")
            client_socket.setblocking(0) # Set to non-blocking
            inputs.append(client_socket) # Add the new client to our list
        else:
            # This is an existing client socket, meaning data is available to read
            data = s.recv(1024)
            if data:
                print(f"Received from {s.getpeername()}: {data.decode('utf-8')}")
                # Echo the data back to the client (this would go into the 'outputs' list in a more complex scenario)
                # s.send(data)
            else:
                # If recv() returns empty data, the client has closed the connection
                print(f"Connection closed by {s.getpeername()}")
                inputs.remove(s) # Stop monitoring this socket
                s.close() # Close the socket
    for s in exceptional:
        # Handle a "broken" socket
        print(f"Handling exceptional condition for {s.getpeername()}")
        inputs.remove(s)
        s.close()

Integrating with Low-Level C Libraries

If you are using a Python wrapper (like ctypes or CFFI) for a C library that needs to operate on a file descriptor, fileno() is essential. You pass the integer to the C function, which then uses it directly with the OS.

Advanced I/O Multiplexing with poll() or epoll()

On Linux systems, select() is considered inefficient for a large number of file descriptors. More advanced mechanisms like poll() and epoll() are used. These APIs also operate directly on file descriptors, not Python objects. The selectors module in Python's standard library provides a higher-level interface to these, but under the hood, it uses fileno().


Important Considerations and Cautions

  1. It's an Integer, Not a Magic Handle: The integer returned by fileno() is a direct link to the OS resource. If you close the Python socket object (s.close()), the file descriptor becomes invalid. Trying to use that integer with a low-level OS function will lead to errors (like ValueError: I/O operation on closed file or Bad file descriptor).

  2. Platform Dependency: While file descriptors are a concept in Unix-like systems (Linux, macOS), Windows handles them differently (it uses SOCKET handles, which are also integers, but the underlying system calls are not POSIX). Code that relies heavily on fileno() for non-socket operations might not be portable. However, for sockets themselves, using fileno() with select is a common cross-platform pattern.

  3. "Leaky Abstraction": Using fileno() breaks the encapsulation of the socket object. You are now responsible for managing the state of the file descriptor alongside the Python object. This can lead to bugs if you're not careful. For most applications, sticking to the high-level socket methods is safer and easier.

Summary

Feature Description
What it is A method that returns the underlying OS file descriptor (an integer) for a socket.
Purpose To bridge the gap between Python's high-level socket API and the low-level OS interface.
Primary Use Case Passing the integer to system-level functions like select.select(), poll(), or epoll() for I/O multiplexing.
Return Value An integer representing the file descriptor.
Caveats The integer becomes invalid if the socket is closed. It's a "leaky abstraction" and should be used with care.
分享:
扫描分享到社交APP
上一篇
下一篇