前言
这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题
于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。
非阻塞套接字
在阻塞模式中,我们会一直等待客户端成功连接到或者消息成功接收到
而在非阻塞模式下,我们不会去等到一个客户端连接就可以继续代码里面下面的语句
但是也可能会因为我们非阻塞直接向下运行程序:TCP模型下可能因为客户端还没有连接到就去获取对应数据;UDP模型则会将直接就去接收别人发来的消息,并认为消息有效。但是可能现在没有任何一个人发送消息。这些情况都会导致我们的程序抛出异常。所以需要我们对非阻塞套接字进行合理的异常捕获处理
s.setblocking(1)
使用s.setblocking(0)将套接字设置为非阻塞模式
非阻塞模式下,常出现的错误是BlockingIOError
,只需要进行简单的异常捕获即可
…
try:
c,addr = s.accept() # 阻塞等待客户端连接
except BlockingIOError as e:
pass
else:
c.setblocking(0) # 设置客户端套接字非阻塞模式
while True: # 循环处理客户端事务
…
端口复用
在关闭了服务端程序之后又在短时间内继续开启,那么代码可能会抛出如下异常
Traceback (most recent call last):
File "tcpServer.py", line 4, in <module>
s.bind( ('',8080) ) # 绑定IP端口
OSError: [Errno 98] Address already in use
因为没有显示的关闭套接字,那么短时间内使用这个端口,会因为端口处于释放状态或者还在为上一个程序工作而抛出Address already in use
这样的错误
而为套接字设置端口复用,是为了让程序重启时,之前占用的端口可以立即被绑定使用,而不是出现地址被使用而绑定失败,那只能等待一会儿,才能使用它(一般需要花费两分多钟)
设置了套接字端口复用之后,我们可以立即使用它,会明显缩短等待时间,下一次这个端口的套接字不需要等待TIME_WAI断开时间也可以立即使用
同时设置套接字端口复用,还有一个重要的意义在于它在下一次使用时将上一次程序所残留的TCP数据包清除,这样也避免了下一次绑定了相同地址而获取到之前的残留数据,而导致发生不可预估的错误
- 端口复用主要目的
- 保证TCP连接可靠关闭
- 为每次使用清除残留TCP数据
- 缩小套接字地址使用等待时间
- 设置端口复用
import socket # 导入必须模块socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建套接字
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 设置端口复用
非阻塞io模型
TCP/IP模型下,普通的通信模型(I/O模型)只能同时监听处理一个socket,其他用户的连接只能默默等待着,这离高并发的网络服务器还遥遥无期
可能我们会想到开启多进程来实现高并发,当有连接用户到达时新开起进程去单独处理,但是问题也随之而来,如果我有成千上万的TCP连接,是要创建成千上万的进程出来,也不太现实
其实这里想一下,我们之前一直是单独的为某一个客户端服务;那么如果现在把连接到的所有的客户端先保存起来,在轮询的挨个一次次去处理,而不是单独的处理完一个之后才能处理下一个;好比一个澡堂,只有一个服务员,迎宾和大厅里的事情只有一个人来做,那么我可以先在门口观察是否有顾客要进来,如果有,则让顾客先进来大厅坐着;没有的话,也回到大厅里,去继续从第一个顾客一直询问到最后一个,来处理他们的需求,处理完成之后再回到门口去欢迎下一个客人,如此循环往复
实际操作起来,像是这样,首先我们将服务端套接字准备为非阻塞,如果没有客户端连接也不要阻塞一直等待下去;如果有客户端连接到了之后,将他保存在一个数据集中,之后轮询去获取每一个客户端的消息,那么这里就同样的也有需要把客户端连接套接字也设置为非阻塞的
- 自实现io复用模型
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setblocking(0) # 设置服务端套接字为非阻塞模型
c_ = [] # 所有连接到的客户端套接字保存列表
while True:
try:
c,c_addr = s.accept() #非阻塞接收其他客户端连接
except : #应对非阻塞模型下可能会因为没有客户端连接而导致错误
pass
else:
c_.setblocking(0) # 设置来访客户端套接字为非阻塞
c_.append(c) # 如果正常连接来访,保存到序列中
for client in c_: # 轮询处理已经连接到的客户端套接字
try: #轮询查看是否有客户端发来消息或者需要发送消息
client.recv()…
client.send()…
except:
pass
- 实际服务器模型代码
import socket
import copy
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #设置端口复用
server.bind( ('',23333) )
server.listen(5)
c_ = {} #保存所有连接客户端套接字
# key: client
# value: c_addr
server.setblocking(0) #设置服务端套接字为非阻塞模式,避免在accept时阻塞
print('[+] server open')
while True:
c_bak = copy.copy(c_) #Python中是无法直接将字典在for循环中动态删除的!
try:
try:
client,c_addr = server.accept()
except BlockingIOError:
pass
else:
client.setblocking(0) #设置套接字属性为非阻塞
print('[+] from:',c_addr)
c_[client] = c_addr #接收到了连接来访之后,立马放入保存数据集中
for c in c_bak: #遍历
try:
data = c.recv(1024).decode('utf-8') #非阻塞
except BlockingIOError:
continue
else:
if not data:
print('[%s] closed.' % (c_bak[c],) )
c.close() #关闭套接字
del c_[c] #在数据集中删除该套接字
else:
print('[%s]:%s' % (c_bak[c],data) )
msg = '自动回复'.encode("utf-8") #每一个客户端的消息都自动返回
c.send(msg)
except KeyboardInterrupt:
break
server.close() #关闭服务端套接字
在实现的代码中们使用了字典做为保存已了解TCP客户端套接字的数据类型
字典的key值为每一个唯一连接到的用户,value值为客户端套接字地址
当每一次通过服务端套接字获取到了客户端的连接之后,我们立即将客户端套接字设置为非阻塞并和客户端地址一起保存在字典中。
之后通过for循环迭代访问保存每一个连接的客户端套接字的字典
如果出现因非阻塞而报的错误,可能是因为没有数据,或者已经断开链接,那么直接跳过该套接字,或者将该套接字删除即可