Python 按行读取 socket 套接字
socket api makefile
文档位置
很好用,但是速度很慢,所以如果程序需要实时性,很难保证
bugs位置
我使用测试脚本,发现 makefile 比 fopen 慢一半
1 2 3 4 5 6
| ''' sock ==> socket 套接字类型 ''' rfile = sock.makefile() print(rfile.readline()) rfile.close()
|
手动处理
1 2 3 4 5 6 7 8 9 10
| ''' sock ==> socket 套接字 '''
last = b'' def readlines(): datas = sock.recv(4096) data_list = (last+data).split("\r\n") last = data_list[-1] return data_list[:-1]
|
fopen 处理
1 2 3 4 5
| rfile = os.fopen(os.dup(sock.fileno()),"rb",4096*2) data = rfile.readline() print(data) sock.close() rfile.close()
|
** 注意 **:最好还是用 os.dup 复制一个文件描述符出来
奇怪的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import socket import os import time sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("0.0.0.0",5000)) sock.listen(1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1)
while True: conn , client_address = sock.accept() print("conn's fd is {}".format(conn.fileno())) rfile = os.fdopen(conn.fileno(),"rb",1024) while True: data = rfile.readline() print(data) conn.close() time.sleep(1) break
|
1 2 3 4 5 6 7 8 9 10 11 12
|
import socket
address = ("192.168.3.20",5000)
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(address)
sock.send(b"test data\r\n")
|
当连接到达时,系统会自动分配一个最小的文件描述符给 socket,比如我电脑上是 4 。当我们读取一行数据之后,关闭 socket,close api 会把 socket 底层的文件描述符直接关闭掉,但是由于 tcp 四次挥手中 TIME_WAIT 状态的存在,所以socket依然存活 2MSL。 TIME_WAIT
状态的套接字主要是用来接收无效的数据包的,不允许读,只允许写,而这段时间内 4 这个文件描述符只允许写,不允许读。
所以我们当我们重新 accept 接受到一个连接之后,系统自动分配一个最小的文件描述符仍然是 4(因为我们调用 close ,关闭了 4),这时我们尝试读取文件描述符 4 ,但是文件描述符目前不可读,所以接下来我们调用 rfile.readline(),读取失败,抛异常 OSERROR
简单地说就是: 系统设置了关闭的套接字所对应的的文件描述符 2MSL 内无法读取
解决方法: 不手动操作 socket 对应的文件描述符
1. 不处理,让系统自动处理失效的 socket。这样断开的 socket 系统会自动回收文件描述符,并且销毁socket。新来的连接,创建 socekt之后,会自动选择一个最小的文件描述符来绑定。这样我们就不会用文件描述符访问到上次失效的 socket 了 2. 使用 shutdown ,shutdown 不会关闭套接字的文件描述符,它只作用于套接字 3. fopen(os.dup(conn.fileno),"rb",1024),dup出来的文件描述符没有读权限的问题,可以正常读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import socket import os import time sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("0.0.0.0",5000)) sock.listen(1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1)
while True: conn , client_address = sock.accept() print("conn's fd is {}".format(conn.fileno())) rfile = os.fdopen(conn.fileno(),"rb",1024) while True: data = rfile.readline() print(data) time.sleep(1) break
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import socket import os import time sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("0.0.0.0",5000)) sock.listen(1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1)
while True: conn , client_address = sock.accept() print("conn's fd is {}".format(conn.fileno())) rfile = os.fdopen(conn.fileno(),"rb",1024) while True: data = rfile.readline() print(data) shutdown(conn.fileno(),socket.SHUT_RDWR) time.sleep(1) break
|
但本质是一样的,都是不可以直接操作 socket 对应 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import socket import os import time sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1) sock.bind(("0.0.0.0",5000)) sock.listen(1)
while True: conn , client_address = sock.accept() print("conn's fd is {}".format(conn.fileno())) copyfd = os.dup(conn.fileno()) print("dup's fd is {}".format(copyfd)) rfile = os.fdopen(copyfd,"rb",1024) while True: data = rfile.readline() conn.close() rfile.close() print(data) time.sleep(1) break
|