跳到主要內容

socket.send with Resource temporarily unavailable error

Problem

我的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

Diagnosis

實驗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有足夠時間釋放。

How to resolve?

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

  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)

Reference

留言

這個網誌中的熱門文章

PostgreSQL - Unattended installation on windows

Introduction 要將別人軟體包裝到自己軟體中,不可或缺的東西就是Unattended installation。以Unattended installation來說,我們可以選擇透過Installer的silent mode安裝,也可以透過把目標軟體做成portable的版本。本篇文章分享這兩種方法,教導大家如何將PostgreSQL透過Unattended installation方式安裝到目標系統成為service。 Note. 本篇以PostgreSQL 10.7為例。 Install with installer Tips 安裝程式或反安裝程式的參數,除了可以直接上官網搜尋Installation User Guide以外,也可以直接使用help參數查詢: postgresql- 10.7 - 2 -windows-x64.exe --help Windows安裝程式主要有EnterpriseDB與BigSQL兩種。BigSQL版本安裝元件是透過網路下載且支援參數不如EnterpriseDB版本多,以我們需求來說,我們傾向於使用EnterpriseDB版本。接下來分享給大家安裝與反安裝方法。 Installation @ echo off set INSTALL_DIR =C:\postgres10 set INSTALLER =postgresql- 10.7 - 2 -windows-x64.exe   rem options for installation set SSMDB_SERVICE =postgresql- 10 set MODE =--unattendedmodeui none --mode unattended   set DB_PASSWD =--superpassword postgres set DB_PORT =--serverport 5432   set SERVICE_NAME =--servicename % SSMDB_SERVICE %   set PREFIX =--prefix "%INSTALL_DIR%" set DATA_DIR =--datadir "%INSTALL_DIR%\data"   set OPTIONS =

How to install RIDE on Windows?

Introduction 多年沒在Windows上開發RobotFramework,趁著這次整理一下RIDE安裝方法。 目前RIDE最新版本與Python對應版本如下: (3.6 < python <= 3.11) Install current released version (2.0.8.1) with: pip install -U robotframework-ride 安裝Python 直接到Python官網找尋最新的3.11版本,我使用3.11.9: link 。安裝就是一直下一步而已。 安裝wxPython 每次安裝RIDE最困難的都是wxPython。看了一下 官網 描述,我就姑且相信一下: 接著進入下 載頁面 就有安裝教學。基本上就是到Python目錄下的Scripts直接執行以下command: pip install -U wxPython 安裝RIDE 接著就如RIDE官網所說,執行以下command: pip install -U robotframework-ride 啟動RIDE 直接在相同目錄下執行ride就可以啟動了,你也可以直接在桌面建ride連結,加快下次啟動時間。 沒想到這次這麼順利就安裝完成了。因為我是使用java去啟動robot framework,就不特別講要怎麼使用pip安裝robot framework了。

Hello World!

即將要搬家,因此舊網頁內容將慢慢轉移至Blogger。 如果要存取舊網頁,可以使用以下連結: https://wiki.tonylin.idv.tw/dokuwiki/doku.php