wifijammer源码分析一

处于学习别人代码风格阶段,github参考学习程序
程序开头会有

#!/usr/bin/python
# -*- coding: utf-8 -*-

一是用来指定脚本语言为 Python,二是用来指定文件编码为utf-8.

1、Python logging 模块使用

最好学习资料还是官方文档

(1)输出等级

logging是一个实用的辅助工具,可以分等级地打印调试信息或错误信息。分为五个等级:

Paste_Image.png

默认是warning等级。设置等级后只有大于等于该等级的信息会打印出。不指定文件则打印在终端上。可以通过以下方式指定输出文件和输出等级:
logging.basicConfig(filename='example.log',level=logging.DEBUG)
也可以在命令行通过 --log=INFO/DEBUG/ERROR等

(2)格式化显示

可以根据自己的需要改变显示信息的格式,如:

logging.warning('%s before you %s', 'Look', 'leap!')

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

打印出:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

即可以通过basicConfig改变输出格式,默认为

WARNING:root:Look before you leap!

关于时间

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

打印出:

2017-04-29 19:47:17,128 is when this event happened

也可以改变默认的时间显示格式(some problem):

logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
(3)深入学习

https://docs.python.org/2/howto/logging-cookbook.html#logging-cookbook
程序示例:

import logging

logging.basicConfig(level=logging.DEBUG,
                format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                datefmt='%a, %d %b %Y %H:%M:%S',
                filename='myapp.log',
                filemode='w')

#################################################################################################
#定义一个StreamHandler,将INFO级别或更高的日志信息打印到标准错误,并将其添加到当前的日志处理对象#
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
#################################################################################################

logging.debug('This is debug message')
logging.info('This is info message')
logging.warning('This is warning message')

当没有指定filename时,默认为标准错误输出,如上程序中的StreamHandler。

2、scapy工具使用

scapy是一个强大的第三方库,用于网络嗅探。能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。它可以很容易地处理一些典型操作,比如端口扫描,tracerouting,探测,单元 测试,攻击或网络发现(可替代hping,NMAP,arpspoof,ARP-SK,arping,tcpdump,tethereal,P0F等)。 最重要的他还有很多更优秀的特性——发送无效数据帧、注入修改的802.11数据帧、在WEP上解码加密通道(VOIP)、ARP缓存攻击(VLAN) 等,这也是其他工具无法处理完成的。

有以下两种使用方式:

执行sudo scapy命令进入交互式数据包处理,或在Python代码中使用from scapy.all import *引入scapy

慢慢学习别人,造轮子

3、threading模块实现多线程

threading对thread(多线程底层支持模块,一般不建议使用)进行了封装,将一些线程的操作对象化
参考 http://www.cszhi.com/20130528/python-threading.html

import threading
 
def thread_fun(num):
    for n in range(0, int(num)):
        print " I come from %s, num: %s" %( threading.currentThread().getName(), n)
 
def main(thread_num):
    thread_list = list();
    # 先创建线程对象
    for i in range(0, thread_num):
        thread_name = "thread_%s" %i
        thread_list.append(threading.Thread(target = thread_fun, name = thread_name, args = (20,)))
 
    # 启动所有线程
    for thread in thread_list:
        thread.start()
 
    # 主线程中等待所有子线程退出
    for thread in thread_list:
        thread.join()
 
if __name__ == "__main__":
    main(3)

列表的广义化,列表可以是函数、类的集合。可以理解为存放的是地址。通过threading.Thread函数创建一个线程,指定target-回调函数,线程名以及参数等。
thread.join()函数会依次检测线程池中的线程是否结束,没有结束就阻塞直到线程结束,结束后会跳转执行下一个线程的join函数。Python的join函数还可以设置超时时间,Thread.join([timeout])。

上述是通过自定义创建函数,并通过Thread运行,也可以继承Thread类进行简化编程。

import threading
 
