python多線程http
A. 如何實現python多進程http伺服器
埠只能綁定一個進程。
1 換成線程實現 SocketServer.ThreadingTCPServer
2 主進程調度分發。主進程收到埠請求後通過進程間通信讓其他進程工作。
我想要用 python 的 multiprocessing 模塊實現一個多進程多線程的 http 伺服器,伺服器會使用進程池 Pool 創建多個子進程,然後每個子進程再用 socketserver 創建多線程的 http 伺服器,但是現在我遇到一個問題,就是伺服器運行以後,只有第一個子進程可以處理 http 連接,如何做到讓每一個子進程都可以處理連接?
備註:通過 getpid 可以看到每次接受請求的都是同一個子進程
# Python 3import os, socketserver, signal, sysfrom multiprocessing import Poolclass MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.data = self.request.recv(1024)
respone = b'HTTP/1.1 200 OK\r\n\r\nOK%d' % os.getpid()
self.request.sendall(respone)def httpd_task():
socketserver.ThreadingTCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('0.0.0.0', 80), MyTCPHandler) try:
server.serve_forever() except: pass
server.server_close()if __name__=='__main__':
p = Pool(4) for i in range(4):
p.apply_async(httpd_task)
p.close()
p.join()
B. python 多線程ctrl+c不起作用
我這上不了網路網盤,沒法下載,看不了你的代碼。但是可以給你個解決思路,你可以寫把進程號傳出來,到時候外面有個程序來處理。ctrl C肯定是停不了的。
還有就是Python多線程涉及到線程安全,推薦你使用多進程。
nostmabole手打,望採納。
C. python多線程退出
這種情況很正常,Python自身的線程並不安全且不與PyQt兼容,你既然用PyQt了,為什麼不嘗試用Qt自身的線程類QThread呢?那麼基於Qt自身的特點,主線程關閉時,子線程就可以安全地退出了,而不會出現你現在這種情況了。
D. python里同時發送多個http請求怎麼弄
多個同進的話,使用多線程就可以,也可以使用多進程,多進程可以更好的利用多核CPU的能力。
E. 求教python發送多線程http post時,一旦超過一定量就會報in _read_status raise BadStatusLine(line)
我遇到過,當時是在header里指定了content-length。
content-length是指定發送數據的長度。一般沒必要手動指定。而你一旦填錯了,比如填比實際內容還長的值,server會等待那些永遠不會收到的數據,而不響應你。可能會有這個錯誤。你可以看看你構造的request里是不是指定了content-length。
另外,也有這種情況會出現這個錯誤,也就是在用完了httplib之後一定要將前面打開的鏈接用close()方法關閉,如果不關閉連接,再次發起請求,也可能會出這個錯誤
F. 關於http請求和Python
邊下載邊計數,下載到32位元組就停止,然後存檔。沒有參數的。
G. python http怎麼加入epoll
介紹
從2.6版本開始, python 提供了使用linux epoll 的功能. 這篇文章通過3個例子來大致介紹如何使用它. 歡迎提問和反饋.
阻塞式socket通訊
第一個例子是一個簡單的python3.0版本的伺服器代碼, 監聽8080埠的http請求, 列印結果到命令行, 回應http response給客戶端.
行 9: 建立伺服器的socket
行 10: 允許11行的bind()操作, 即使其他程序也在監聽同樣的埠. 不然的話, 這個程序只能在其他程序停止使用這個埠之後的1到2分鍾後才能執行.
行 11: 綁定socket到這台機器上所有IPv4地址上的8080埠.
行 12: 告訴伺服器開始響應從客戶端過來的連接請求.
行 14: 程序會一直停在這里, 直到建立了一個連接. 這個時候, 伺服器socket會建立一個新的socket, 用來和客戶端通訊. 這個新的socket是accept()的返回值, address對象標示了客戶端的IP地址和埠.
行 15-17: 接收數據, 直到一個完整的http請求被接收完畢. 這是一個簡單的http伺服器實現.
行 18: 為了方便驗證, 列印客戶端過來的請求到命令行.
行 19: 發送回應.
行 20-22: 關閉連接, 以及伺服器的監聽socket.
Example1:
import socketEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
connectiontoclient, address = serversocket.accept()
request = b''while EOL1 not in request and EOL2 not in request:
request += connectiontoclient.recv(1024)print(request.decode())
connectiontoclient.send(response)
connectiontoclient.close()
serversocket.close()
第2個例子, 我們在15行加上了一個循環, 用來循環處理客戶端請求, 直到我們中斷這個過程(在命令行下面輸入鍵盤中斷, 比如Ctrl-C). 這個例子更明顯地表示出來了, 伺服器socket並沒有用來做數據處理, 而是接受伺服器過來的連接, 然後建立一個新的socket, 用來和客戶端通訊.
最後的23-24行確保伺服器的監聽socket最後總是close掉, 即使出現了異常.
Example 2:
import socketEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
try: while True:
connectiontoclient, address = serversocket.accept()
request = b''
while EOL1 not in request and EOL2 not in request:
request += connectiontoclient.recv(1024) print('-'*40 + '\n' + request.decode()[:-2])
connectiontoclient.send(response)
connectiontoclient.close()
finally:
serversocket.close()24
非同步socket和linux epoll的優勢
第2個例子裡面的socket採用的是阻塞方式, 因為python解釋器在出現事件之前都處在停止狀態. 16行的accept()一直阻塞, 直到新的連接進來. 19行的recv()也是一直阻塞, 直到從客戶端收到數據(或者直到沒有數據可以接收). 21行的send()也一直阻塞, 直到所有需要發送給客戶端的數據都交給了linux內核的發送隊列.
當一個程序採用阻塞socket的時候, 它經常採用一個線程(甚至一個進程)一個socket通訊的模式. 主線程保留伺服器監聽socket, 接受進來的連接, 一次接受一個連接, 然後把生成的socket交給一個分離的線程去做交互. 因為一個線程只和一個客戶端通訊, 在任何位置的阻塞都不會造成問題. 阻塞本身不會影響其他線程的工作.
多線程阻塞socket模式代碼清晰, 但是有幾個缺陷, 可能很難確保線程間資源共享工作正常, 可能在只有一個CPU的機器上效率低下.
C10K(單機1萬連接問題!) 探討了其他處理並行socket通訊的模式. 一種是採用非同步socket. socket不會阻塞, 直到特定事件發生. 程序在非同步socket上面進行一個特定操作, 並且立即得到一個結果, 不管執行成功或者失敗. 然後讓程序決定下一步怎麼做. 因為非同步socket是非阻塞的, 我們可以不採用多線程. 所有的事情都可以在一個線程裡面完成. 雖然這種模式有它需要面對的問題, 它對於特定程序來說還是不錯的選擇. 也可以和多線程合起來使用: 單線程的非同步socket可以當作伺服器上面處理網路的一個模塊, 而線程可以用來訪問阻塞式的資源, 比如資料庫.
Linux 2.6有一些方式來管理非同步socket, python API能夠用的有3種: select, poll和epoll. epoll和poll比select性能更好, 因為python程序不需要為了特定的事件去查詢單獨的socket, 而是依賴操作系統來告訴你什麼socket產生了什麼事件. epoll比poll性能更好, 因為它不需要每次python程序查詢的時候, 操作系統都去檢查所有的socket, 在事件產生的時候, linux跟蹤他們, 然後在python程序調用的時候, 返回具體的列表. 所以epoll在大量(上千)並行連接下, 是一種更有效率, 伸縮性更強的機制.
採用epoll的非同步socket編程示例
採用epoll的程序一般這樣操作:
建立一個epoll對象
告訴epoll對象, 對於一些socket監控一些事件.
問epoll, 從上次查詢以來什麼socket產生了什麼事件.
針對這些socket做特定操作.
告訴epoll, 修改監控socket和/或監控事件.
重復第3步到第5步, 直到結束.
銷毀epoll對象.
採用非同步socket的時候第3步重復了第2步的事情. 這里的程序更復雜, 因為一個線程需要和多個客戶端交互.
行 1: select模塊帶有epoll功能
行 13: 因為socket默認是阻塞的, 我們需要設置成非阻塞(非同步)模式.
行 15: 建立一個epoll對象.
行 16: 注冊伺服器socket, 監聽讀取事件. 伺服器socket接收一個連接的時候, 產生一個讀取事件.
行 19: connections表映射文件描述符(file descriptors, 整型)到對應的網路連接對象上面.
行 21: epoll對象查詢一下是否有感興趣的事件發生, 參數1說明我們最多等待1秒的時間. 如果有對應事件發生, 立刻會返回一個事件列表.
行 22: 返回的events是一個(fileno, event code)tuple列表. fileno是文件描述符, 是一個整型數.
行 23: 如果是伺服器socket的事件, 那麼需要針對新的連接建立一個socket.
行 25: 設置socket為非阻塞模式.
行 26: 注冊socket的read(EPOLLIN)事件.
行 31: 如果讀取事件發生, 從客戶端讀取新數據.
行 33: 一旦完整的http請求接收到, 取消注冊讀取事件, 注冊寫入事件(EPOLLOUT), 寫入事件在能夠發送數據回客戶端的時候產生.
行 34: 列印完整的http請求, 展示即使通訊是交錯的, 數據本身是作為一個完整的信息組合和處理的.
行 35: 如果寫入事件發生在一個客戶端socket上面, 我們就可以發送新數據到客戶端了.
行s 36-38: 一次發送一部分返回數據, 直到所有數據都交給操作系統的發送隊列.
行 39: 一旦所有的返回數據都發送完, 取消監聽讀取和寫入事件.
行 40: 如果連接被明確關閉掉, 這一步是可選的. 這個例子採用這個方法是為了讓客戶端首先斷開, 告訴客戶端沒有數據需要發送和接收了, 然後讓客戶端斷開連接.
行 41: HUP(hang-up)事件表示客戶端斷開了連接(比如 closed), 所以伺服器這端也會斷開. 不需要注冊HUP事件, 因為它們都會標示到注冊在epoll的socket.
行 42: 取消注冊.
行 43: 斷開連接.
行s 18-45: 在這里的異常捕捉的作用是, 我們的例子總是採用鍵盤中斷來停止程序執行.
行s 46-48: 雖然開啟的socket不需要手動關閉, 程序退出的時候會自動關閉, 明確寫出來這樣的代碼, 是更好的編碼風格.
Example 3:
import socket, selectEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0)
epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN)
try:
connections = {}; requests = {}; responses = {} while True:
events = epoll.poll(1) for fileno, event in events: if fileno == serversocket.fileno():
connection, address = serversocket.accept()
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response
elif event & select.EPOLLIN:
requests[fileno] += connections[fileno].recv(1024) if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT) print('-'*40 + '\n' + requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:] if len(responses[fileno]) == 0:
epoll.modify(fileno, 0)
connections[fileno].shutdown(socket.SHUT_RDWR)
elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
epoll有2種模式, 邊沿觸發(edge-triggered)和狀態觸發(level-triggered). 邊沿觸發模式下, epoll.poll()在讀取/寫入事件發生的時候只返回一次, 程序必須在後續調用epoll.poll()之前處理完對應事件的所有的數據. 當從一個事件中獲取的數據被用完了, 更多在socket上的處理會產生異常. 相反, 在狀態觸發模式下面, 重復調用epoll.poll()只會返回重復的事件, 直到所有對應的數據都處理完成. 一般情況下不產生異常.
比如, 一個伺服器socket注冊了讀取事件, 邊沿觸發程序需要調用accept建立新的socket連接直到一個socket.error錯誤產生, 然後狀態觸發下只需要處理一個單獨的accept(), 然後繼續epoll查詢新的事件來判斷是否有新的accept需要操作.
例子3採用默認的狀態觸發模式, 例子4展示如何用邊沿觸發模式. 例子4中的25, 36和45行引入了循環, 直到錯誤產生(或者所有的數據都處理完了), 32, 38 和48行捕捉socket異常. 最後16, 28, 41 和51行添加EPOLLET mask用來設置邊沿觸發.
Example 4:
import socket, selectEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0)
epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN | select.EPOLLET)
try:
connections = {}; requests = {}; responses = {} while True:
events = epoll.poll(1) for fileno, event in events: if fileno == serversocket.fileno():
try: while True:
connection, address = serversocket.accept()
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN | select.EPOLLET)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response
except socket.error:
pass
elif event & select.EPOLLIN:
try: while True:
requests[fileno] += connections[fileno].recv(1024)
except socket.error:
pass if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT | select.EPOLLET) print('-'*40 + '\n' + requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
try: while len(responses[fileno]) > 0:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:]
except socket.error:
pass if len(responses[fileno]) == 0:
epoll.modify(fileno, select.EPOLLET)
connections[fileno].shutdown(socket.SHUT_RDWR)
elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
因為比較類似, 狀態觸發經常用在轉換採用select/poll模式的程序上面, 邊沿觸發用在程序員不需要或者不希望操作系統來管理事件狀態的場合上面.
除了這兩種模式以外, socket經常注冊為EPOLLONESHOT event mask, 當用到這個選項的時候, 事件只有效一次, 之後會自動從監控的注冊列表中移除.
H. 為什麼 python 多線程還要比 gevent 要快一點
#coding=utf-8
import requests
import threading
import time
import Queue
geturl='http://192.168.1.41/login.php'#內部伺服器地址,可以改成自己的
xrange_num=1000#設置循環總次數
con_num=20#設置線程數
start=time.time()
class testThread(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
while 1:
try:
url= self.queue.get_nowait()
except Queue.Empty:
raise SystemExit
requests.get(url)
queue = Queue.Queue()
for x in xrange(xrange_num):
#line = line.strip()
queue.put(geturl)
threads = []
for mmy in xrange(con_num):
t = testThread(queue)
t.start()
threads.append(t)
# Wait for all threads to finish
for thread in threads:
thread.join()
print time.time()-start
import gevent
from gevent import monkey
monkey.patch_all()
from gevent.pool import Pool
import requests
start2=time.time()
p = Pool(con_num)
def down(url):
requests.get(url)
for x in xrange(xrange_num):
p.spawn(down, geturl)
p.join()
print time.time()-start2
raw_input()