Windows命名管道非阻塞读写:Python(ctypes)客户端遇权限错误
Ah, I’ve been down this exact road before—server-side non-blocking pipes work like a charm, but client-side is full of gotchas like permission errors and unexpected blocking reads. Let’s break this down and get your code working properly.
Why You’re Running Into This Mess
First, let’s clear up a common misconception: the PIPE_NOWAIT flag you set on the server only affects the server’s pipe handle behavior. The client’s handle defaults to blocking mode regardless of what the server sets.
As for the ERROR_ACCESS_DENIED when calling SetNamedPipeHandleState? That’s almost certainly because you didn’t request enough permissions when opening the pipe with CreateFileW. The default read/write permissions aren’t sufficient to modify the pipe’s state—you need an extra permission flag for that.
Fixing the Client-Side Non-Blocking Pipe
Here’s the step-by-step fix:
Step 1: Open the Pipe With the Right Permissions
When calling CreateFileW, you need to add FILE_WRITE_ATTRIBUTES to your requested access rights. This permission lets you modify the pipe’s state later. Also, make sure you’re using the correct share mode and open disposition:
- Access rights:
GENERIC_READ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES - Share mode:
FILE_SHARE_READ | FILE_SHARE_WRITE(so the server can still access the pipe) - Disposition:
OPEN_EXISTING(since the server already created the pipe)
Step 2: Set Non-Blocking Mode Correctly
Use SetNamedPipeHandleState to set the client’s pipe handle to PIPE_NOWAIT. If your server uses message-mode pipes, keep PIPE_READMODE_MESSAGE in the mode flag to maintain compatibility.
Step 3: Handle Non-Blocking Reads/Writes Properly
In non-blocking mode, ReadFile and WriteFile will return immediately. If there’s no data available to read, ReadFile will return False, and GetLastError() will return ERROR_NO_DATA (232)—this is expected behavior, not an actual error.
Full Working Code Examples
Server Code (Non-Blocking Message Pipe)
import ctypes from ctypes import wintypes # Load kernel32 with error tracking kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # Constants PIPE_ACCESS_DUPLEX = 0x00000003 PIPE_TYPE_MESSAGE = 0x00000004 PIPE_READMODE_MESSAGE = 0x00000002 PIPE_NOWAIT = 0x00000001 BUFSIZE = 4096 INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value # Type definitions for API calls kernel32.CreateNamedPipeW.argtypes = [ wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, wintypes.LPCVOID ] kernel32.CreateNamedPipeW.restype = wintypes.HANDLE kernel32.ConnectNamedPipe.argtypes = [wintypes.HANDLE, wintypes.LPOVERLAPPED] kernel32.ConnectNamedPipe.restype = wintypes.BOOL kernel32.ReadFile.argtypes = [ wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD, wintypes.LPOVERLAPPED ] kernel32.ReadFile.restype = wintypes.BOOL kernel32.WriteFile.argtypes = [ wintypes.HANDLE, wintypes.LPCVOID, wintypes.DWORD, wintypes.LPDWORD, wintypes.LPOVERLAPPED ] kernel32.WriteFile.restype = wintypes.BOOL kernel32.DisconnectNamedPipe.argtypes = [wintypes.HANDLE] kernel32.DisconnectNamedPipe.restype = wintypes.BOOL kernel32.CloseHandle.argtypes = [wintypes.HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL def run_server(): pipe_name = r'\\.\pipe\MyTestPipe' # Create non-blocking message-mode pipe pipe_handle = kernel32.CreateNamedPipeW( pipe_name, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT, 1, BUFSIZE, BUFSIZE, 0, None ) if pipe_handle == INVALID_HANDLE_VALUE: print(f"CreateNamedPipe failed: {ctypes.get_last_error()}") return print("Waiting for client connection...") # Non-blocking ConnectNamedPipe returns immediately; loop until connected while True: connected = kernel32.ConnectNamedPipe(pipe_handle, None) error = ctypes.get_last_error() if connected or error == 233: # ERROR_PIPE_CONNECTED print("Client connected!") break elif error != 234: # ERROR_NO_DATA (still waiting) print(f"Connection error: {error}") break # Send test message to client msg = b"Hello from server!" bytes_written = wintypes.DWORD(0) if not kernel32.WriteFile(pipe_handle, msg, len(msg), ctypes.byref(bytes_written), None): print(f"Write failed: {ctypes.get_last_error()}") else: print(f"Sent {bytes_written.value} bytes") # Read client data (non-blocking) buffer = ctypes.create_string_buffer(BUFSIZE) bytes_read = wintypes.DWORD(0) success = kernel32.ReadFile(pipe_handle, buffer, BUFSIZE, ctypes.byref(bytes_read), None) if success: print(f"Received: {buffer.value.decode('utf-8')}") else: error = ctypes.get_last_error() if error == 232: # ERROR_NO_DATA print("No client data available (non-blocking)") else: print(f"Read failed: {error}") # Cleanup kernel32.DisconnectNamedPipe(pipe_handle) kernel32.CloseHandle(pipe_handle) if __name__ == "__main__": run_server()
Client Code (Non-Blocking Mode)
import ctypes from ctypes import wintypes kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # Constants GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 FILE_WRITE_ATTRIBUTES = 0x00010000 FILE_SHARE_READ = 0x00000001 FILE_SHARE_WRITE = 0x00000002 OPEN_EXISTING = 3 PIPE_NOWAIT = 0x00000001 PIPE_READMODE_MESSAGE = 0x00000002 BUFSIZE = 4096 INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value # Type definitions kernel32.CreateFileW.argtypes = [ wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.LPCVOID, wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE ] kernel32.CreateFileW.restype = wintypes.HANDLE kernel32.SetNamedPipeHandleState.argtypes = [ wintypes.HANDLE, wintypes.LPDWORD, wintypes.LPDWORD, wintypes.LPDWORD ] kernel32.SetNamedPipeHandleState.restype = wintypes.BOOL kernel32.ReadFile.argtypes = [ wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD, wintypes.LPOVERLAPPED ] kernel32.ReadFile.restype = wintypes.BOOL kernel32.WriteFile.argtypes = [ wintypes.HANDLE, wintypes.LPCVOID, wintypes.DWORD, wintypes.LPDWORD, wintypes.LPOVERLAPPED ] kernel32.WriteFile.restype = wintypes.BOOL kernel32.CloseHandle.argtypes = [wintypes.HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL def run_client(): pipe_name = r'\\.\pipe\MyTestPipe' # Open pipe with sufficient permissions to modify state pipe_handle = kernel32.CreateFileW( pipe_name, GENERIC_READ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) if pipe_handle == INVALID_HANDLE_VALUE: print(f"CreateFile failed: {ctypes.get_last_error()}") return # Set pipe to non-blocking message mode pipe_mode = wintypes.DWORD(PIPE_READMODE_MESSAGE | PIPE_NOWAIT) if not kernel32.SetNamedPipeHandleState(pipe_handle, ctypes.byref(pipe_mode), None, None): print(f"Set pipe state failed: {ctypes.get_last_error()}") kernel32.CloseHandle(pipe_handle) return print("Successfully enabled non-blocking mode") # Read server message (non-blocking) buffer = ctypes.create_string_buffer(BUFSIZE) bytes_read = wintypes.DWORD(0) success = kernel32.ReadFile(pipe_handle, buffer, BUFSIZE, ctypes.byref(bytes_read), None) if success: print(f"Received: {buffer.value.decode('utf-8')}") else: error = ctypes.get_last_error() if error == 232: # ERROR_NO_DATA print("No server data available (non-blocking)") else: print(f"Read failed: {error}") # Send message to server msg = b"Hello from client!" bytes_written = wintypes.DWORD(0) if not kernel32.WriteFile(pipe_handle, msg, len(msg), ctypes.byref(bytes_written), None): print(f"Write failed: {ctypes.get_last_error()}") else: print(f"Sent {bytes_written.value} bytes") kernel32.CloseHandle(pipe_handle) if __name__ == "__main__": run_client()
Key Takeaways
- Always include
FILE_WRITE_ATTRIBUTESin your client’sCreateFileWaccess rights if you need to modify the pipe state. - Server-side
PIPE_NOWAITdoesn’t propagate to the client—you have to explicitly set it on the client handle. - Non-blocking
ReadFilereturningFalsewithERROR_NO_DATAis normal behavior, not an error.
内容的提问来源于stack exchange,提问作者Ray P.