class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self);
 
    def run(self):
        print "I am %s" %self.name
 
if __name__ == "__main__":
    for thread in range(0, 5):
        t = MyThread()
        t.start()

通过自定义run函数重写Thread中的run函数。

扩展:
(1)setdaemon函数

Python中得thread的一些机制和C/C++不同:在C/C++中,主线程结束后,其子线程会默认被主线程kill掉。而在python中,主线程结束后,会默认等待子线程结束后,主线程才退出。
setDaemon:主线程A启动了子线程B,调用b.setDaemaon(True),则主线程结束时,会把子线程B也杀死,与C/C++中得默认效果是一样的。

#! /usr/bin/env python

import threading
import time

class myThread(threading.Thread):
   def __init__(self, threadname):
     threading.Thread.__init__(self, name=threadname)
     self.st = 2

   def run(self):
     time.sleep(self.st)
     print self.getName()
   def setSt(self, t):
     self.st = t

def fun1():
   t1.start()
   print "fun1 done"

def fun2():
   t2.start()
   print "fun2 done"

t1=myThread("t1")
t2=myThread("t2")
t2.setSt(10);
# t2.setDaemon(True)
fun1()
fun2()
print "now u will see me"

当 t2.setDaemon(True)没有生效时,打印出

fun1 done
fun2 done
now u will see me
t1
t2

生效后,打印出

fun1 done
fun2 done
now u will see me
t1

t2.setDaemon(True)表示主进程结束后立即结束子进程,不管子进程有没有运行完成。

(2)互斥锁

多个线程访问同一资源,由于先后顺序不确定,产生“线程不安全”,引入互斥锁,使无序变有序。

import threading
import time
 
counter = 0
mutex = threading.Lock()
 
class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
 
    def run(self):
        global counter, mutex
        time.sleep(1);
        if mutex.acquire():
            counter += 1
            print "I am %s, set counter:%s" % (self.name, counter)
            mutex.release()
 
if __name__ == "__main__":
    for i in range(0, 100):
        my_thread = MyThread()
        my_thread.start()

当一个线程调用Lock对象的acquire()方法获得锁时,这把锁就进入“locked”状态。因为每次只有一个线程1可以获得锁,所以如果此时另一个线程2试图获得这个锁,该线程2就会变为“block“同步阻塞状态。直到拥有锁的线程1调用锁的release()方法释放锁之后,该锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

(3)避免死锁
(4)进程间通信
(5)管道pipe

4、subprocess 模块

官网参考资料
subprocess允许开启一个新的进程,并与之通信。
subprocess用来代替以下模块:

  • os.system
  • os.spawn*
  • os.popen*
  • popen2.*
  • commands.*
(1)subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

运行参数中的命令,并返回 returncode,如:
subprocess.call(['ls','-al']) 在官方文档中有以下注意

Note :Do not use stdout=PIPE or stderr=PIPE with this function as that can deadlock based on the child process output volume. Use Popen
with the communicate() method when you need pipes.

值得注意shell=False这个参数,根据官网shell=False比shell=True更安全,假设运行以下

 cmd = "cat test.txt; rm test.txt"  
subprocess.call(cmd, shell=True)

shell=True参数会让subprocess.call接受字符串类型的变量作为命令,并调用shell去执行这个字符串,第一个测试中的分号被认为是shell命令中的分隔符,执行了cat和rm两个命令。
当shell=False时,subprocess.call只接受数组变量作为命令,并将数组的第一个元素作为命令,剩下的全部作为该命令的参数,因此第二个测试只执行了cat命令,并试着打开了作为参数的”text.txt;”,”rm” , “text.txt”三个文件。

(2)subprocess.check_call (*popenargs , **kwargs )

执行上面的call命令,并检查返回值,如果子进程返回非0,则会抛出CalledProcessError异常,这个异常会有个returncode
属性,记录子进程的返回值。

(2)subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)

