Block Socket VS DeadLock Socket Python描述
一個男生在追一個女生,在女生未同意前,男生是不會做其它事情的,我們可以稱之為阻塞(就像本文的題圖一樣)
兩名舔狗同時碰面鞠躬以示舔功到位,兩位舔狗都在等對方先站起來自己再戰起來,結果就是誰都不會站起來,我們可以稱之為死鎖(就像下面這張圖一樣)
下面我們可以用代碼來實現阻塞
server.py
import socket
import time
def recvall(sock, length):
data = b
while len(data) < length:
more = sock.recv(length - len(data))
data += more #直到收到的數據達到length的值才會返回
return data
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((localhost, 5000))
sock.listen(1)
while True:
sc, sockname = sock.accept()
print(I have accepted a connection from, sockname)
print(Socket name: , sc.getsockname())
print(Socket peer: , sc.getpeername())
message = recvall(sc, 64)
print(Recevied info is , repr(message))
sc.sendall(bResponse from server)
sc.close()
邏輯上是沒有任何問題的,但是問題來了,如果客戶端發出的數據小於length
的值怎麼辦?
block-client.py
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((localhost, 5000))
sock.send(bResquest from client) #長度小於64
response = sock.recv(64)
sock.close()
print(response)
print(Test for blocking)
執行結果
為什麼會發生這種事情?主要是因為server.py
中的recvall
函數
def recvall(sock, length):
data = b
while len(data) < length:
more = sock.recv(length - len(data))
data += more #直到收到的數據達到length的值才會返回
return data
這個循環代表著,如果收到的數據小於length
那麼它就會一直等著,直到收到長度為length
的數據,才會進行下一步操作
但是客戶端總共才送出了length < 30
的數據
那麼服務端會便會進入死循環狀態,在讀取數據未完成前,是不會進行寫操作的,即阻塞
在解釋死鎖前,先了解一下TCP建立連接後的工作流程
在建立連接後,分別在客戶端和服務端建立兩個緩衝區,分別為Output
、Input
客服端傳送數據,服務端接收數據(執行完後緩衝區會被清理)
服務端處理數據,並發送數據,客戶端接收數據(執行完後緩衝區會被清理)
完成,進入下一次循環
那麼為什麼形成死鎖呢?
- 服務端或者客服端不能同時執行兩個操作,即寫的時候不能讀,讀的時候不能寫
- 緩衝區被佔滿
想像一下緩衝區被佔滿的情況
假定緩衝區設定為100KB,那麼假定客戶端會傳送220KB的數據,這時候client
的Output buffer
會被佔滿,而server
的Input Buffer
是從 client
的Output buffer
複製的數據因此也會被佔滿
server
先從Input Buffer
拿到10KB的數據,並返回220KB的數據給client
,這時候server
的Output Buffer
會被佔滿,而client
的Input Buffer
是從server
的Output Buffer
複製的數據因此也會被佔滿
這時候server
開始著急了,要趕緊把Output Buffer
的值先送出去才能進行下一次請求,而client
這時候不僅不幫忙,還對server
說:Hi,哥們,我送出的數據你還沒處理呢
你看,server
既想把數據先送出去,又要處理client
的請求,那乾脆什麼都不做算了!同時也給client
帶去了麻煩
與此同時,client
也著急了,想趕緊把Output Buffer
的值送出去,但是server
想:哎呀,我可不能讓client
這麼輕鬆,給它點數據玩玩吧
這樣client
既想先把數據送出去,又要讀取server
給的數據,太忙了!不幹了!
這樣就行成了死鎖
下面代碼來源於,因為在這裡只是當個示例,因此不會解釋代碼
Python網路編程第三版tcp-deadlock.py
import argparse, socket, sys
def server(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
print(Listening at, sock.getsockname())
while True:
sc, sockname = sock.accept()
print(Processing up to 1024 bytes at a time from, sockname)
n = 0
while True:
data = sc.recv(1024)
if not data:
break
output = data.decode(ascii).upper().encode(ascii)
sc.sendall(output) # send it back uppercase
n += len(data)
print(
%d bytes processed so far % (n,), end= )
print()
sc.close()
print( Socket closed)
def client(host, port, bytecount):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
bytecount = (bytecount + 15) // 16 * 16 # round up to a multiple of 16
message = bcapitalize this! # 16-byte message to repeat over and over
print(Sending, bytecount, bytes of data, in chunks of 16 bytes)
sock.connect((host, port))
sent = 0
while sent < bytecount:
sock.sendall(message)
sent += len(message)
print(
%d bytes sent % (sent,), end= )
print()
print(Receiving all the data the server sends back)
received = 0
while True:
data = sock.recv(42)
if not received:
print( The first data received says, repr(data))
if not data:
break
received += len(data)
print(
%d bytes received % (received,), end= )
print()
sock.close()
if __name__ == __main__:
roles = (client, server)
parser = argparse.ArgumentParser(description=Get deadlocked over TCP)
parser.add_argument(role, choices=roles, help=which role to play)
parser.add_argument(host, help=interface the server listens at;
host the client sends to)
parser.add_argument(bytecount, type=int, nargs=?, default=16,
help=number of bytes for client to send (default 16))
parser.add_argument(-p, metavar=PORT, type=int, default=1060,
help=TCP port (default 1060))
args = parser.parse_args()
if args.role == client:
client(args.host, args.p, args.bytecount)#set data length
else:
server(args.host, args.p)
當我們傳輸的數據很小時
這時候很快就完成了交互工作
當我們傳輸的數據很大時
BOMB!死鎖工作完成
參考文獻:
[1]關於死鎖的圖就是根據該鏈接所畫,並且該鏈接對死鎖的解釋很清楚
TCP deadlock problems - OmniMark Concept[2]第三章TCP關於死鎖的章節
Python網路編程(第3版)[3]書的源碼
brandon-rhodes/fopnp
推薦閱讀: