最近要直接用Socket做一個簡單的Server,想使用non-blocking的Scoket,但是遇到一些問題硼被,解決了所以在這里總結(jié)一下惶岭。
簡單的Server端代碼片段(只有接受數(shù)據(jù)的):
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import ssl
import select
import socket
DEFAULT_SERVER_HOST = "0.0.0.0"
DEFAULT_SERVER_PORT = 14443
class Server(object):
def __init__(self, host, port, is_ssl=False, cert_file=None, key_file=None):
self.host = host
self.port = port
self.is_ssl = is_ssl
self.cert_file = cert_file
self.key_file = key_file
self.context = None
self.__socket = None
self.running = False
self.multiplex = None
self.read_set = set()
self.write_set = set()
self.error_set = set()
def __initialize(self):
if self.is_ssl and (self.cert_file is None or self.key_file is None):
raise Exception("If you want to enable ssl, please set cert_file and key_file")
if self.is_ssl:
self.context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
self.context.load_cert_chain(certfile=self.cert_file, keyfile=self.key_file)
self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.__socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT & socket.SO_REUSEADDR, 1)
self.__socket.bind((self.host if self.host is not None else DEFAULT_SERVER_HOST,
self.port if self.port is not None else DEFAULT_SERVER_PORT))
self.__socket.setblocking(0)
self.__socket.listen(5)
def start(self):
self.__initialize()
server_fd = self.__socket.fileno()
self.read_set.add(server_fd)
while True:
read_list, write_list, error_list = select.select(self.read_set, self.write_set, self.error_set, 2)
if server_fd in read_list:
conn, addr = self.__socket.accept()
conn.setblocking(0)
if self.is_ssl:
conn = self.context.wrap_socket(conn, server_side=True, do_handshake_on_connect=False)
i = 0
while True:
i += 1
print(i)
try:
conn.do_handshake()
select.select([conn], [], [])
break
except ssl.SSLError as err:
if err.args[0] == ssl.SSL_ERROR_WANT_READ:
print("read")
select.select([conn], [], [])
elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
print("write")
select.select([], [conn], [])
else:
raise
# rfile = conn.makefile("rb")
# a = rfile.read(1024*8)
a = conn.recv(1024*8)
print(a)
if __name__ == "__main__":
cs = Server("0.0.0.0", 14443, True, "snakeoil.crt", "snakeoil.key")
cs.start()
由于self.__socket.setblocking(0)
conn.setblocking(0)
都設(shè)置為非阻塞寿弱,所以conn = self.context.wrap_socket(conn, server_side=True, do_handshake_on_connect=False)
不能設(shè)置為連接時自動握手犯眠。
在成功握手后按灶,發(fā)現(xiàn)一個問題,調(diào)用
rfile = conn.makefile("rb")
a = rfile.read(1024*8)
如果讀取范圍較大筐咧,會出現(xiàn)
Traceback (most recent call last):
File "/home/ming/Application/pycharm-2017.1.4/helpers/pydev/pydevd.py", line 1591, in <module>
globals = debugger.run(setup['file'], None, None, is_module)
File "/home/ming/Application/pycharm-2017.1.4/helpers/pydev/pydevd.py", line 1018, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "/home/ming/Application/pycharm-2017.1.4/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/home/ming/PycharmProjects/Test/server.py", line 86, in <module>
cs.start()
File "/home/ming/PycharmProjects/Test/server.py", line 80, in start
a = rfile.read(1024*8)
File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/socket.py", line 576, in readinto
return self._sock.recv_into(b)
File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/ssl.py", line 937, in recv_into
return self.read(nbytes, buffer)
File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/ssl.py", line 799, in read
return self._sslobj.read(len, buffer)
File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/ssl.py", line 583, in read
v = self._sslobj.read(len, buffer)
ssl.SSLWantReadError: The operation did not complete (read) (_ssl.c:2090)
這個錯誤鸯旁,表示讀未完成。
而使用a = conn.recv(1024*8)
則不會發(fā)生錯誤量蕊。
發(fā)現(xiàn)使用makefile()
后的讀操作铺罢,將會多次調(diào)用ssl.py
的SSLSocket.class
的recv_into
方法,最后到ssl.py
的SSLObject
def read(self, len=1024, buffer=None):
"""Read up to 'len' bytes from the SSL object and return them.
If 'buffer' is provided, read into this buffer and return the number of
bytes read.
"""
if buffer is not None:
v = self._sslobj.read(len, buffer) //makefile 后執(zhí)行這句
else:
v = self._sslobj.read(len)
return v
残炮,直至錯誤出現(xiàn)韭赘。
而直接使用socket
的read
方法,則是直接調(diào)用ssl.py
的SSLObject
的
def read(self, len=1024, buffer=None):
"""Read up to 'len' bytes from the SSL object and return them.
If 'buffer' is provided, read into this buffer and return the number of
bytes read.
"""
if buffer is not None:
v = self._sslobj.read(len, buffer)
else:
v = self._sslobj.read(len) // socket 的read執(zhí)行這句
return v
更深入的原因還沒找出势就,目前覺得應(yīng)該是makefile后把socket當(dāng)成文件讀取泉瞻,會嘗試讀直至無法繼續(xù)讀取,所以才會導(dǎo)致錯誤發(fā)生苞冯。