和上个函数类似,主要不同在于将所有输出保存为字符串,而不直接打印到标准输出。

>>> import subprocess
>>> str = subprocess.check_output(['echo','hello world'])
>>> str
'hello world\n'
>>> 
(3) class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

是该模块中最为重要的方法之一,创建一个新进程,类似于unix系统下的 os.execvp()
,windows下的 CreateProcess() 。

>>> import shlex, subprocess
>>> command_line = raw_input()
/bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'"
>>> args = shlex.split(command_line)
>>> print args
['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"]
>>> p = subprocess.Popen(args) # Success!

shelex是一个简单的词典分析模块 ,shlex.split()
可以被用于序列化复杂的命令参数,比如:

>>> shlex.split('ls ps top grep pkill')
['ls', 'ps', 'top', 'grep', 'pkill']
args参数:

可以是一个字符串,可以是一个包含程序参数的列表。要执行的程序一般就是这个列表的第一项,或者是字符串本身。
subprocess.Popen(["cat","test.txt"])
subprocess.Popen("cat test.txt")
这两个之中,后者将不会工作。因为如果是一个字符串的话,必须是程序的路径才可以。
但是下面的可以工作 subprocess.Popen("cat test.txt", shell=True)
这是因为它相当于 subprocess.Popen(["/bin/sh", "-c", "cat test.txt"])
在*nix下,当shell=False(默认)时,Popen使用os.execvp()来执行子程序。args一般要是一个【列表】。如果args是个字符串的
话,会被当做是可执行文件的路径,这样就不能传入任何参数了。

executable参数 :

很少用到,用来指定要执行的程序,一般程序可以用args参数指定。

preexec_fn参数:

如果把preexec_fn设置为一个可调用的对象(比如函数),就会在子进程被执行前被调用。(仅限*nix)

close_fds参数:

如果把close_fds设置成True,*nix下会在开子进程前把除了0、1、2以外的文件描述符都先关闭。在 Windows下也不会继承其他文件描述符。

shell参数:

如果把shell设置成True,指定的命令会在shell里解释执行。

cwd参数:

如果cwd不是None,则会把cwd做为子程序的当前目录。注意,并不会把该目录做为可执行文件的搜索目录,所以不要把程序文件所在
目录设置为cwd 。

env参数:

如果env不是None,则子程序的环境变量由env的值来设置,而不是默认那样继承父进程的环境变量。注意,即使你只在env里定义了
某一个环境变量的值,也会阻止子程序得到其他的父进程的环境变量
后面几个很少用到。

Popen类的method
Popen.poll()

Check if child process has terminated. Set and return returncode attribute.

Popen.wait()

Wait for child process to terminate. Set and return returncode attribute.

Popen.communicate(input=None)

Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child.

Popen.send_signal(signal)
Popen.terminate()

停止一个子进程,在linux下发送SIGTERM信号给子进程

Popen.kill()

杀死一个子进程,在linux下发送SIGKILL给子进程。

常用的一些属性
  • Popen.returncode
  • Popen.pid
  • Popen.stderr
  • Popen.stdout
  • Popen.stdin
    对于Popen.stdin,如果stdin是PIPE,则这个属性返回的是为子进程提供输入的文件对象。
    同理,If the stdout argument was PIPE
    , Popen.stdout is a file object that provides output from the child process. Otherwise, it is None
    示例:
    output= dmesg | grep hda
    等价于
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
output = p2.communicate()[0]

注意以下几点:
communicate函数返回一个元祖 (stdoutdata, stderrdata)
当Popen函数中stdout=PIPE时,表示输出到一个管道中,要获取该管道,Popen.stdout会返回该句柄,可以通过读文件方法读出该管道中数据。

pipe = os.popen("cmd", 'r', bufsize)
==>
pipe = Popen("cmd", shell=True, bufsize=bufsize, stdout=PIPE).stdout

推荐阅读更多精彩内容