回顾原生Socket
一、Socket起源:
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。
socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
“他是所有WEB服务器的祖宗”
pupepet、ansible、他们也可以通过输入命令然后返回结果这个也是基于Socket来实现的。
二、socket和file的区别:
file模块是针对某个指定文件进行【打开】【读写】【关闭】
socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
三、原生Socket增强:
过程:
第一请求发发了一个操作,server端返回了,那么现在两头等在等待这输入。
那么这段时间第二个请求还在等待!现在服务端是不是在空闲着呢?他只占着I/O资源,CPU是不是空闲着呢?他阻塞着后面的请求无法进来。
不急继续往下看!
网络IO模型:阻塞IO和非阻塞IO|同步IO和异步IO
介绍:
网络I/O模型讨论的背景是Linux环境下的network IO。本文最重要的参考文献是Richard Stevens的“UNIX? Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”,Stevens在这节中详细说明了各种IO的特点和区别,如果英文够好的话,推荐直接阅读。Stevens的文风是有名的深入浅出,所以不用担心看不 懂。
一、什么是I/O
1、先了解什么是I/O:I/O(input/output),即输入/输出端口。每个设备都会有一个专用的I/O地址,用来处理自己的输入输出信息。
2、I/O model:阻塞:blocking IO、非阻塞:non-blocking IO、同步:synchronous IO 、 异步:asynchronous IO 之间的区别
3、IO发生时涉及的对象和步骤:以输入操作的socket为例:第一步:首先等待网络数据到达,当数据接收就会复制到内核缓冲区中,第二步:复制从内核缓冲区到应用缓冲区
- 等待数据准备 (Waiting for the data to be ready)
- 将数据从内核拷贝到进程中(Copying the data from the kernel to the process) 记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。
二、Blocking I/O Model
默认情况下所有的Socket是阻塞(分享例子----分享完后需要删除本括号内容),看下面的图例:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如没有收到一个完整的TCP/UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block的状态,重新运行起来。
所以阻塞:blocking IO的特点是I/O执行时的两个操作(等待数据准备 (Waiting for the data to be ready)、将数据从内核拷贝到进程中(Copying the data from the kernel to the process))都是阻塞的。
python socket中:accept() recv() 是阻塞的
所以,所谓阻塞型接口是指系统调用(一般是IO接口)如果不返回结果就一直阻塞,就是socket经常说的,有发就有收收发必相等如果两边都在同时收,是不是阻塞着后面的代码就无法执行?
那既然原生的Socket是阻塞的,那有什么办法来解决呢?
使用多线程(或多进程)、多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
我们假设对上述的服务器 / 客户机模型,提出更高的要求,即让服务器同时为多个客户机提供一问一答的服务。于是有了如下的模型。
在上述的线程 / 时间图例中,主线程持续等待客户端的连接请求,如果有连接,则创建新线程,并在新线程中提供为前例同样的问答服务。
很多初学者可能不明白为何一个socket可以accept多次。实际上socket的设计者可能特意为多客户机的情况留下了伏笔,让accept()能够返回一个新的socket。
执行完bind()和listen()后,操作系统已经开始在指定的端口处监听所有的连接请求,如果有请求,则将该连接请求加入请求队列。
调用accept()接口正是从的请求队列抽取第一个连接信息,创建一个新的socket返回句柄。新的socket句柄即是后续read()和recv()的输入参数。如果请求队列当前没有请求,则accept()将进入阻塞状态直到有请求进入队列。
上述多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。
很多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。
这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等。但是,“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。
三、非阻塞:non-blocking IO
(分享例子----分享完后需要删除本括号内容)
#!/usr/bin/env python #-*- coding:utf-8 -*- import time import socket #创建socket对象 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt #设置监听的IP与端口 sk.bind(('127.0.0.1',6666)) #设置client最大等待连接数 sk.listen(5) sk.setblocking(False) #这里设置setblocking为Falseaccept将不在阻塞,但是如果没有收到请求就会报错 while True: #循环 try: print 'waiting client connection .......' #connection代表客户端对象,address是客户端的IP connection,address = sk.accept() #等待接收客户端信息 client_messge = connection.recv(1024) #打印客户端信息 print address #发送回执信息给client 收发必须相同 connection.sendall('hello Client this server') connection.send() #关闭和client的连接 connection.close() except Exception as e: print e time.sleep(4)
看上面的代码,我修改了setblocking的值,那么现在accept()将不再阻塞。所以他类似下面的图:
EWOULDBLOCK 意思是说:该操作可能会被阻塞。E是error,WOULD BLOCK是可能会被阻塞的意思。
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从 用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次 发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。python中的 sk.setblocking(False) accept() 将不会阻塞
四、多路复用IO(IO multiplexing)
IO multiplexing这个词可能有点陌生,但是如果我说select/epoll,大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select /epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。就通知用户进程。它的流程如图:
Windows Python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll
注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。
普通文件操作所有系统都是完成不了的,普通文件是属于I/O操作!但是对于python来说文件变更python是监控不了的,所以我们能用的只有是“终端的输入输出,Socket的输入输出”
对于Select:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间) 参数: 可接受四个参数(前三个必须) 返回值:三个列表 select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。 1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中 2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中 3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中 4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化 5、当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
利用select监听终端操作实例
#!/usr/bin/env python # -*- coding:utf-8 -*- import select import sys while True: readable, writeable, error = select.select([sys.stdin,],[],[],1) '''select.select([sys.stdin,],[],[],1)用到I/O多路复用,第一个参数是列表,我放进去的是stdin就是我输入进去东西的描述符, 相当于打开一个文件,和obj = socket(),类似的文件描述符, sys.stdin 他只是一个特殊的文件描述符= 终端的输入,一旦你输入OK select I/O多路复用他就感知到了。 先看readable这个参数,其他的县不用看一旦你发生了我就他他发到readable里了, 这里添加的就是修改的那个文件描述符,如果你一直没有修改过,那么readable他就是一个空的列表 ''' if sys.stdin in readable: print 'select get stdin',sys.stdin.readline() ''' 注: 1、[sys.stdin,] 以后不管是列表还是元组在最后的元素后面建议增加一个逗号,那元组举例(1,) | (1) 这两个有区别吗?是不是第二个 更像方法的调用或者函数的调用,加个,是不是更容易分清楚。还有就是在以后写django的配置文件的时候,他是必须要加的。写作习惯 2、select第一个参数他就是监听多个文件句柄,当谁改变了我是不是就可以监听到! 3、select参数里1是超时时间,当到slect那一行后,如果这里还是没有输入,那么我就继续走! ''' ''' when runing the program get error : Traceback (most recent call last): File "E:/study/GitHub/homework/tianshuai/share_3_select_socket.py", line 8, in <module> readable, writeable, error = select.select([sys.stdin,],[],[],1) select.error: (10093, 'Either the application has not called WSAStartup, or WSAStartup failed') when windows only use select socket !!!!! '''
利用select监听终端操作实例
#/usr/bin/env python #-*- coding:utf-8 -*- import time import socket import select #创建socket对象 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt #设置监听的IP与端口 sk.bind(('127.0.0.1',6666)) #设置client最大等待连接数 sk.listen(5) sk.setblocking(False) #这里设置setblocking为Falseaccept将不在阻塞,但是如果没有收到请求就会报错 while True: readable_list, writeable_list, error_list = select.select([sk,],[],[],2) #监听第一个列表的文件描述符,如果里面有文件描述符发生改变既能捕获并放到readable_list中 for r in readable_list: #如果是空列表将不执行,如果是空列表。将执行。 conn,addr = r.accept() print addr