IO模型

 这里比较难理解:参考http://www.cnblogs.com/Eva-J/articles/8324837.html

同步:提交一个任务之后,要等待这个任务执行完毕
异步:只管提交任务,不等待这个任务执行完毕就可以做其他事情
阻塞:recv,recvfrom,accept,程序停了。一个程序从运行状态——阻塞状态——就绪后还需要重新排队,等待操作系统的调用
非阻塞:

 再说一下IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段:

#1)等待数据准备 (Waiting for the data to be ready)
#2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。

    而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

可以这样理解:陷入阻塞就相当于献出了CPU的运行权力。

非阻塞模型

sever端

import socket
sk=socket.socket()
sk.setblocking(False)#这里默认是Ture,也就是说允许存在阻塞,那么设置为fasle就表示不存在阻塞
sk.bind(('127.0.0.1',8080))
sk.listen()

while 1:
    conn,addr=sk.accept()

clent端

import socket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
sk.send(b'hello')

sk.close()

但是此时sever端会报错,因为sever端此时不允许阻塞,但是sever端连接不上client端只能阻塞,所以就会报错

Traceback (most recent call last):
  File "F:/python/python学习/人工智能/第一阶段day2/sever.py", line 8, in <module>
    conn,addr=sk.accept()
  File "D:\anoconda\lib\socket.py", line 212, in accept
    fd, addr = self._accept()
BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。

Process finished with exit code 1

那么就需要采用try方法

sever

import socket
sk=socket.socket()
sk.setblocking(False)#这里默认是Ture,也就是说允许存在阻塞,那么设置为fasle就表示不存在阻塞
sk.bind(('127.0.0.1',8080))
sk.listen()

while 1:
    try:
        conn,addr=sk.accept()
        print('建立连接')
        print(conn.recv(1024))
    except BlockingIOError:
        pass

client

import socket
import time
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
sk.send(b'hello')
time.sleep(1)
sk.send(b'bye')
sk.close()

此时结果是

建立连接
b'hello'

Process finished with exit code -1

只能接收到第一个send这是因为当接收第一个send后,client睡眠了1秒,此时这1秒内,sever端接收不到消息,就判断为阻塞,但是由于阻塞为false,那么就执行except条件,然后等到执行到第二个send的时候,sever端无法和client端再建立连接,所以无法接收到第二个send的信息。那么:

最终结果

sever

import socket
sk=socket.socket()
sk.setblocking(False)#这里默认是Ture,也就是说允许存在阻塞,那么设置为fasle就表示不存在阻塞
sk.bind(('127.0.0.1',8080))
sk.listen()
l1=[]#来保存来所有来请求sever端的conn连接
del_l=[]#来保存与sever端断开的conn连接
while 1:
    try:
        conn,addr=sk.accept()
        print('建立连接:',addr)
        l1.append(conn)#利用列表将接收到没有执行的conn都先收集起来,
    except BlockingIOError:
            for con in l1:#放到下面这里执行
                try:
                    msg = con.recv(1024)
                    if msg==b'':
                        del_l.append(con)
                        continue
                    else:
                        print(msg)
                        con.send(b'bye')
                except BlockingIOError:pass
            for con in del_l:
                con.close()
                l1.remove(con)
            del_l.clear()

 

client

import socket
import time
from threading import Thread

def talk():
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    sk.send(b'hello')
    time.sleep(1)
    print(sk.recv(1024))
    sk.close()

for i in range(20):
    Thread(target=talk).start()

 注意上面的代码以后会很常用,因为在未来在使用异步的过程中是不可能出现阻塞的,所以需要多练习。

但是注意上面的代码中使用了while循环,但是while循环非常占用内存资源,所以看下面的方法。

多路复用IO(IO multiplexing)

原理:

第一步

sever

import socket
import select
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.setblocking(False)
sk.listen()
#这里一定要树立一个观念就是,sk监听的是连接,而conn监听的是收发信息
read_list=[sk]
r_list,w_list,x_list=select.select(read_list,[],[])
for i in r_list:
    conn,addr=i.accept()
    print(conn)
print(sk)

client

import socket
import time
from threading import Thread

def talk():
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    sk.send(b'hello')
    time.sleep(1)
    print(sk.recv(1024))
    sk.close()

for i in range(20):
    Thread(target=talk).start()

sever端结果

<socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 64695)>#这就是conn
<socket.socket fd=464, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>

进阶

sever

import socket
import select
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.setblocking(False)
sk.listen()
#这里一定要树立一个观念就是,sk监听的是连接,而conn监听的是收发信息
read_list=[sk]
while 1:
    r_list,w_list,x_list=select.select(read_list,[],[])
    for i in r_list:
        if i is sk:
            conn,addr=i.accept()
            read_list.append(conn)
        else:
            msg=i.recv(1024)
            if msg==b'':#自己在运行过程没有出现b'',但是没有这行代码的话,i.send(b'bye')无法正常运行
                i.close()
                read_list.remove(i)
                continue
            print(msg)
            i.send(b'bye')

client

import socket
import time
from threading import Thread

def talk():
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    sk.send(b'hello')
    time.sleep(1)
    print(sk.recv(1024))
    sk.close()

for i in range(20):
    Thread(target=talk).start()

 

IO多路复用,这是windows内部的select机制完成的。select实际上是在操作系统的帮助下循环这个列表,查看每一项是否有可读事件。
注意:select机制在widows系统和linux系统上都有;但是linux系统上还有pool和epool(mac系统)
select:
pool:可以监听的对象比select机制下要多
以上两种方式,都是操作系统轮询(类似于for循环)每一个被监听的项,看是否有读操作;但是随着监听项的增加,导致效率降低
epool:相当于在每一个监听的项上绑定了一个回调函数,那么一旦该项接收到信息,回调函数会立即反馈给用户,但是这个功能windows上没有

posted @ 2019-04-19 00:50  舒畅123  阅读(110)  评论(0编辑  收藏  举报