Python或者准确来说CPython,存在GIL(global interpreter lock)的问题,会导致多线程处理的速度远低于预期,并动辄出现运行异常,而多进程的开销和通信也是个麻烦。
近期看到了一个协程的概念(嘿嘿,不是某旅游app),通过异步机制,可以在遇到网络访问阻塞之类的应用场景下,进行自动切换,理论上来说可以大幅降低系统负载以提升处理速度。考虑到前期做的多线程扫描器的高负载情况,打算用协程对其改造一番。
在python的自带功能中,yield可以实现基本的协程功能,这里则使用了gevent库,通过其monkey及pool进行实施,其中monkey用于对阻塞操作打补丁,pool则用于保持固定的协程数量,导入示例如下:
创建协程池及初步处理部分代码如下:
51行为创建上限300的协程池。
53-56行则为通过xlrd库读取目标文件(03及之前的Excel格式)。
58行则通过IPy来判断IP地址的正确性,省得写那个超长的正则了。
59行即为通过pool的spawn方法,调用do_scan进行目标扫描操作。
协程池的大小和系统的同时打开文件数量参数是相关的(因为do_scan的网络操作),这个参数可以通过ulimit看一下:
如果协程池过大,而且确实也派生了那么多,就会看到下面的错误:
这个呢可以通过ulimit -n调整一下就能解决,不过在实际操作中,将“open files”增大到65535,协程池扩大到2000后,发现运行反而异常缓慢,所以还是调成了300来进行操作。
do_scan的操作是通过nmap的PortScanner实现的,scan_res用来暂存扫描结果入库(入库的代码就不列了),示例如下:
启动扫描功能后,通过top观察负载情况:
可以看到,系统负载相对还是挺低的,与之前的多线程模式下,200线程CPU负荷即超过了50%相比,可说是改善良多。另外还可以看到,python中进行nmap模块导入,实际上是会调用系统中nmap程序去执行扫描,所以nmap还是版本新点比较好。
执行过程通过time记录了一下,结果如下:
real为实际执行时间,可以看到用了52分钟,处理记录数为20260条,与之前多线程模式处理足足用了近4个小时相比,同样是提升巨大,嘿嘿,这样扫描模式完全可以考虑改成全端口扫描了。
总体来说,试用还是挺成功的,在网络操作或类似的访问阻塞场景下,协程对线程具备非常好的替代性,后续还可以考虑从以下几个方面再改进一下:
增加多进程模式,进程+协程进一步提升扫描能力。
目标文件的读取也可以采用独立进程实施。
协程中不做数据库操作提交,通过一个单独进程进行合并提交。
领取专属 10元无门槛券
私享最新 技术干货