網絡通信中的死鎖
客戶端代碼
點擊查看代碼
import socket
from pathlib import Path
SERVER_ADDRESS = Path("/tmp/uds_socket")
CHUNK_SIZE = 1024
def main():
# Create Unix Domain Socket in client side
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(str(SERVER_ADDRESS))
s.sendall(b"Hello from client")
response = b""
while data := s.recv(CHUNK_SIZE):
response += data
print(response.decode())
if __name__ == "__main__":
main()
服務端代碼
點擊查看代碼
import socket
from pathlib import Path
SERVER_ADDRESS = Path("/tmp/uds_socket")
CHUNK_SIZE = 1024
def main():
# If the socket file already exists, remove it
if SERVER_ADDRESS.exists():
SERVER_ADDRESS.unlink()
# Create Unix Domain Socket in server side
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.bind(str(SERVER_ADDRESS))
s.listen(16)
print(f"[*] Listening at {str(SERVER_ADDRESS)}...")
while True:
try:
conn, _ = s.accept()
except KeyboardInterrupt:
print("\n[*] Shutting down server...")
break
with conn:
request = b""
while data := conn.recv(CHUNK_SIZE):
request += data
print(f"[From client]: {request.decode()}")
conn.sendall(b"Hello from server")
conn.shutdown(socket.SHUT_WR)
if __name__ == "__main__":
main()
啟動服務端,然后運行客戶端就會出現死鎖:客戶端和服務端都阻塞在recv方法上,具體過程和解決辦法分析如下
- 過程
啟動服務端,服務端首先阻塞在accept上
啟動客戶端,客戶端成功連接服務端,服務端從accept返回,然后阻塞在recv方法上
客戶端使用sendall發送數據之后也阻塞在recv方法上,至此死鎖就發生了!
- 解決辦法
這是經典的死鎖發生的必要條件之一:循環等待。
客戶端在發送完數據之后應該將自己的狀態設置為半關閉狀態,然后客戶端也會將自己的半關閉狀態通知給客戶端。
服務端的接收緩沖區為空,因此recv方法會一直阻塞,除非發生以下兩種情況之一:
- 客戶端發送通知自己是半關閉狀態
- 客戶端發送通知自己是全關閉狀態
服務端收到客戶端的半關閉或者全關閉通知之后,會從recv方法中返回,獲得一個空字節串,然后結束while循環。
服務端使用sendall方法發送所有數據,然后進入半關閉狀態,然后進入全關閉狀態。
客戶端recv方法收到數據并且知道了服務端處于半關閉或者全關閉狀態,就會從recv方法中返回,獲得一個空字節串,然后結束while循環。
解決辦法:在客戶端發送萬數據之后,添加一段代碼s.shutdown(socket.SHUT_WR)。
在服務端代碼中,shutdown方法不是必要的,因為服務端代碼在sendall之后,即使沒有使用shutdown進入半關閉狀態,也會關閉自己的socket,進入全連接狀態
但是,如果客戶端/服務端確認自己不會再發送數據了,關閉自己的寫端進入半關閉狀態可以讓代碼更健壯。
修改后的客戶端代碼
點擊查看代碼
import socket
from pathlib import Path
SERVER_ADDRESS = Path("/tmp/uds_socket")
CHUNK_SIZE = 1024
def main():
# Create Unix Domain Socket in client side
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(str(SERVER_ADDRESS))
s.sendall(b"Hello from client")
# TODO client.shutdown is very important, otherwise server won't get EOF and keep waiting for data
# TODO then client and server will be deadlocked!!!
s.shutdown(socket.SHUT_WR)
response = b""
while data := s.recv(CHUNK_SIZE):
response += data
print(response.decode())
if __name__ == "__main__":
main()
運行結果


浙公網安備 33010602011771號