Anaconda下安装Talib库[转]

1、 如果你的系统是x64平台,直接使用命令pip install talib安装,会报错。 原因在于python pip源中TA-Lib是32位的,不能安装在X64平台上, 而从TA-Lib的官网 http://ta-lib.org 下载的安装包其实也是32位的,如果你的系统平台是64位的,一样无法正确安装。

正确的方法是下载64位的安装包,本地安装,64位安装包官网并没有提供,必须自行下载。

talib库有很多第三方库,安装的时候尽量找靠谱的源。

加州大学的python库:Archived: Python Extension Packages for Windows – Christoph Gohlke (uci.edu)

按ctrl+F键 页面搜索 Ta-lib 即可找到

2、 注意要下载自己python版本所对应的文件, 所以选择 TA_Lib-0.4.19-cp38-cp38-win_amd64.whl

否则后续安装又会报错 “ERROR: TA_Lib-0.4.19-cp39-cp39-win_amd64.whl is not a supported wheel on this platform.”</p>

把TA_Lib-0.4.19-cp38-cp38-win_amd64.whl 放在Anaconda工作文件夹下面, 然后直接敲命令 pip install TA_Lib-0.4.19-cp38-cp38-win_amd64.whl

你会发现成功安装~

Anaconda报错:ImportError: DLL load failed while importing _ssl: 找不到指定的程序。

Win10系统安装了Anaconda3-2022.05-Windows-x86_64.exe版本,并把D:\Anaconda3;D:\Anaconda3\Scripts;D:\Anaconda3\Library\bin;D:\Anaconda3\Library\mingw-w64\bin加入到环境变量中后运行jupyter notebook命令,仍报了:ImportError: DLL load failed while importing _ssl: 找不到指定的程序。这样的错误。

一番折腾之后,总算在Stack Overflow上找到解决方法:

从目录 anaconda3\Library\bin 拷贝下面的文件到 anaconda3/DLLs中。

  • libcrypto-1_1-x64.dll
  • libssl-1_1-x64.dll

再次运行jupyter notebook命令,成功打开网页。

Jupyter介绍[转]

Jupyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言。

Jupyter Notebook 的本质是一个 Web 应用程序,便于创建和共享程序文档,支持实时代码,数学方程,可视化和 markdown。 用途包括:数据清理和转换,数值模拟,统计建模,机器学习等等 [1]  。

用户可以通过电子邮件,Dropbox,GitHub 和 Jupyter Notebook Viewer,将 Jupyter Notebook 分享给其他人。

在Jupyter Notebook 中,代码可以实时地生成图像,视频,LaTeX和JavaScript。

组件

Jupyter包含以下组件:

Jupyter Notebook 和 Notebook 文件格式

Jupyter Qt 控制台

内核消息协议 (kernel messaging protocol)

许多其他组件

下载安装

一种方式是通过 pip 命令安装。

pip install jupyter

复制

另一种方式是使用集成科学计算环境Anaconda。推荐使用这种方式,下载过程就不说了。根据自己的系统环境,选择对应的版本进行下载。安装基本上也就是一路next,也没有什么要说的。

pip install jupyter

注:Anaconda是Python另一个非常流行的发行版,它之后有着自己的叫做“conda”的安装工具。用户可以使用它来安装很多第三方包。然而,Anaconda会预装很多包,包括了Jupyter Notebook,所以若已经安装了Anaconda,则Jupyter已经自动安装完毕。

启动Jupyter Notebook

使用 Anaconda 安装成功后,默认会将 Jupyter Notebook的启动程序添加到环境变量中。启动程序为jupyter-notebook。可以进入命令行,然后使用 jupyter-notebook --h 来查看使用说明。

(c:\soft\py3) C:\Users\wangwei01>jupyter notebook --h
usage: jupyter-notebook [-h] [--pylab [NOTEBOOKAPP.PYLAB]]
                        [--keyfile NOTEBOOKAPP.KEYFILE]
                        [--client-ca NOTEBOOKAPP.CLIENT_CA]
                        [--notebook-dir NOTEBOOKAPP.NOTEBOOK_DIR]
                        [--port-retries NOTEBOOKAPP.PORT_RETRIES]
                        [--config NOTEBOOKAPP.CONFIG_FILE]
                        [--certfile NOTEBOOKAPP.CERTFILE]
                        [--log-level NOTEBOOKAPP.LOG_LEVEL]
                        [--browser NOTEBOOKAPP.BROWSER]
                        [--transport KERNELMANAGER.TRANSPORT]
                        [--ip NOTEBOOKAPP.IP] [--port NOTEBOOKAPP.PORT] [-y]
                        [--script] [--no-mathjax] [--no-browser]
                        [--allow-root] [--debug] [--no-script]
                        [--generate-config]

optional arguments:
  -h, --help            show this help message and exit
  --pylab [NOTEBOOKAPP.PYLAB]
  --keyfile NOTEBOOKAPP.KEYFILE
  --client-ca NOTEBOOKAPP.CLIENT_CA
  --notebook-dir NOTEBOOKAPP.NOTEBOOK_DIR
  --port-retries NOTEBOOKAPP.PORT_RETRIES
  --config NOTEBOOKAPP.CONFIG_FILE
  --certfile NOTEBOOKAPP.CERTFILE
  --log-level NOTEBOOKAPP.LOG_LEVEL
  --browser NOTEBOOKAPP.BROWSER
  --transport KERNELMANAGER.TRANSPORT
  --ip NOTEBOOKAPP.IP
  --port NOTEBOOKAPP.PORT
  -y, --y
  --script
  --no-mathjax
  --no-browser
  --allow-root
  --debug
  --no-script
  --generate-config

启动前需要先说明一个概念,jupyter notebook中有个叫做工作空间(工作目录)的概念,也就是说如果你想在哪个目录进行之后的工作,那就在哪个目录启动它。例如,这里我想将家目录的的jp_workspace目录作为工作空间,那我就需要进入到这个目录下。

在此文件夹下打开终端,并执行命令:

jupyter notebook

这将开启Jupyter,并使用你的默认浏览器访问URL:http://localhost:8888/tree

像这样:

注意:现在并没有运行一个Notebook,而是仅仅运行了Notebook服务器。

创建一个Notebook

点击“New”按钮,打开菜单选择环境。
这里我的电脑里安装了Python3,因此选择Python3。

命名

点击上方的”Untitled”,然后键入想要的名称。

这里改为demo。

运行Cells

在cell中输入代码后,可以点击

或者可以使用键盘shift + enter运行代码。

注意:当一个页面中有多个单元格时,按顺序运行它们,它们之间则可以共享方法与变量。无需在单元格中重新导入模块和变量。

当运行单元格时,在单元格左侧的单词In旁边有一些方括号。方括号将自动填充一个数字,该数字表示将运行单元格的顺序。例如,如果我们打开一个全新的Notebook并运行该Notebook顶部的第一个单元格,则方括号将填充数字1。

File

File 菜单中主要包含了以下功能:创建新的Notebook、打开新的界面、拷贝当前Notebook、重命名Notebook、保存还原点、恢复到指定还原点、查看Notebook预览、下载Notebook、关闭Notebook。

这里重点强调下下载Notebook选项,它可以将当前Notebook转为py文件、html文件、markdown文件、rest文件、latex文件、pdf文件。

保存检查点(Ctrl + s) 这个功能较特殊且有用,它可以保存以后想用来回滚的检查点,并在需要的时候Revert to Checkpoint。

Edit
接下来是编辑菜单。在这里可以剪切、复制和粘贴单元格。这里也可以删除、分割或合并一个单元。并且也可以在这里重新排序单元格。

选中单元格时,左侧竖条呈蓝色,这时可以对单元格进行操作。
点按”x”剪切单元格。
点按”c”复制单元格。
点按”v”粘贴单元格。
双击”d,d”删除单元格。
选中单元格中的文本框内容时,左侧竖条呈绿色,这时可以对文本进行操作。
使用”Ctrl + 上述单个字符”实现对文本的操作。

注意:我们会发现Edit中有一些条目选项是灰色的,不可点击。这是因为在当前类型的单元格中不可使用这些功能。例如,代码单元格中不能够插入图片,但Markdown单元格中可以。

View
View栏可以开关顶栏和工具栏,还可以开关代码行号(Ctrl + L)。

Insert
Insert栏可以在当前单元格的上方和下方插入单元格。(A / B)

Cell
Cell 菜单主要包含了运行cells、运行cells后并在之后插入新的cell、运行所有cells、运行当前之上的所有cell、运行当前之下的所有cell、改变cell类型(code、markdown、raw nbconvert)等。

Kernel
内核单元用于处理在后台运行的内核。在这里,我们可以重新启动内核,重新连接它,关闭它,甚至更改您的Notebook使用的内核。
虽然我们可能不会经常使用内核,但是在调试Notebook时,可能需要重新启动内核。当这种情况发生时,就会去这里操作,也可使用toolbar的循环箭头重启内核。

Widgets
Widgets菜单用于保存和清除widget状态。小部件基本上是JavaScript小部件,我们可以将其添加到单元格中,以使用Python(或其他内核)生成动态内容。

Help
可以从中查看键盘快捷键,使用界面教程和许多相关工具。

熟悉工具栏

工具栏上的内容都在下图中:

依次来介绍下,保存还原点、在当前位置之下添加cell、剪切当前cell、拷贝选择的cell、复制选择的cell、上移选中的cell、下移选中的cell、运行cell、中断kernel、重启kernel、修改cell类型、打开命令行调色板、美化cell代码。

很明显,工具栏中的功能大多都是菜单栏中的一部分功能的体现,主要是为了方便寻找。

运行Python代码

想要运行Python代码,其实很简单,因为Python代码最后都在 Cell 中编写的。首先在cell中编写好Python代码,然后点击运行,可以直接在下面看到结果。

仔细一点,我们可以发现,第一个cell前面有 “In [1]:”提示符,第二个cell前面有“In[2]:”提示符,同时也有“Out[2]:”输出符,这是因为如果没有print语句的话,Notebook会将当前cell的最后一条语句的结果以“Out[?]:”的方式输出。

编写Markdown

Notebook最友好的一个功能就是可以在cell中通过Markdown来编写文本。我们首先创建一个cell,然后更改类型为markdown,更改成功后,cell开头没有“In[?]:”的提示符。然后点击cell,按照markdown语法来输入文本。

除了对markdown语法的支持外,同时也支持html代码,此外,也支持latex公式语法。

使用latex公式时,如果需要在当前行内插入公式,公式前后分别需要一个$,如果需要在下一行单独插入公式,公式前后分别需要两个$。

在markdown类型的cell中输入以下内容:

## 这是一个二级标题

这是一个markdown类型的cell

- 下面演示了对html代码的支持:

  <a href="www.naodongopen">这是使用html的a标签实现的超链接</a>


- 下面演示的对latex公式的支持:

  当前行内插入公式:$ \int_0^{+\infty} x^2 dx $

  下一行插入公式:$$ \int_0^{+\infty} x^2 dx $$

运行该cell,得到以下结果:

开启终端或其他

Jupyter Notebook中,除了代码页面外,还可以开启终端,或文本。
终端框实现了在web网页中打开服务器端的操作系统终端。允许你运行bash,Powershell等shell。

查看什么正在运行

