socket.recv

本文仅讨论阻塞socket的情况。在v2ex上看到了2个提问,socket.recv 完整接收数据python socket 一个很简单的问题把我难住了.主要会有2个方面。怎么判断数据是否传输完成了进行下一步操作。recv什么时候返回数据,返回多少字节
从一个读取文件的例子开始说起

with open('backup.py') as f:
data = ''
while 1:
buffer = f.read(1024)
if len(buffer) == 0:
break
data += buffer

这个例子中read是一直有数据返回的,文件读取完成就会一直返回空字符,这样就能判断什么时候完成就停止循环进入下一步操作了。
可能有些人会应用到socket编程中,recv后得到字符串,判断字符串长度。当长度为0的时候就认为传输完成了。惊奇的是这样写大多数时候居然真的能正常使用。看起来没什么问题。下面是一个例子


import socket, time

s = socket.socket()
s.connect(('down.sandai.net', 80))
s.send("""GET /thunder7/Thunder_dl_7.9.43.5054.exe HTTP/1.1
Host: down.sandai.net
User-Agent: curl/7.43.0
Accept: */*

""")

data = ''
wait_time = 0

while 1:
start = time.time()
ret = s.recv(2048)
print(len(ret))
end = time.time()
if end - start > wait_time:
wait_time = end - start
data += ret
if len(ret) == 0:
break

print(wait_time)

上面这个例子请求迅雷下载链接获得数据,在网络正常的时候是没有什么问题的。最后的打印的wait_time大概30左右。这其实是数据传输完成到socket关闭的时间(因为服务端设置了Connection: Keep-Alive且为30秒)。从效率来说数据传输完成我们白白等待了30秒,其次更重要的是仅仅对方服务器执行关闭操作s.recv才会返回空
。万一对方服务器没有写好一直不关闭连接怎么办(不要以为不关闭不可能,不信可以连一下z.cn试一试)。所以从tcp层面来判断数据传输完成是不可取的
,这种需求需要在应用层完成。
拿应用最广泛的http协议来说。它有明显的传输完成标志。上面的程序改写一下(忽略异常处理,不对chunk进行处理)

import socket, time, re

s = socket.socket()
s.connect(('down.sandai.net', 80))

s.send("""GET /thunder7/Thunder_dl_7.9.43.5054.exe HTTP/1.1
Host: down.sandai.net
User-Agent: curl/7.43.0
Accept: */*

""")

length_re = re.compile(r'Content-Length: (\d+)',re.MULTILINE)
length, body = -2, -1
while 1:
ret = s.recv(2048)
data += ret
if body > 0:
body += len(ret)
if body == length:
break
if length == -1 and '\r\n\r\n' in data:
length = int(length_re.search(data).group(1))
body = len(data.split('\r\n\r\n')[-1])
if length == body:
break
if len(ret) == 0:
break

print(length)
print(len(data))
print(body)

根据http应用层协议当服务端返回内容的时候,获取Content-Length头部内容然后recv接收所有需要内容后主动关闭连接
,这样就不需要等待服务器关闭连接了。
另外recv或许还有一个比较容易曲解的地方recv(maxsize)并不是阻塞到直到获取到maxsize长度后才返回。这个地方可以这样理解,把这个IO流当做一个盒子。当盒子里面没有内容的时候recv是阻塞的。某一时刻盒子里面放进了一些内容,不管放进了多少recv会读取最多maxsize内容返回。顺便说一下epoll中边缘触发(edge-triggered)和水平触发(level-triggered)的理解。边缘触发就是当这个盒子中放进数据的时候我通知你一下。水平触发就是当这个盒子中还有数据没有取出的时候我通知你一下。这样就造成了使用边缘触发当通知的时候必须处理完该IO流(试想一下如果你第上一个通知没有处理完,下一个通知的时候也可以处理,依次递推可能造成最后一个通知没有处理完,因为没有下一个通知哒,所以漏处理了一些IO流)
总结来说就是2个方面。1.recv(maxsize)一有数据就返回并不是积累到maxsize长度再返回。2.判断传输完成不是TCP层面做的事情,应该在应用层处理
其他的一些
keepalive
tcp层面的:是表示当没有tcp报文的时候发送tcp报文给对方。实际上TCP协议规范是只有2小时没有tcp交互才会关闭TCP连接的,可是现实中各种NAT设备并没有遵循该规范,毕竟和性能有关联,如果长时间没有tcp包交互那么可能会中断该TCP连接,此时就有了tcp层面的keepalive,当没有tcp包的时候会自动发送。你以为这样就完了,too naive!有的NAT设备会当tcp中长时间没有有效荷载的时候中断该连接。于是乎有的应用在应用层每隔一段时间发送一个echo数据这样子就能够避免这种情况了。下图设置了keepalive同时对socket设置超时的情况

def set_keepalive_linux(sock, after_idle_sec=1, interval_sec=3, max_fails=5):
"""Set TCP keepalive on an open socket.

It activates after 1 second (after_idle_sec) of idleness,
then sends a keepalive ping once every 3 seconds (interval_sec),
and closes the connection after 5 failed ping (max_fails), or 15 seconds
"""
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)

s.settimeout(10)
set_keepalive_osx(s)

下图是抓包结果

可以看到数据传输完成后每隔3秒钟发送了一个tcp包。由于同时设置了settimeout,十秒后没有tcp荷载,客户端断开了连接(注意settimeout设置为秒只对阻塞模式有效)
http层面的:这个就简单了,http1.0版本以前是请求→返回模式,请求一次就关闭了。因为tcp建立连接是挺耗时的,于是就有了一次数据来回就并不关闭下一个请求接着用。
超时检测
我觉得一个好的socket程序肯定会有超时机制和断线重连机制。上面的2个示例。测试中途断开网络再次重连接,会一直卡在recv阶段,对于一个死的TCP链接如果程序无法感知一直卡着绝对是无法接受的。如果是阻塞socket的超时我们只需要设置setttimeout就好了。配合应用层的echo也可以实现长连接。如果使用epoll这些那么settimeout就不行了,就需要自己维护状态检查~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容