多程序模型
基於最原始的阻塞網路 I/O, 如果伺服器要支援多個客戶端,其中比較傳統的方式,就是使用多程序模型,也就是為每個客戶端分配一個程序來處理請求。
伺服器的主程序負責監聽客戶的連線,一旦與客戶端連線完成,accept() 函式就會返回一個「已連線 Socket」,這時就透過 fork()
函式建立一個子程序,實際上就把父程序所有相關的東西都複製一份,包括檔案描述符、記憶體地址空間、程式計數器、執行的程式碼等。
這兩個程序剛複製完的時候,幾乎一模一樣。不過,會根據返回值來區分是父程序還是子程序,如果返回值是 0,則是子程序;如果返回值是其他的整數,就是父程序。
正因為子程序會複製父程序的檔案描述符,於是就可以直接使用「已連線 Socket 」和客戶端通訊了,
可以發現,子程序不需要關心「監聽 Socket」,只需要關心「已連線 Socket」;父程序則相反,將客戶服務交給子程序來處理,因此父程序不需要關心「已連線 Socket」,只需要關心「監聽 Socket」。
下面這張圖描述了從連線請求到連線建立,父程序建立生子程序為客戶服務。
另外,當「子程序」退出時,實際上核心裡還會保留該程序的一些資訊,也是會佔用記憶體的,如果不做好“回收”工作,就會變成殭屍程序,隨著殭屍程序越多,會慢慢耗盡我們的系統資源。
因此,父程序要“善後”好自己的孩子,怎麼善後呢?那麼有兩種方式可以在子程序退出後回收資源,分別是呼叫 wait()
和 waitpid()
函式。
這種用多個程序來應付多個客戶端的方式,在應對 100 個客戶端還是可行的,但是當客戶端數量高達一萬時,肯定扛不住的,因為每產生一個程序,必會佔據一定的系統資源,而且程序間上下文切換的“包袱”是很重的,效能會大打折扣。
程序的上下文切換不僅包含了虛擬記憶體、棧、全域性變數等使用者空間的資源,還包括了核心堆疊、暫存器等核心空間的資源。