在Jupyter服务器的首页(http://localhost:8888/tree)中,有另外两个按钮:Running和Clusters。

Running中会告诉你哪个Notebooks和终端在运行。这对于你想在关闭服务器前确保相应数据被保存的情况将很有用处。

安装插件管理器

第一步:用pip安装插件管理包

pip install jupyter_contrib_nbextensions
pip install jupyter_nbextensions_configurator

第二步:安装一些插件并启用插件管理器

jupyter nbextensions_configurator install --user
jupyter nbextensions_configurator enable --user

然后打开Jupyter notebook会发现菜单栏多了一个选项Nbextensions

记得勾选第一项,否则下方插件是不可选状态。

我们可以通过命令来管理开户中或关闭某个插件,但是我觉得还是通过直接勾选我们需要的插件效率更高。

选择插件

我们从上面可以看出,jupyter notebook有很多插件,我们该用哪一个呢?我推荐5款个人认为不错的插件。

  • Table of Contents
  • Execute Time
  • Nofity
  • Codefolding
  • Hinterland

下面分别介绍一下它们的功能

Table of Contents是一款自动生成目录的工具,它能够通过我们我们富文本中定义的标题自动生成目录,这样我们能够通过点击左侧目录快速定位到我们想要的到达的代码片段。

Execute Time顾名思义,执行时间,我觉得这是一款非常实用的插件,在企业项目开发中,效率是永远无法越过的一个门槛,和学术上理论效果足够优秀即可不同,在企业项目中对效率要求也很高,因此,我们需要统计代码的运行时间,其中最初级的用法就是在每个函数开始和结尾处写一个计时语句,这样比较繁琐。然后再高阶一些的用法就是通过装饰器写一些计时器,在每个函数上调用这个装饰器。其实,如果用jupyter notebook完全没必要这么麻烦。我们只需要打开Execute Time,它就能统计每个cell的运行耗费时间,结束时间等,非常详细,一目了然。

Nofity同样是一款非常实用的插件,当我们运行一个耗时较长的代码时,我们不可能一直盯着屏幕等待,但是我们又希望及时知道它运行结束了,Notify这款插件就可以实现这个功能,它能够在代码运行结束时发出通知,及时告知你代码运行结束了。

Codefolding是一款代码折叠工具,有时候我们写的一个函数非常长,但是我们又不关注 ,这样在阅读过程中会使得效率很低,代码折叠就是一个不错的选择,折叠我们不关注的代码块,Codefolding能够像其他IDE那样让你轻松自如的折叠代码块。

Hinterland是一款自动补全插件,称一个IDE“优秀”,如果没有自动补全显然是说不过去的。jupyter notebook自带补全功能,但是每次都需要点击tab键来补全,这样效率比较低,我们可以通过勾选Hinterland让jupyter notebook具备自动补全功能,当我们输入几个包含字母后它能够快速补全我们想要的函数,补全速度堪比pycharm。

主题修改

很多同学使用jupyter notebook都会觉得,这款开发工具界面太单调了,只有纯白色的主题,其实并不是这样,jupyter notebook也支持主题修改,而且非常方便。

首先在命令行下输入下面命令安装主题,

$ pip install jupyterthemes

jupyter notebook的主题管理工具叫做jt,我们可以通过下面命令查看可用主题,

$ jt -l
Available Themes:
   chesterish
   grade3
   gruvboxd
   gruvboxl
   monokai
   oceans16
   onedork
   solarizedd
   solarizedl

然后通过下面命令设置主题,

$ jt -t <theme_name>

其中theme_name为主题名称。

如果觉得不满意,想退回默认主题,可以通过下方命令实现,

$ jt -r

多语言支持

很多同学是因为Python而解除到jupyter notebook的,因此会认为这就是一款Python专属的开发工具,如果这样的话,那么也不足以我专门用一篇文章来介绍这款开发工具。

它更像是eclipse、IDEA、vscode,是一款综合的开发工具,它不仅支持Python,还支持C++、julia、R、ruby、Go、Scala、C#、Perl、PHP、Octave、Matlab、Lua、Tcl、等多种编程语言,功能十分强大,支持语言详情,请查看下方链接,

https://link.zhihu.com/?target=https%3A//github.com/jupyter/jupyter/wiki/Jupyter-kernels

不同语言的配置方式各不相同,这里不再一一介绍,可以根据自己需要的语言自行在网上搜索相关配置资料进行配置。

jupyter notebook服务

如果非要找出使用jupyter notebook的缺点,我认为就是每次启动的时候相对繁琐,我们启动本地安装的IDE,一个命令或者点击一下图标即可,但是如果启动jupyter notebook就需要进入命令行或终端,输入“jupyter notebook”进行打开,如果使用的是虚拟环境,首先还要激活虚拟环境,这无疑是非常繁琐的,而且启动后它会占用一个终端或命令行窗口,如果意外关闭则会终止jupyter notebook服务。其实,这也是有解决方法的,我们搭建一个持续化的jupyter notebook服务,让它一直在服务器后台运行,这样既不占用窗口,也不需要繁琐的打开步骤,我们只需要把对应的URL收藏,每次需要时能够秒级速度打开,下面就来介绍一下jupyter notebook的搭建步骤。

第一步:获取sha1密码

在命令行下输入ipython,

In [1]: from IPython.lib import passwd
In [2]: passwd()
Enter password:
Verify password:
Out[2]: 'sha1:746cf729d33f:0af9cda409de9791f237a6c46c3c76a3237962fc'

导入passwd函数,调用后会让你输入密码,你可以设置一个明文密码,例如123,然后它会生成一个sha1密码串,这个很重要,后面会用到。

修改jupyter配置文件,linux系统配置文件路径为~/.jupyter/jupyter_notebook_config.py,windows系统配置文件路径为C:\Users\User\.jupyter\jupyter_notebook_config.py,如果没有这个文件,可以使用下面命令生成,

$ jupyter notebook --generate-config

这个配置文件很长,以linux为例,主要关注的是如下几项,

c.NotebookApp.ip = '*'  
c.NotebookApp.password = u'sha1:xxx:xxx' 
c.NotebookApp.open_browser = False 
c.NotebookApp.port = 8888
c.NotebookApp.enable_mathjax = True 

c.NotebookApp.ipc.NotebookApp.port,ip要和服务器保持一致,端口可以自行设定,不和其他端口冲突即可,后续访问时在浏览器输入ip:port即可。

c.NotebookApp.password就是前面生成的sha1密码串,复制过来即可。

c.NotebookApp.open_browser = False 的含义为是每次启动命令时是否打开浏览器,由于我们用的时候直接输入URL即可,所以这里不需要打开浏览器。

c.NotebookApp.enable_mathjax的含义为是否用mathjax,它是一种用于数学公式显示的工具,这里选True。

配置好这几项之后保存退出,输入下面命令即可启动,

$ nohup jupyter notebook > /dev/null 2>&1 &

nohup的含义是后台运行,这样就不用占用一个窗口来了。

配置好之后只要服务器不关机,jupyter notebook的服务会一直处于运行状态,我们随时可以使用,只需要打开ip:port即可。

Jupyter现如今已经成为Python开发中举足轻重的IDE,在数据分析等领域应用非常广泛。

尤其,随着Python逐渐在机器学习、数据分析、可视化、自动化办公等方面越来越广泛的应用,Jupyter会得到更多的重视。

Select详解(select、epoll)(转)

转自:http://blog.csdn.net/songfreeman/article/details/51179213

select函数操作集合的时候有个要求,要么集合本身是描述符,要么他提供一个fileno()接口,返回一个描述符。

 

I/O多路复用是在单线程模式下实现多线程的效果,实现一个多I/O并发的效果。看一个简单socket例子:

import socket  
  
SOCKET_FAMILY = socket.AF_INET  
SOCKET_TYPE = socket.SOCK_STREAM  
  
sockServer = socket.socket(SOCKET_FAMILY, SOCKET_TYPE)  
sockServer.bind(('0.0.0.0', 8888))  
sockServer.listen(5)  
  
while True:  
    cliobj, addr = sockServer.accept()  
    while True:  
        recvdata = cliobj.recv(1024)  
        if recvdata:  
            print(recvdata.decode())  
        else:  
            cliobj.close()  
            break

客户端:

import socket  
  
socCli = socket.socket()  
socCli.connect(('127.0.0.1', 8888))  
while True:  
    data = input("input str:")  
    socCli.send(data.encode())

以上为一个简单的客户端发送一个输入信息给服务端的socket通信的实例,在以上的例子中,服务端是一个单线程、阻塞模式的。如何实现多客户端连接呢,我们可以使用多线程模式,这个当然没有问题。 使用多线程、阻塞socket来处理的话,代码会很直观,但是也会有不少缺陷。它很难确保线程共享资源没有问题。而且这种编程风格的程序在只有一个CPU的电脑上面效率更低。但如果一个用户开启的线程有限的情况下,比如1024个。当第1025个客户端连接是仍然会阻塞。
有没有一种比较好的方式呢,当然有,其一是使用异步socket。

这种socket只有在一些event触发时才会阻塞。相反,程序在异步socket上面执行一个动作,会立即被告知这个动作是否成功。程序会根据这个信 息决定怎么继续下面的操作由于异步socket是非阻塞的,就没有必要再来使用多线程。所有的工作都可以在一个线程中完成。这种单线程模式有它自己的挑 战,但可以成为很多方案不错的选择。它也可以结合多线程一起使用:单线程使用异步socket用于处理服务器的网络部分,多线程可以用来访问其他阻塞资 源,比如数据库。Linux的2.6内核有一系列机制来管理异 步socket,其中3个有对应的Python的API:select、poll和epoll。epoll和pool比select更好,因为 Python程序不需要检查每一个socket感兴趣的event。相反,它可以依赖操作系统来告诉它哪些socket可能有这些event。epoll 比pool更好,因为它不要求操作系统每次都去检查python程序需要的所有socket感兴趣的event。而是Linux在event发生的时候会 跟踪到,并在Python需要的时候返回一个列表。因此epoll对于大量(成千上万)并发socket连接,是更有效率和可扩展的机制
异步I/O处理模型

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

select 原理

1.从用户空间拷贝fd_set到内核空间(fd_set 过大导致占用空间且慢);
2.注册回调函数__pollwait;
3.遍历所有fd,对全部指定设备做一次poll(这里的poll是一个文件操作,它有两个参数,一个是文件fd本身,一个是当设备尚未就绪时调用的回调函数__pollwait,这个函数把设备自己特有的等待队列传给内核,让内核把当前的进程挂载到其中)(遍历数组中所有 fd);
4.当设备就绪时,设备就会唤醒在自己特有等待队列中的【所有】节点,于是当前进程就获取到了完成的信号。poll文件操作返回的是一组标准的掩码,其中的各个位指示当前的不同的就绪状态(全0为没有任何事件触发),根据mask可对fd_set赋值;
5.如果所有设备返回的掩码都没有显示任何的事件触发,就去掉回调函数的函数指针,进入有限时的睡眠状态,再恢复和不断做poll,再作有限时的睡眠,直到其中一个设备有事件触发为止。
只要有事件触发,系统调用返回,将fd_set从内核空间拷贝到用户空间,回到用户态,用户就可以对相关的fd作进一步的读或者写操作了。

epoll 原理

调用epoll_create时,做了以下事情:

内核帮我们在epoll文件系统里建了个file结点;
在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket;
建立一个list链表,用于存储准备就绪的事件。

调用epoll_ctl时,做了以下事情:

把socket放到epoll文件系统里file对象对应的红黑树上;
给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。

调用epoll_wait时,做了以下事情:

观察list链表里有没有数据。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已。

1.优缺点

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1).单个进程可监视的fd数量被限制

