• Python程序设计8——网络编程


      Python是一个很强大的网络编程工具,python内有很多针对场景网络协议的库,在库顶部可以获得抽象层,这样就可以集中精力在程序的逻辑处理上,而不是停留在网络实现的细节中。

    1 少数几个网络设计模块

      在标准库中有很多网络设计模块,在其他地方还有更多,下面是常用的模块。

    1.1 socket 模块                  

      在网络编程中一个基本组件是套接字(socket,又是哪个人才翻译的),套接字是两个程序之间的信息通道,python因此了socket模块的基本细节,并不直接和套接字交互。套接字包括两个:服务器套接字和客户机套接字,创建一个服务器套接字,让它等待连接,这样它就在某个网络地址处(IP+端口号)监听。处理客户端套接字通常比处理服务器端套接字容易,因为服务器端随时处理客户端的连接,还有处理多个连接,而客户机只是简单地连接,完成事务,断开连接。
      socket模块中的socket方法用于创建套接字,socket方法语法格式如下:
    socket(socket_family, socket_type, protocol = 0)
    其中:
    socket_family:该参数的值可以为AF_UNIX或AF_INET
    socket_type:该参数的值可以为SOCK_STREAM或SOCK_DGRAM
    protocol:该参数一般不赋值,默认值为0
    套接字相当于应用程序访问下层网络服务的接口,使用套接字,使得用户可以在不同的主机之间进行通信,从而实现数据交换。

     

      一个正在被使用的套接字有着和其匹配的通信类型及相关的进程信息,并且会和另外一个套接字交换数据。当前Socket规范支持两种类型的套接字,即流套接字和数据报套接字,其中,流套接字提供了双向有序且不重复的数据服务。而数据报套接字虽然支持双向的套接字,但是对报文的可靠性和有序性不能保证。换句话说,通过数据报套接字接口获取的数据有可能是重复的,这需要上层的应用程序来进行区分,另外还有更多的套接字类型,比如原始套接字类型,这和系统中的实现相关。
      一个套接字就是一个Socket模块中的Socket类的实例,它的实例化需要3个参数:
    1.第一个参数是地址簇,默认值是socket.AF_INET
    2.第二个参数是流(默认值是socket.SOCK_STREAM)或数据报(默认值是socket.SOCK_DGRAM)套接字
    3.第三个参数是使用的协议,默认值为0,使用默认值即可。
      不同的套接字类型有着不同的套接字地址,在AF_UNIX地址簇中使用一个简单的字符串;在AF_INET地址簇中使用的是host和port地址对,其中host为主机地址名,IP地址或url,而port是一个整数;AF_INET6地址簇中用(host,port,flowinfo,scopeid)元组来表示,其中的host和port和AF_INET相同。

    1.2 客户端socket通信过程  

      客户端应用程序在生成套接字对象后,可以调用bind()方法来绑定自己的请求套接字接口地址,然后调用connect方法来连接服务器端进程。当连接建立后,可以使用send和recv方法来传输数据,然后使用close方法将端口关闭。

    1.3 服务端socket通信过程    

      对于服务端套接字来说,使用bind方法绑定一个套接字接口地址,接着使用listen方法监听客户端请求,当有客户端请求时,通过accept方法来生成一个连接对象,然后通过此连接对象发送和接收数据,数据传输完毕,可以调用close方法将生成的连接关闭。服务器端通信过程的方法调用如下:

     

    2 urllib 和urllib2模块                                        

      urllib和urllib2是非常强大的库,它允许通过网络访问文件,这些文件像存储在本地电脑上一样,只需要一个简单的方法调用,几乎可以把任何URL所指向的文件用程序访问。
      urllib2是更强大的库,如果只使用简单的下载,urllib就足够了,如果需要使用HTTP验证或Cookie以及需要为协议写扩展程序,那么urllib2是不错的选择。urllib2是python的一个获取url的模块,它用urlopen方法提供了一个非常简洁的接口,使得用各种各样的协议获取url成为可能。同时提供了稍微复杂的接口来处理常见的状况,例如基本的认证、cookies和代理等,这些都是由openner和handler的对象来处理。

    2.1 打开远程文件                   

      可以像打开本地文件一样打开远程文件,不同之处是可以使用只读模式,使用的是来自urllib2模块中的urlopen方法,该方法格式如下:
    urlopen(url, data = None,proxies= None)
    方法说明:
    url:符合url规范的字符串
    data:向指定url发送的数据字符串,get和post都可以,但必须符合标准格式,格式为key = value
    proxies:代理服务器地址字典,如果未指定,在windows平台上依据IE设置,不支持需要验证的代理服务器。如proxies={"http","htttp://www.python.org:8080"}
    下面介绍用urlopen打开远程文件

    import urllib2
    response = urllib2.urlopen("http://10.137.168.14:11100/store/web/portalone");
    html = response.read();
    print html

      这段代码中,首先使用urllib2模块中的urlopen方法打开了一个远程文件,返回一个类文件对象,该类文件对象支持close、read、readlines和readline方法,同时也支持迭代,本例中用read方法打开远程文件,返回url指向的页面源码

    <script type="text/javascript">
    $(function(){
    if(musicPlay)
    {
    musicPlay.hide();
    }
    });
    
    </script>

      如果需要访问本地文件,则可以用file开头的URL作为urlopen方法。

    import urllib2
    response = urllib2.urlopen(r"file:///home/jack/demo/4.5/hell/Demo09.py");
    html = response.read();
    print html

    2.2 获取远程文件                  

      用urlopen提供一个能从中读取数据的类文件对象,如果需要urllib下载所指向的文件并在本地文件中存储一个文件副本,那么就可以使用urllib模块中的urlretrieve方法,urlretrieve方法返回一个元组(filename,headers),而不是类文件对象,其中filename是本地文件的名字由urllib自动创建,headers包含一些远程文件的信息,urlretrieve方法语法格式如下:

    urlretrieve(url, filename=None,reporthook=None,data=None)
    该方法说明如下:
    url:符合URL规范的字符串
    filename:本地文件路径的字符串,从URL返回的数据将保存在该文件中,如果设置为None,则声称一个临时文件
    reporthook:一个方法的引用,可以任意自定义该方法的行为,只需要保证方法有3个参数:第一个参数为目前为止传递的数据库数量:第二个参数为每个数据块的大小,单位为byte:第三个参数为文件的总大小
    data:向指定的url发送字符串,get和post都可以,必须符合标准格式:key=value
    下面介绍如何使用urlretrieve方法获取远程文件:

    import urllib;
    urllib.urlretrieve(r"http://www.cnblogs.com/people", r"F:Current Studypythonpeople.html")

    该程序将一个网页http://www.cnblogs.com/people 下载下来,并以people.html名称保存。

    python的其他模块

    2.3 服务器与客户端通信         

      服务端和客户端通信是构建网络通信的基础,例如HTTP访问,它也是客户端和服务端的通信,即浏览器从HTTP服务器获取HTML数据并显示。
    下面先创建一个服务端程序。

    View Code

      上面的listen方法有一个参数,用来设置连接队列的长度

    2.3.1 使用SocketServer模块

      如前所述,用Socket生成一个服务进程相对比较复杂,为了简化,python专门提供了SocketServer模块,此模块有5个服务类。如下所示:

      一般情况下,BaseServer类不会被实际使用,因此SocketServer模块中只包含了4个基本的类:针对TCP套接字流的TCPServer,针对UDP数据报套接字的UDPServer,以及UNIXStreamServer和UNIXDatagramServer,其中最重要的类是TCPServer,此类中有简单TCP协议实现的服务器接口,为应用程序提供了可靠的流数据传输,除此之外,还有更多模块中的类派生于TCPServer类,包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer等等,通过对SocketServer模块中类的继承,可以实现更多功能的服务端应用程序,接着是UDPServer类,此类中有数据报服务的接口。此类同样提供数据传输服务,但是并不保证数据的可靠性和有序性。另外两个使用较少的是UNIXStreamServer和UNIXDatagramServer类,这两个类都是使用UNIX域套接字地址,他们几乎不能使用在非UNIX平台上。
      为了写一个使用SocketServer框架的服务器,大部分代码会包含在一个请求处理程序中,每当服务器收到一个请求时,就会实例化一个请求处理程序,并且它的各种处理方法会在 处理请求时被调用。基本的BaseRequestHander类把所有的操作都放到了handle方法中,这个方法会被服务器调用,然后访问属性self.request中的客户端套接字。如果使用的是流,那么可以使用StreamRequestHander类来创建两个新属性self.rfile和self.wfile,然后使用这些类文件对象和客户机通信。
      SocketServer框架中的其他类实现了对HTTP服务器的基本支出,其中包括允许CGI脚本,也包括对XML RPC的支持。
      下面用SocketServer模块制作一个小型服务器:

    View Code

    客户端的构建

    import socket;
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
    host = socket.gethostname();
    port = 1234;
    s.connect((host, port));
    print s.recv(1024);
    s.close;

      对于一个客户端而言,首先用Socket模块导入,然后调用其socket方法生成一个Socket对象,并使用gethostname方法获取服务器地址,之后再通过connect方法连接服务程序,当客户端连接服务器后,一直等待的服务进程被唤醒,并处理此连接,这里的客户端处理直接调用recv方法获取服务端发送过来的数据,最后调用close方法将连接关闭。运行:
    Link come from ('192.168.1.103', 4642)

    2.3.2 使用urlparse包也可以创建客户端

    View Code

      Socket套接字不仅可以连接服务器,还可以处理客户端请求,从而达到服务端与客户端的通信。下面用Socket对象分别创建一个服务端和客户端。
    服务端socket流程是:
      首先生成Socket对象,然后使用bind方法绑定主机和端口号,并使用listen方法监听,之后进行while循环,使服务器一直处于监听状态,使用accept方法可以接受客户端的一个连接,接着使用send方法来向客户端发送一个字符串的数据,最后使用close连接,释放资源。
      总的来说:先创建socket连接并监听,然后阻塞等待,如果有消息过来,就发送消息,然后关闭连接,总共4步
    服务端:

    View Code

    客户端:
    客户端的流程是:使用Socket模块中的socket方法生成socket对象,然后使用connect连接服务器,并输出服务端发送过来的消息,最后关闭Socket对象。

    import socket;
    import time;
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
    sock.connect(("localhost", 8001));
    time.sleep(2);
    sock.send("1");
    print sock.recv(1024);
    sock.close();

    2.4 异步通信方式                   

      在上面的示例中,所有的服务器端实现都是同步的,也就是说,服务程序只有处理完一个连接后,才能处理另外一个连接。如果要使服务器端应用程序能够同时处理多个连接,则需要使用异步通信方式。python标准库提供了三种处理方式:分叉方式、线程方式和异步I/O方式。

    2.4.1 使用SocketServer进行分叉处理

      当有多个连接同时到达服务器端时,可以通过分叉方式进行处理,对于接收到的每个连接,主进程都会生成相应的一个子进程专门用来处理此连接,而主进程则依旧保持在监听状态,从而使每个连接都由一个对应的子进程来处理。由于生成的子进程和主进程是同时运行的,所以不会阻塞新的连接。这种方式好处是比较简单、有效,但是由于生成进程消耗的资源比较大,所以当连接很多时,会带来性能问题。
      分叉是相当于复制了主进程,但是只是子进程,相当于在时间线上创建了分支,最后得到两个独立存在的进程,进程可以判断哪个是主进程,哪个是子进程(使用fork,就行叉子一样)
    在一个使用分叉的服务器中,每一个客户端连接都利用分叉创造一个子进程,主进程继续监听新的连接,同时子进程处理客户端,当客户端请求结束后,子进程就退出了,因为分叉的进程是并行运行的,客户端之间不必相互等待。

    View Code

      在该例子中定义了Server类,该类继承自ForkingMixIn和TCPServer类,使此类具有两个类的特点,也就是提供流数据传输服务和多连接处理。

    2.4.2 使用线程方式                              

      由于线程是一种轻量级的进程,具有进程所没有的优势,当存在大量连接而消耗资源太多的情况下,则可以使用线程方式进行处理。线程实现的方式和分叉的处理方式类似。当有连接到来的时候,主线程将生成一个子线程来处理连接,而在子线程处理连接的时候,主线程依然处于监听状态,并不会阻塞连接。
      由于生成的子线程和主线程都存在于相同的进程中,共享内存,因此这种处理方式的效率非常高,从而使得大量地使用线程会造成线程之间的数据同步,如果处理不好,则可能使得服务程序时区效应,这就必须确保主线程和子线程的变量不冲突,在现代操作系统中一般都使用分叉方式来处理多连接,但是windows不支持。

      下面是利用分叉创建线程的方式:

    View Code

      这段代码与使用分叉处理的示例相同,唯一区别在于在生成的Server类时采用了ThreadingMixIn类,这样生成的Server类在处理多连接时采用线程方式处理

    2.4.3 异步IO方式                               

      当服务器与客户端通信时,来自客户端的数据持续的时间较长且数据突发的多连接情况下,如果使用分叉或线程处理,占用资源太多,一种改进的方式就是采用专门的异步IO通信方式,即在一定的时间段查看已有的连接并处理。处理的过程包括读取数据和发送数据。
      在python标准库中,由asyncore和asynchat模块来实现异步IO处理,这种功能依赖于select()和poll方法,这两个方法定义在select模块中。
    注意:select和poll方法,poll方法的伸缩性更好,但它只能在UNIX系统中使用,在windows系统中不可用。
    1.select模块
    select方法用于指定的文件描述符进行监视,并在文件描述符集改变的时候做出响应。select模块中的select方法的语法格式如下:
    select.select(List rlist, List wlist, List xlist[, long timeout])
    select方法有4个参数:
    1.rlist、wlist和xlist,这是3个必须参数,分别表示等待输入、输出和错误的文件描述符,这3个参数都为文件描述符列表。空列表也是允许的,但是如果3个参数都是空列表,则表示平台相关,在linux系统平台下允许,在windows平台下不允许。
    2.timeout是可选的,参数为一个浮点数,用例指定系统监视文件描述符集改变的超时时间,单位为妙。
    select方法返回值是一个包含3个值的元组,元组中的3个值即为在select方法中前3个参数已经准备好的文件描述符。下面介绍select方法

    2.使用asyncore模块
      在python中,可以使用asyncore模块来实现异步通信,实际上,该模块提供了用例构建异步通信方式的客户端和服务端的基础架构,特别适用于聊天类的服务器和协议的实现。其基本思想是,创建一个或多个网络信道,实际上网络信道是Socket对象的一个封装,当信道创建后,调用loop方法来激活网络信道服务,直到最后一个网络信道关闭。
      在asyncore模块中,主要用于网络事件循环检测的loop方法是核心,在loop方法中将会通过select方法来检测特定的网络信道。当select方法返回所有事件的socket对象后,loop方法检查检查此事件和套接字状态并创建一个高层次的事件信息,然后针对此高层次的事件信息调用相应的方法,asyncore还提供了底层的API用来创建服务器。
      同时,该模块中还有一个dispatcher类,这是一个Socket对象的轻量级封装,用于处理网络交互事件,其中的方法是在异步loop方法中调用或者直接当作一个普通的非阻塞Socket对象。框架是:

     

      在dispatcher类中,当在特定的时间或连续状态条件下,会触发一些高层次的事件,在继承类中可以通过重载这些方法来处理这些特定的事件,默认事件如下:
    handle_connect:连接时的访问接口
    handle_close:接口关闭
    handle_accept:从监听端口上获取数据
      注意:在进行异步处理的过程中,可以通过网络信道的readable和writeable方法来对事件进行控制,使用这两个方法,能够判断是否需要使用select或poll方法来读取事件
      在asyncore模块中,readable和writable默认不做任何操作,而直接访问True,可以通过重载这两个方法来判断需要检查的脸颊,从而控制流程及网络状态。之后可以使用handle_read和handle_write方法来读写网络数据,完成对数据的接收和发送。下面是一个HTTP协议的客户端例子:

    View Code

      在该段代码中,导入了asyncore模块和socket模块,接着定义了一个名称为http_client的类,该类继承自asyncore.dispatcher类,因此可以在http_client类中重载dispatcher类中的处理方法。
      在构造函数中,首先调用了dispatcher类的构造函数,然后用create_socket方法创建socket对象,该方法封装了socket模块的socket方法。在调用socket方法之后,还用了setblocking方法设置其阻塞方式为非阻塞,并获取了套接字的文件描述符最后通过add_channel方法将文件描述符加入。在构造函数中使用connect方法连接特定服务器的80端口,这也是http默认端口,并设置类变量buffer为一个HTTP获取命令,获取内容。
     接下来定义了5个事件处理方法,分别在不同的时间发生时候被调用。
     1.handle_connect方法:将在HTTP连接时候被调用
     2.handle_close方法:关闭连接
     3.handle_read方法:调用recv方法来获取http数据,recv方法中的参数为一次读取最大字节数,需要注意的是,缓冲区大小最好为2的幂,如1024或者4086等数据
     4.handle_write方法:用来处理发送时的数据,这里首先调用send方法发送数据,其返回值为依据发送成功的数据,然后设置buffer为未发送的数据,这样做的原因是在异步通信过程中,不一定能保证每次发送都能发送成功
     5.writable方法:用来判断在什么时候发送数据,在方法体重,只是判断需要发送数据的缓冲区是否为空,如果不为空,则返回True,表示需要发送数据,而当缓冲区为空时,则不需要继续发送数据。
    示例:使用asyncore和socket模块将所请求的服务器端信息打印出来
    步骤:
    1.导入asyncore和socket模块
    2.新建一个类,该类继承asyncore.dispatcher类,并重载asyncore.dispatcher类中的__init__、handle_connect、handle_read、writable、handle_write和handle_close方法。在__init__方法中获取与服务端的脸颊,并将指定的页面请求赋值与request对象;在handle_connect方法中输出连接的服务器信息;在handle_write方法中奖请求对象request发送到服务器端;在handle_read方法中奖获取的服务器发送给客户端的信息写入一个记事本中。

  • 相关阅读:
    WPF学习之路(八)页面
    面试题整理:C#(一)
    [转载] Tomcat架构分析
    [转载] ConcurrentHashMap原理分析
    [转载] Java并发编程:Lock
    [转载] KAFKA分布式消息系统
    [转载] Java并发编程:Callable、Future和FutureTask
    [转载] Java线程池框架源码分析
    [转载] 红黑树(Red Black Tree)- 对于 JDK TreeMap的实现
    [转载] RED-BLACK(红黑)树的实现TreeMap源码阅读
  • 原文地址:https://www.cnblogs.com/people/p/3243349.html
Copyright © 2020-2023  润新知