[Python]再学 socket 之非阻塞 Server
再學 socket 之非阻塞 Server
本文是基于 python2.7 實現,運行于 Mac 系統下
本篇文章是上一篇初探 socket 的續集,
上一篇文章介紹了:如何建立起一個基本的 socket 連接、TCP 和 UDP 的概念、socket 常用參數和方法
Socket 是用來通信、傳輸數據的對象,上一篇已經研究了如果進行基本的通行和傳輸數據。因為,在這個互
聯網爆發的時代,做為 Server 的 socket 要同時接收很多的請求。
通過閱讀:地址,強烈推薦閱讀原文。
整理了下面的文字,如何:創建一個 非阻塞的 server。
一、阻塞 Server
- 阻塞 Server 示例
- 為什么會出現阻塞
1.1 阻塞 Server 示例
下面就通過C/S模型,展示阻塞狀態:
- 接收其它 socket 請求的 socket 叫做:Server(S)
- 請求 Server 的 socket 叫做:Client(C)
該代碼片段分別是:阻塞的 Server 和測試用的 Client:
#!/usr/bin/env python # -*- coding:utf-8 -*- # # Author : XueWeiHan # Date : 17/2/25 上午10:39 # Desc : 阻塞 server import socket import timeSERVER_ADDRESS = (HOST, PORT) = '', 50007 REQUEST_QUEUE_SIZE = 5def handle_request(client_connection):"""處理請求"""request = client_connection.recv(1024)print('Server recv: {request_data}'.format(request_data=request.decode()))time.sleep(10) # 模擬阻塞事件http_response = "Hello, I'm server"client_connection.sendall(http_response)def server():listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)listen_socket.bind(SERVER_ADDRESS)listen_socket.listen(REQUEST_QUEUE_SIZE)print('Server on port {port} ...'.format(port=PORT))while 1:client_connection, client_address = listen_socket.accept()handle_request(client_connection)client_connection.close()if __name__ == '__main__':server()- REQUEST_QUEUE_SIZE:在 sever 阻塞的時,允許掛起幾個連接。便于可以處理時直接從該隊列中取得連接,減少建立連接的時間
- time.sleep:用于模擬阻塞
測試用的 Client
#!/usr/bin/env python # -*- coding:utf-8 -*- # # Author : XueWeiHan # Date : 17/2/25 上午11:13 # Desc : 測試 clientimport socketSERVER_ADDRESS = (HOST, PORT) = '', 50007def send_message(s, message):"""發送請求"""s.sendall(message)def client():message = "Hello, I'm client"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(SERVER_ADDRESS)send_message(s, message)print 'Client is Waiting response...'data = s.recv(1024)s.close()print 'Client recv:', repr(data) # 打印從服務器接收回來的數據if __name__ == '__main__':client()打開三個終端,先運行 Server,在另外兩個終端運行 Client(分別起名為client1、client2),會發現
服務器先接收 client1 的數據,然后返回響應。再此之前 client2 一直處于等待的狀態。只有等 Server
處理完 client1 的請求后,才會接收 client2 的數據。
這樣一個個地接收請求、處理請求的 Server 就叫做 阻塞 Server。
1.2 為什么會出現阻塞?
因為服務器處理請求是需要消耗時間的,正如我上面的阻塞 Server 代碼中的time.sleep(10),用于模擬
服務器處理請求消耗的時間。
在處理完上一個請求(返回給 Client 數據)的這段時間中,服務器無法處理其它的請求,只能讓其它的 Client 等待。這樣的效率是
極其低下的,所以下面就介紹如何創建一個非阻塞的 Server
二、非阻塞 Server
- 需要知道的一些基本概念
- 非阻塞 Server 示例(多進程)
后面會用多進程實現 非阻塞socket,在此之前需要了解一些基本知識和概念,便于理解后面的代碼。
2.1 需要知道的一些基本概念
- Socket 處理請求的過程
- 進程
- 文件描述符
- 如何查看進程和用戶資源
2.1.1 Socket 處理請求的過程
參照上面寫的阻塞 Server 的代碼,可以看出:服務器端的socket對象,listen_socket 從不和客戶端交換數據。它只會通過accept方法接受連接。然后,創建一個新的socket對象,client_connection用于和客戶端通信。
所以,服務器端的socket 分為:接受請求的socket(listen_socket) 和 與客戶端傳輸數據的socket(client_connection)。
正如上面說到的,真正阻塞地方是:與客戶端傳輸數據的socket(client_connection) 需要等待處理請求的結果,然后返還給客戶端,結束這次通信,才能處理后面的請求。
2.1.2 進程
存在硬盤中的叫做‘程序’(*.py),當程序運行加載到內存中的時候叫做‘進程’。系統會分配給每個進程一個唯一 ID,
這個 ID 叫做:PID ,進程還分為父進程和子進程,父進程(PPID)創建子進程(PID)。關系如下圖:
可以通過ps命令來查看進程的信息:每天一個linux命令(41):ps命令
需要注意:
- 子進程一定要關閉
- 子進程關閉一定要通知父進程,否則會出現‘僵尸進程’
- 一定要先結束父進程,再結束子進程,否則會出現‘孤兒進程’
僵尸進程:一個進程使用fork創建子進程,如果子進程退出,而父進程并沒有調用wait或waitpid獲取子進程的狀態信息,那么子進程的進程描述符仍然保存在系統中。這種進程稱之為僵尸進程。(系統所能使用的進程號是有限制的,如果大量的產生僵死進程,將因為沒有可用的進程號而導致系統不能產生新的進程。則會拋出OSError: [Errno 35] Resource temporarily unavailable異常)
孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那么那些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,并由init進程對它們完成狀態收集工作。(沒有危害)
2.1.3 文件描述符
在UNIX中一切都是一個文件,當操作系統打開一存在的個文件的時候,便會返回一個‘文件描述符’,進程通過
操作該文件操作符,從而實現對文件的讀寫。Socket 是一個操作文件描述符的進程,Python 的 socket
模塊提供了這些操作系統底層的實現。我們只需要調用socket對象的方式就可以了。
需要注意:
- 文件描述符的回收機制是采用引用計數方式
- 每次操作完文件描述符需要調用close()方法,關閉文件描述符。道理和進程一樣,操作系統都會最多可創建的文本描述符的限制,如果一直不關閉文本描述符的話,導致數量太多無法創建新的,就會拋出OSError: [Errno 24] Too many open file異常。
2.1.4 如何查看進程和用戶資源極限
計算機的計算和存儲能力都是有限的,統稱為計算機資源。
上面說了進程和文件描述符號都是有個最大數量(極限),下面就是用于查看和修改用戶資源限制的命令——ulimit。
-a 列出所有當前資源極限。 -c 以 512 字節塊為單位,指定核心轉儲的大小。 -d 以 K 字節為單位指定數據區域的大小。 -f 使用 Limit 參數時設定文件大小極限(以塊為單位),或者在未指定參數時報告文件大小極限。缺省值為 -f 標志。 -H 指定設置某個給定資源的硬極限。如果用戶擁有 root 用戶權限,可以增大硬極限。任何用戶均可減少硬極限。 -m 以 K 字節為單位指定物理內存的大小(駐留集合大小)。系統未強制實施此限制。 -n 指定一個進程可以擁有的文件描述符數的極限。 -r 指定對進程擁有線程數的限制。 -s 以 K 字節為單位指定堆棧的大小。 -S 指定為給定的資源設置軟極限。軟極限可增大到硬極限的值。如果 -H 和 -S 標志均未指定,極限適用于以上二者。 -t 指定每個進程所使用的秒數。 -u 指定對用戶可以創建的進程數的限制。常用命令如下:
- ulimit -a:查看
- ulimit -n:設置一個進程可擁有文件描述符數量
- ulimit -u:最多可以創建多少個進程
2.2 Fork 方式的非阻塞 Server
采用 fork 的方式實現非阻塞 Server,主要原理就是當 socket 接受到(accept)一個請求,就 fork 出一個子進程
去處理這個請求。然后父進程繼續接受請求。從而實現并發的處理請求,不需要處理上一個請求才能接受、處理下一個請求。
如閱讀代碼時出現的問題,可以參考下面的關鍵字:
最后
該非阻塞 Server 是通過操作系統級別的 fork 實現的,用到了多進程和信號機制。
因為多進程解決非阻塞的問題,很好理解,但是十分消耗計算機資源的,后面會介紹更加輕量級的——利用事件循環實現非阻塞 Server。
挖個坑~
參考
- 什么是孤兒進程、僵尸進程
- linux 如何清理僵尸進程
- 什么是pid、ppid
- ulimit 命令
總結
以上是生活随笔為你收集整理的[Python]再学 socket 之非阻塞 Server的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 读《Oracle DBA工作笔记》知识点
- 下一篇: websocket python爬虫_p