2).需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

3).对socket进行扫描时是线性扫描

4).用户也需要对返回的 fd_set 进行遍历

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:
大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。在前面说到的复制问题上,epoll使用mmap减少复制开销。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

2.支持一个进程所能打开的最大连接数
select 单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是32*32,同理64位机器上FD_SETSIZE为32*64),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
poll poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
epoll 虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接

3 FD剧增后带来的IO效率问题
select 因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。
poll 同上
epoll 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。

4 消息传递方式
select 内核需要将消息传递到用户空间,都需要内核拷贝动作。
poll 同上
epoll epoll通过内核和用户空间共享一块内存来实现的。

下面我们对上面的socket例子进行改造,看一下select的例子:
用线程的IO多路复用实现一个读写分离的、支持多客户端的连接请求

import socket  
import queue  
from select import select  
  
SERVER_IP = ('127.0.0.1', 9999)  
  
# 保存客户端发送过来的消息,将消息放入队列中 
message_queue = {}  
input_list = []  
output_list = []  
  
if __name__ == "__main__":  
    server = socket.socket()  
    server.bind(SERVER_IP)  
    server.listen(10)  
    # 设置为非阻塞  
    server.setblocking(False)  
  
    # 初始化将服务端加入监听列表  
    input_list.append(server)  
  
    while True:  
        # 开始 select 监听,对input_list中的服务端server进行监听  
        stdinput, stdoutput, stderr = select(input_list, output_list, input_list)  
  
        # 循环判断是否有客户端连接进来,当有客户端连接进来时select将触发  
        for obj in stdinput:  
            # 判断当前触发的是不是服务端对象, 当触发的对象是服务端对象时,说明有新客户端连接进来了  
            if obj == server:  
                # 接收客户端的连接, 获取客户端对象和客户端地址信息  
                conn, addr = server.accept()  
                print("Client {0} connected! ".format(addr))  
                # 将客户端对象也加入到监听的列表中, 当客户端发送消息时 select 将触发  
                input_list.append(conn)  
                # 为连接的客户端单独创建一个消息队列,用来保存客户端发送的消息  
                message_queue[conn] = queue.Queue()  
  
            else:  
                # 由于客户端连接进来时服务端接收客户端连接请求,将客户端加入到了监听列表中(input_list),客户端发送消息将触发  
                # 所以判断是否是客户端对象触发  
                try:  
                    recv_data = obj.recv(1024)  
                    # 客户端未断开  
                    if recv_data:  
                        print("received {0} from client {1}".format(recv_data.decode(), addr))  
                        # 将收到的消息放入到各客户端的消息队列中  
                        message_queue[obj].put(recv_data)  
  
                        # 将回复操作放到output列表中,让select监听  
                        if obj not in output_list:  
                            output_list.append(obj)  
  
                except ConnectionResetError:  
                    # 客户端断开连接了,将客户端的监听从input列表中移除  
                    input_list.remove(obj)  
                    # 移除客户端对象的消息队列  
                    del message_queue[obj]  
                    print("\n[input] Client  {0} disconnected".format(addr))  
  
        # 如果现在没有客户端请求,也没有客户端发送消息时,开始对发送消息列表进行处理,是否需要发送消息  
        for sendobj in output_list:  
            try:  
                # 如果消息队列中有消息,从消息队列中获取要发送的消息  
                if not message_queue[sendobj].empty():  
                    # 从该客户端对象的消息队列中获取要发送的消息  
                    send_data = message_queue[sendobj].get()  
                    sendobj.sendall(send_data)  
                else:  
                    # 将监听移除等待下一次客户端发送消息  
                    output_list.remove(sendobj)  
  
            except ConnectionResetError:  
                # 客户端连接断开了  
                del message_queue[sendobj]  
                output_list.remove(sendobj)  
                print("\n[output] Client  {0} disconnected".format(addr)) 

