为什么 httpserver 不会与 tkinter 在单独的线程上

Why httpserver won't on separate thread with tkinter

首先,我是 python 的新手,所以如果我做了一些非常愚蠢的事情,请告诉我。

我有这个桌面应用程序 运行 在 raspberyy pi 上使用 python 和 tkinter 开发用于 GUI 并且由于项目中的新要求,它需要能够接收远程执行某些操作的命令。

为此,我想向项目添加一个 http 服务器

在我的主脚本中我有这个:

from tkinter import *  
from http.server import BaseHTTPRequestHandler, HTTPServer
import threading

# Create GUI app and define general properties
window = Tk()
window.attributes("-fullscreen", True)
window.config(cursor="none")

winWidth = int(window.winfo_screenwidth() * 1)
winHeight = int(window.winfo_screenheight() * 1)

window.geometry(f"{winWidth}x{winHeight}")

class HttpHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
        self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))

 
#Start HTTP Server
webServer = HTTPServer(('localhost', 9080), HttpHandler)
print('running http server on port: ', 9080)
threading.Thread(target=webServer.serve_forever, daemon=True) 
    
window.mainloop()

现在代码非常基础,只是为了得到它 运行ning 但我的想法是稍后对 http 请求执行 IO 操作。例如 /api/lights/on 会触发 GPIO。

我不得不使用 threading.Thread 因为否则脚本会阻塞 webServer.serve_forever()

通过使用线程,它不再阻塞并正确显示 GUI。还有 'netstat -lnt' 我可以看出 http 服务器正在监听指定的端口。

当我在 http://127.0.0.1:9080/ 打开浏览器时,浏览器从未得到响应。

我是不是做错了什么?

我发现了两个错误

  1. 你忘记导入 threading 所以它给出了错误信息

    import threading
    
  2. 您创建了线程但忘记启动它

    t = threading.Thread(target=webServer.serve_forever, daemon=True)
    t.start()
    

顺便说一句:

你可以更好地组织代码。
参见:PEP 8 --Style Guide for Python Code

import threading
import tkinter as tk  # PEP8: `import *` is not preferred
from http.server import BaseHTTPRequestHandler, HTTPServer

# --- classes ---

class HttpHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
        self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))

# --- functions ---

# empty

# --- main ---

webServer = HTTPServer(('localhost', 9080), HttpHandler)
print('running http server: http://localhost:9080')  # some consoles will display URL as clickable so it is easier to run browser
t = threading.Thread(target=webServer.serve_forever, daemon=True)
t.start()

window = tk.Tk()

#window.attributes("-fullscreen", True)
window.config(cursor="none")

#winWidth = int(window.winfo_screenwidth() * 1)
#winHeight = int(window.winfo_screenheight() * 1)
#window.geometry(f"{winWidth}x{winHeight}")
    
window.mainloop()

我只是想知道使用 http.server 是否是个好主意。如果你想通过网页访问,那么用 Flask 创建页面会更简单。如果您想发送小命令,那么使用服务器 MQTT 可能会更简单 而不是 HTTP。我已经使用了一些 IoT 台设备 MQTT


其他问题可能导致线程之间的通信。 Tkinter 不喜欢在子线程中 运行 所以你不能直接在服务器线程中访问小部件,它需要队列将值发送到主线程并且 tkinter 需要 after(millisecond, function) 定期检查队列以获取命令。


编辑:

使用 queue 将信息从 http server 发送到 tkinter 并将其显示在小部件 Text

中的版本
import threading
import tkinter as tk  # PEP8: `import *` is not preferred
from http.server import BaseHTTPRequestHandler, HTTPServer
import queue

# --- classes ---

class HttpHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
        self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))

        q.put("Request: %s" % self.path)  # put in `queue`
        
# --- functions ---

def check_queue():
    if not q.empty():
        text.insert('end', q.get()+'\n') # get from `queue` and put in `Text`
    window.after(100, check_queue)  # check again after 100ms
    
# --- main ---

q = queue.Queue()

webServer = HTTPServer(('localhost', 9080), HttpHandler)
print('running http server: http://localhost:9080')  # some consoles will display URL as clickable so it is easier to run browser
t = threading.Thread(target=webServer.serve_forever, daemon=True)
t.start()

window = tk.Tk()

text = tk.Text(window)
text.pack()

check_queue()

window.mainloop()


编辑:

Flask。它可以获取数据:args、json、表单、文件等

import queue
import threading
import tkinter as tk  # PEP8: `import *` is not preferred
from flask import Flask, request, render_template_string

# --- classes ---

    
# --- functions ---

app = Flask(__name__)

#@app.route('/')
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):

    print('path:', path)
    print(f'Request: {request.method} {request.url}')
    print('args:', request.args)
    print('form:', request.form)
    print('data:', request.data)
    print('json:', request.json)
    print('files:', request.files)
    
    q.put(f'Request: {request.method} {request.url}')  # put in `queue`

    return render_template_string('''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>https://pythonbasics.org</title>
</head>
<body>
<p>Request: {{ request.method }} {{ request.url }}</p>
<p>This is an example web server.</p>
</body>
</html>''', request=request)

def check_queue():
    if not q.empty():
        text.insert('end', q.get()+'\n') # get from `queue` and put in `Text`
    window.after(100, check_queue)  # check again after 100ms
    
# --- main ---

q = queue.Queue()

print('running http server: http://localhost:9080')  # some consoles will display URL as clickable so it is easier to run browser
t = threading.Thread(target=app.run, args=('localhost', 9080), daemon=True)
t.start()

window = tk.Tk()

text = tk.Text(window)
text.pack()

check_queue()

window.mainloop()

但现在的问题是:如果可以在 flask

中完成,为什么要使用 tkinter