通過 fork 創建子進程的方式可以實現父子進程監聽相同的端口。
方法:在綁定端口號(bind函數)之后,監聽端口號之前(listen函數),用fork()函數生成子進程,這樣子進程就可以克隆父進程,達到監聽同一個端口的目的。
# 代碼示例:一主一子
import socket
import select
import sys
import struct
import os
import time
if __name__ == '__main__':
pid = os.getpid()
s1 = socket.socket() # 創建 socket 對象
# host = socket.gethostname() # 獲取本地主機名
host = '127.0.0.1'
port1 = 12346 # 設置端口號
# port2 = 12347
# --關鍵代碼--
s1.bind((host, port1))
pid1 = os.fork()
print("我會被主子進程分別執行一次")
# 也可以分別寫到分子進程里
s1.listen(5)
# --關鍵代碼--
while True:
if pid1 == 0:
# s1.listen(5)
print("子進程")
socket1, addr1 = s1.accept()
print(addr1)
socket1.send("子進程響應".encode('utf-8'))
socket1.close()
print('結束服務端子進程')
else:
# s1.listen(5)
print("主進程")
socket2, addr2=s1.accept()
print(addr2)
socket2.send("主進程響應".encode('utf-8'))
socket2.close()
print('結束服務端主進程')
# 代碼示例:一主多子
import socket
import select
import sys
import struct
import os
import time
if __name__ == '__main__':
pid = os.getpid()
s1 = socket.socket() # 創建 socket 對象
# host = socket.gethostname() # 獲取本地主機名
host = '127.0.0.1'
port1 = 12346 # 設置端口號
# port2 = 12347
s1.bind((host, port1))
pid1 = os.fork()
# s1.listen(5)
print("我會被主子進程分別執行一次")
while True:
if pid1 == 0:
s1.listen(5)
print("子進程1")
socket1, addr1 = s1.accept()
print(addr1)
socket1.send("子進程響應1".encode('utf-8'))
socket1.close()
print('結束服務端子進程1')
elif pid1 != 0:
pid2 = os.fork()
if pid2 == 0:
s1.listen(5)
print("子進程2")
socket2, addr2 = s1.accept()
print(addr2)
socket2.send("子進程響應2".encode('utf-8'))
socket2.close()
print('結束服務端子進程2')
else:
s1.listen(5)
print("主進程")
socket2, addr2 = s1.accept()
print(addr2)
socket2.send("主進程響應".encode('utf-8'))
socket2.close()
print('結束服務端主進程')
# 試想下子進程還有子進程的寫法和用法
驚群現象
當連接到來時,子進程、父進程都可以 accept, 這就是著名的“驚群”問題(thundering herd problem)。
在該模型下(多個子進程同時共享監聽套接字)即可實現服務器並發處理客戶端的連接。這里要注意的是,計算機三次握手創建連接是在內核進程里完成的,不需要應用服務進程參數的,而服務進程僅僅要做的是調用accept將已建立的連接構建對應的連接套接字connfd(可參考 http://blog.csdn.net/ordeder/article/details/21551567)。多個服務進程同時阻塞在accept等待監聽套接字已建立連接的信息,那么當內核在該監聽套接字上建立一個連接,那么將同時喚起這些處於accept阻塞的服務進程,從而導致“驚群現象”的產生,喚起多余的進程將影響服務器的性能(僅有一個服務進程accept成功,其他進程被喚起后沒搶到“連接”而再次進入休眠)。
應用多進程多線程模型
一直疑惑一個應用app如何才能以多進程,多線程的方式運行。對於多線程可能很好理解,我們只要在進程中啟用多線程的模式即可。也就是來一個請求,我們就用函數pthread_create()啟用一個線程即可。這樣我們的應用就可以在單進程,多線程的模式下工作。
一個應用app通常工作在多進程,多線程的模式下,它的效率是最高的。那么我們如何才能做到多進程模式呢?經驗告訴我們,如果多次啟動一個進程會報錯:“Address already in use!"。這是由於bind函數導致的,由於該端口號已經被監聽了。
fork原理
fork時,子進程復制一份父進程的資源。然后父子進程分別執行os.fork()之后的程序
子進程中fork函數返回0,父進程中返回子進程的pid
Python的os.fork()是一個會返回兩次的函數
https://www.cnblogs.com/Magic-Dev/p/11405448.html
通過linux內核的SO_REUSEPORT選項實現多個進程監聽相同的端口
# reuseport.py代碼
import socket
import os
#xiaorui.cc
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(('0.0.0.0', 1234))
s.listen(1)
while True:
conn, addr = s.accept()
print('Connected to {}'.format(os.getpid()))
data = conn.recv(1024)
conn.send(data)
conn.send(str(os.getpid()))
conn.close()
# 啟動多個進程
nohup python reuseport.py &
nohup python reuseport.py &
nohup python reuseport.py &
# 使用nc測試,可以得到隨機的一個進程響應
echo "xiaorui" | nc localhost 1234
# 使用socat(nc的增強版)測試,可以得到隨機的一個進程響應
echo "ss" | socat - tcp-connect:localhost:1234
http://xiaorui.cc/2015/12/02/使用socket-so_reuseport提高服務端性能/
https://www.jianshu.com/p/7e84a33b46e9