epoll实现实例

#!/usr/bin/env python  
import select  
import socket  
  
response = b''  
  
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
serversocket.bind(('0.0.0.0', 8080))  
serversocket.listen(1)  
# 因为socket默认是阻塞的,所以需要使用非阻塞(异步)模式。  
serversocket.setblocking(0)  
  
# 创建一个epoll对象  
epoll = select.epoll()  
# 在服务端socket上面注册对读event的关注。一个读event随时会触发服务端socket去接收一个socket连接  
epoll.register(serversocket.fileno(), select.EPOLLIN)  
  
try:  
    # 字典connections映射文件描述符(整数)到其相应的网络连接对象  
    connections = {}  
    requests = {}  
    responses = {}  
    while True:  
        # 查询epoll对象,看是否有任何关注的event被触发。参数“1”表示,我们会等待1秒来看是否有event发生。  
        # 如果有任何我们感兴趣的event发生在这次查询之前,这个查询就会带着这些event的列表立即返回  
        events = epoll.poll(1)  
        # event作为一个序列(fileno,event code)的元组返回。fileno是文件描述符的代名词,始终是一个整数。  
        for fileno, event in events:  
            # 如果是服务端产生event,表示有一个新的连接进来  
            if fileno == serversocket.fileno():  
                connection, address = serversocket.accept()  
                print('client connected:', address)  
                # 设置新的socket为非阻塞模式  
                connection.setblocking(0)  
                # 为新的socket注册对读(EPOLLIN)event的关注  
                epoll.register(connection.fileno(), select.EPOLLIN)  
                connections[connection.fileno()] = connection  
                # 初始化接收的数据  
                requests[connection.fileno()] = b''  
  
            # 如果发生一个读event,就读取从客户端发送过来的新数据  
            elif event & select.EPOLLIN:  
                print("------recvdata---------")  
                # 接收客户端发送过来的数据  
                requests[fileno] += connections[fileno].recv(1024)  
                # 如果客户端退出,关闭客户端连接,取消所有的读和写监听  
                if not requests[fileno]:  
                    connections[fileno].close()  
                    # 删除connections字典中的监听对象  
                    del connections[fileno]  
                    # 删除接收数据字典对应的句柄对象  
                    del requests[connections[fileno]]  
                    print(connections, requests)  
                    epoll.modify(fileno, 0)  
                else:  
                    # 一旦完成请求已收到,就注销对读event的关注,注册对写(EPOLLOUT)event的关注。写event发生的时候,会回复数据给客户端  
                    epoll.modify(fileno, select.EPOLLOUT)  
                    # 打印完整的请求,证明虽然与客户端的通信是交错进行的,但数据可以作为一个整体来组装和处理  
                    print('-' * 40 + '\n' + requests[fileno].decode())  
  
            # 如果一个写event在一个客户端socket上面发生,它会接受新的数据以便发送到客户端  
            elif event & select.EPOLLOUT:  
                print("-------send data---------")  
                # 每次发送一部分响应数据,直到完整的响应数据都已经发送给操作系统等待传输给客户端  
                byteswritten = connections[fileno].send(requests[fileno])  
                requests[fileno] = requests[fileno][byteswritten:]  
                if len(requests[fileno]) == 0:  
                    # 一旦完整的响应数据发送完成,就不再关注写event  
                    epoll.modify(fileno, select.EPOLLIN)  
  
            # HUP(挂起)event表明客户端socket已经断开(即关闭),所以服务端也需要关闭。  
            # 没有必要注册对HUP event的关注。在socket上面,它们总是会被epoll对象注册  
            elif event & select.EPOLLHUP:  
                print("end hup------")  
                # 注销对此socket连接的关注  
                epoll.unregister(fileno)  
                # 关闭socket连接  
                connections[fileno].close()  
                del connections[fileno]  
