socket.send with Resource temporarily unavailable error

我的python程式如下,目標是建立一個socket server等待連線,並將檔案給送到socket client:

import socket
import os
import sys
import stat
from os.path import dirname, abspath, join
 
def transmitDebugInfo():
	filename = "/bootpart.gz"
 
	debugfile = None
	s = None
	conn = None
	try:
		debugfile = open(filename,'rb')
		blocksize = os.path.getsize(filename)
 
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		s.settimeout(60)
		s.bind(('0.0.0.0', 5555))
		s.listen(1)
		conn, addr = s.accept()
		data = debugfile.read()
		conn.send(data)
	except:
		print sys.exc_info()[0]
		print sys.exc_info()[1]
	finally:	
		if s is not None:
			s.close()
		if conn is not None:
			conn.close()
		if debugfile is not None:
			debugfile.close()
 
 
transmitDebugInfo()
我的client為了方便測試,是直接使用linux的nc command:
nc 192.168.0.1 5555 > test.txt
然而,在VMWare ESXI6.0 u2下會出現以下錯誤:
<class `socket.error`>
[Errno 11] Resource temporarilty unavaliable

實驗1 - 分批送+sleep

首先我發現接收到的檔案大小一定是33304 bytes,於是我加了以下程式做試驗:

print "bufsize = %s" % s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 5000)
print "bufsize = %s" % s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
server程式輸出為:
bufsize  = 32768
bufsize  = 5000
而client輸出檔案大小則變為5792 bytes。我猜想可能和發送buffer有關,於是找了一台ubuntu 14.04做測試;沒想到雖然buffer size為16384卻能正常的發送完檔案。因此我猜測可能和buffer釋放時間有關,於是改寫了以下發送方式:
while 1:
	data = debugfile.read(1024)
	if not data: break
	conn.send(data)
	time.sleep(0.000001)
以每次1024 bytes發送,每次中間等待0.001秒,如此就能順利完成發送。但問我無法確定是由於send data thread搶了所有執行資源,造成buffer無法清空的問題;還是由於清buffer就是比較慢。在無法確認實際原因情況下,這方法我不敢使用。

實驗2 - Remove settimeout

在remove settimeout後,就可以正常傳送與接收資料;沒設定timeout等效於setblocking(1)。

實驗3 - setdefaulttimeout

我在建立socket前,使用socket.setdefaulttimeout(60),發現可以正常傳送與接收資料。這挺奇怪的,我還不曉得這與settimeout有何不同。

實驗4 - 使用strace去跑分批送

與實驗1的區別在於,我移除sleep並透過strace去執行也可以正常執行完畢。

strace -o output.txt -s512 python test.py
我猜測透過strace可能也造成send資料之間的延遲,好讓buffer有足夠時間釋放。

根據實驗結果,有以下結論:

  1. 實驗二說明在blocking mode下,sendall應能正常傳送資料。實驗三為例外,原因還不明。
  2. 實驗一與四說明,如果將send分批進行且有緩衝,應能正常傳送資料。
  3. 並非所有OS有相同結果,可能與python或內核有關。

綜合以上,如果要設定timeout,安全作法應該使用non-blocking IO的存取方式。因此參考此篇文章修改程式碼,針對Resource temporarilty unavaliable錯誤retry:

def sendData(sock, data):
	total_sent = 0
	while len(data):
		try:
			sent = sock.send(data)
			total_sent += sent
			data = data[sent:]
			print 'Sending data'
		except socket.error, e:
			if e.errno != errno.EAGAIN:
				raise e
			print 'Blocking with', len(data), 'remaining'
			select.select([], [sock], [])  
	return total_sent
呼叫程式改為:
data = debugfile.read()
total_data = len(data)
total_send = sendData(conn, data)
print "data size = %s, send size = %s" % (total_data, total_send)