我用Python做了一个服务器。
import logging
from Server import Server
logging.basicConfig(
filename='app.log', # Log file name
filemode='w', # Append mode ('w' for overwrite)
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # Log format
datefmt='%Y-%m-%d %H:%M:%S', # Date format
level=logging.INFO # Logging level
)
logger = logging.getLogger(__name__)
def main():
logger.info('starting server...')
server = Server()
server.run()
if __name__ == '__main__':
main()
from threading import Thread, Condition, Timer, Lock, Semaphore, Event
import sys
import logging
logger = logging.getLogger(__name__)
class Server:
def __init__(self):
self.istream = sys.stdin.buffer
self.ostream = sys.stdout.buffer
self.requests = []
self.requestsLock = Lock()
self.mainSem = Semaphore(0)
self.writeSem = Semaphore(0)
self.blockEvent = Event()
self.createThreads()
self.w = None
self.r = None
logger.info('Server initialized')
def readThread(self):
while True:
buffer = self.istream.read()
logger.info(f'readThread: {buffer}')
# next: do something with the buffer like extract the request and add it to the requests list
def writeThread(self):
while True:
self.writeSem.acquire()
logger.info(f'writeThread: {self.requests}')
def createThreads(self):
self.r = Thread(target=self.readThread)
self.w = Thread(target=self.writeThread)
self.r.start()
self.w.start()
def blockCallback(self):
self.blockEvent.set()
def run(self):
while True:
self.mainSem.acquire()
print(f'run: {self.requests}')
if __name__ == '__main__':
server = Server()
运行
python.exe main.py
后,当我在windows中按Ctrl + C时,日志是:
2024-07-22 08:35:14 - __main__ - INFO - starting server...
2024-07-22 08:35:14 - Server - INFO - Server initialized
2024-07-22 08:35:22 - Server - INFO - readThread: b''
2024-07-22 08:35:22 - Server - INFO - readThread: b''
2024-07-22 08:35:22 - Server - INFO - readThread: b''
2024-07-22 08:35:22 - Server - INFO - readThread: b''
2024-07-22 08:35:22 - Server - INFO - readThread: b''
2024-07-22 08:35:23 - Server - INFO - readThread: b''
是stdin(buffer)劫持信号,但我不确定。对此不太熟悉。 那么,发生了什么以及如何终止这个进程?
======================================== =============================================== 更多:
当使用
pyinstaller
制作.exe应用程序时,也不能杀死内部的python进程。
读取线程陷入死循环。
pyinstaller --onefile --name simplified-server --paths ./ .\main.py
使用nodejs生成运行:
按Ctrl + C: 在此处输入图像描述
生成代码:
const { spawn } = require("child_process");
const { resolve } = require("path");
// const child = spawn("python", [resolve(__dirname, "programs/main.py")], {
// argv0: "stubborn-child-process",
// });
child = spawn(resolve(__dirname, "dist/simplified-server.exe"));
process.on("exit", () => {
console.log("Process exited");
});
process.on("SIGINT", () => {
console.log(arguments);
console.log("SIGINT received");
console.log(child.killed);
child.kill("SIGINT");
console.log(child.killed);
process.exit();
});
我不是熟悉带有 stdin 缓冲区块的 .exe 进程和 python 进程。
你说的对,问题出在
sys.stdin.buffer.read()
阻塞了你的读取线程,导致它无法响应
SIGINT
信号(也就是 Ctrl+C)。 当程序收到
SIGINT
信号时,它只会尝试退出主线程,但读取线程仍然卡在
read()
调用中。
为了解决这个问题,你可以尝试以下几种方法:
1. 使用非阻塞读取
不要使用阻塞的
read()
方法,而是使用非阻塞的读取方式,例如:
-
使用
os.read()
并设置非阻塞标志:
import os
# ...
def readThread(self):
# 将标准输入设置为非阻塞模式
os.set_blocking(sys.stdin.fileno(), False)
while True:
try:
# 尝试读取数据,如果没有数据可用,则抛出 BlockingIOError 异常
buffer = os.read(sys.stdin.fileno(), 1024) # 读取最多 1024 字节
if buffer:
logger.info(f'readThread: {buffer}')
# 处理读取到的数据...
except BlockingIOError:
# 没有数据可读,可以进行其他操作,例如检查事件或休眠一段时间
time.sleep(0.1)
-
使用
select
模块:
import select
# ...
def readThread(self):
while True:
# 检查 stdin 是否可读
readable, _, _ = select.select([sys.stdin], [], [], 0.1)
if sys.stdin in readable:
buffer = self.istream.read()
logger.info(f'readThread: {buffer}')
# 处理读取到的数据...
2. 使用信号处理
你可以在读取线程中捕获
SIGINT
信号,并在收到信号时退出循环。
import signal
# ...
def readThread(self):
# 定义信号处理函数
def signal_handler(sig, frame):
logger.info('Received SIGINT, exiting readThread...')
exit(0)
# 设置信号处理函数
signal.signal(signal.SIGINT, signal_handler)
while True:
# ... 你的读取逻辑 ...
3. 在主线程中关闭 stdin
你可以在主线程中捕获
KeyboardInterrupt
异常(由 Ctrl+C 触发),并在退出程序之前关闭
sys.stdin
,这将导致
read()
方法抛出异常并退出。
def main():
logger.info('starting server...')
server = Server()
try:
server.run()
except KeyboardInterrupt:
logger.info('KeyboardInterrupt received, exiting...')
sys.stdin.close()
# ...
关于 PyInstaller
当使用 PyInstaller 打包成 .exe 后,信号处理可能会更加复杂。 你可以尝试以下方法:
-
使用
--console
选项: 将 PyInstaller 的--console
选项添加到你的命令中,这将创建一个控制台窗口,可以更可靠地接收信号。 -
使用
pyinstaller-hooks-contrib
: 这个第三方库提供了一些钩子,可以帮助 PyInstaller 正确处理信号。
希望这些方法可以帮助你解决问题!
标签:python,windows,process,buffer,stdin From: 78776655