finally:  
    # 打开的socket连接不需要关闭,因为Python会在程序结束的时候关闭。这里显式关闭是一个好的代码习惯  
    epoll.unregister(serversocket.fileno())  
    epoll.close()  
    serversocket.close()

python select参数说明(转)

select函数原型:
select.select(rlist, wlist, xlist[, timeout])
本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。
select是一个直接调用unix中select()的简单接口。前三个参数都是‘等待对象’的序列:整型的文件描述符或者是一个无参数方法fileno()返回的整数:

rlist: 等待直到准备好读

wlist: 等待直到准备好写

xlist: 等待一种意外的情况 (在手册中查看你的系统中认为的那种情况)

rlist参数标识等待可读性检查的套接口。如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成。对其他套接口而言,可读性意味着有排队数据供读取。或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()或recvfrom()操作均能无阻塞完成。
wlist参数标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()和sendto()调用将无阻塞完成。
xlist参数标识等待带外数据存在性或意味错误条件检查的套接口。请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否。对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。
空序列也是允许的,但是能不能3个参数都为空就要由你的系统决定了。(众所都知unix下是行得,windows下不行)timeout指定了一个秒级的浮点型参数表示超时时间当timeout参数为空的时候省略了函数会阻塞直到至少有一个文件描述符已经准备好了。
返回值是三个已经准备好的列表,也是3个参数对象的子集。如果超时了,返回的是三个空列表。其中列表中可以接收的参数类型是Python中的文件参数(例如sys.stdin或者是open()、sys.popen()的返回对象),或者是 socket.socket的返回对象。你也可以自己封装成一个类,只要适合fileno()方法。
注意:在windows中文件对象是无法接受的,但是socket是可以使用的。