zoukankan      html  css  js  c++  java
  • python学习笔记(十 三)、网络编程

    博客已迁移到CSDN《https://blog.csdn.net/qq_33375499

    最近心情有点儿浮躁,难以静下心来

      Python提供了强大的网络编程支持,很多库实现了常见的网络协议以及基于这些协议的抽象层,让你能够专注于程序的逻辑,而无需关心通过线路来传输比特的问题。

    1 几个网络模块

      1.1 模块socket

      网络编程中的一个基本组件是套接字(socket)。套接字基本上是一个信息通道,两端各有一个程序。这些程序可能位于(通过网络相连接的)不同的计算机上,通过套接字向对方发送消息。在Python中,大多数网络编程都隐藏了模块socket的基本工作原理,不与套接字直接交互。

      套接字分为两类:服务端套接字和客户端套接字。创建服务端套接字后,让它等待连接请求的到来。这样,它将在某个网络地址(由IP地址和端口号组成)处监听,知道客户端套接字建立连接,还必须处理多个连接;而客户端套接字只需连接,完成任务后再断开连接即可。

      套接字是模块socket中socket类的实例。实例化套接字时最多可指定三个参数:一个地址族(默认为socket.AF_INET);是流套接字(socket.SOCK_STREAM,默认设置)还是数据报套接字(socket.SOCK_DGRAM);协议(使用默认值0就好)。

      服务器套接字先调用方法bind,在调研方法listen来监听特定的地址。然后,客户端套接字通过调用方法connect并提供bind时指定的地址来连接服务端。这里的地址是一个格式为(host, port)的元祖,其中host是主机名,port是端口号。方法listen接收一个参数——代办任务清单的长度(即最多可有多少个连接在队列中等待接纳,到达这个数量后将开始拒绝连接)。

      服务端套接字开始监听后,就可接收客户端连接,使用方法accept来等待连接。这个方法将阻断(等待)到客户端连接到来为止(有点类似与yield关键字),然后返回一个格式为(client, address)的元组,其中client为客户端套接字,而address为地址。服务端能以其认为合适的方式处理客户端连接,然后再次调用accept以等待新连接到来。

      为传输数据,套接字提供了两个方法:send-发送和recv-接收(表示receive),这两个方法都是接收或发送字节流数据。

      简单的服务器:

    import socket
    
    #创建套接字
    s = socket.socket()
    
    #获取主机名
    host = socket.gethostname()
    #端口号
    port = 8080
    address = (host, port)
    #创建连接
    s.bind(address)
    
    #监听:最大连接数为5
    s.listen(5)
    while True:
        #等待连接
        client, addr = s.accept()
        #接收消息
        str = bytes.decode(client.reve(1024))
        #发送消息
        client.send(str.encode('我是服务端!','utf-8'))

      简单的客服端:

    import socket
    s = socket.socket()
    
    #连接服务端地址
    host = socket.gethostname()
    port = 8080
    address = (host, port)
    #连接
    s.connect(address)
    #接收消息
    str = bytes.decode(s.reve(1024))
    #发送消息
    s.sent(str.encode('我是客户端!','utf-8'))

      1.2 模块urllib和urllib2

      模块urllib和urllib2,由名字可知,它们让你能够通过网络访问文件,就像这些文件位于你的计算机中一样。只需一个简单的函数调用,就几乎可将统一资源定位符(URL)可指向的任何动作作为程序的输入。这两个模块一般用于下载网页、从中提取信息自动生成研究报告等。对于简单的操作,urllib绰绰有余,如果要实现HTTP身份验证或Cookie,或编写扩展来处理自己的协议,urllib2可能是更好的选择。

      1.2.1 打开远程文件

      使用模块urllib.request中的函数urlopen来打开远程文件,只能进行读取操作。如:

      from urllib.request import urlopen

      page = urlopen('www.baidu.com')

      如果连接到网络,变量page将包含一个类似于文件的对象,这个对象与你所连接的地址相关联。urlopen返回类似于文件的对象支持方法clost、read、readline和readlines,还支持迭代等。

      1.2.2 获取远程文件

      函数urlopen返回一个类似于文件的对象,可从中读取数据。如果要让urllib替你下载文件,并将其副本存储在本地,可使用urllib.request中的函数urlretrieve。这个函数返回一个格式为(filename, headers)的元祖,其中filename是本地文件的名称(由urllib自动创建),而headers包含一些有关远程文件的信息。如果要给下载的副本指定文件名,可通过函数urlretrieve的第二个参数来指定。

      函数格式为: urlretrieve(url, filename=None, reporthook=None, data=None),函数urlretrieve将url中的内容存储在filename中,如果filename没有指定,下载的副本将放在某个临时位置,可使用函数open来打开。但使用完毕后,你可能想将其删除,以免暂用磁盘空间,可调用函数urlcleanup来替你完成清除工作。

      1.2.3 其他模块

      Python标准库提供了一些与网络相关的模块,如下(只列举了一些常用的):

      cgi            基本的CGI文件

      asyncore        异步套接字处理程序

      asynchat        包含补充asyncore的功能

      Cookie           Cookie对象操作,主要用于服务器

      cookielib        客户端Cookie支持

      email           电子邮件支持

      ftplib          FTP客户端模块

      httplib           HTTP客户端模块

      mailbox          读取多种邮箱格式

      urlparse          用于解读URL

    2 SocketServer及相关的类

      模块SocketServer是标准库提供的服务器框架的基石,这个框架包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer等服务器,它们在基本服务器的基础上添加了各种功能。

      SocketServer包含4个基本的服务器:TCPServer(支持TCP套接字流)、UDPServer(支持UDP套接字流)、UnixStreamServer和UnixDatagramServer。后面3个不常用。

      使用模块SocketServer编写服务器时,大部分代码都位于请求处理中。每当服务器收到客户端的连接请求时,都将实例化一个请求处理程序,并对其调用各种处理方法来处理请求。基本请求处理程序类BaseRequestHandler将所有操作都放在一个方法中——服务器自动调用的方法handle。这个方法可通过书信self.request来访问客户端套接字。如果处理的是流(TCPServer时很可能如此),可使用StreamRequestHandler类,它包含另外两个属性:self.rfile(用于读取)和self.wfile(用于写入)。你可以使用这两个类似与文件的对象来与客户端通信。

      模块SocketServer还包含很多其他的类,它们为HTTP服务器提供基本的支持,以及XML-RPC支持。

      

    from socketserver import TCPServer,StreamRequestHandler
    
    class Handle(StreamRequestHandler):
        def handle(self):
            addr = self.request.getpeername()
            print("addr:", addr)
            self.wfile.write("谢谢")
    
    server = TCPServer(('',1234), Handle)
    server.serve_forever()

    3 多个连接

      处理多个连接的主要方式有三种:分叉(forking)、线程化和异步I/O。通过结合使用SocketServer中的混合类和服务器类,很容易实现分叉和线程化。但是,分叉占用资源较多,且在客户端很多时可伸缩性不高;而线程化可能带来同步问题。并且Windows不支持分叉,分叉是UNIX术语。

      3.1 使用SocketServer实现分叉和线程化

      使用框架SocketServer创建分叉或线程化服务器非常简单,一般仅当方法handle需要很长时间才能执行完毕是,分叉和线程化才能提供有效帮助。创建如下:

      1.分叉服务器

    from socketserver import TCPServer,StreamRequestHandler,ForkingMixIn
    
    class Server(ForkingMixIn, TCPServer):pass
    
    class Handle(StreamRequestHandler):
        def handle(self):
            addr = self.request.getpeername()
            print("addr:", addr)
            self.wfile.write("谢谢")
    
    server = Server(('',1234), Handle)
    server.serve_forever()

      2.线程化服务器

    from socketserver import TCPServer,StreamRequestHandler,ThreadingMixIn
    
    class Server(ThreadingMixIn, TCPServer):pass
    
    class Handle(StreamRequestHandler):
        def handle(self):
            addr = self.request.getpeername()
            print("addr:", addr)
            self.wfile.write("谢谢")
    
    server = Server(('',1234), Handle)
    server.serve_forever()

      3.2 使用select和poll实现异步I/O

      当服务器与客户端通信时,来自客户端的数据可能时断时续。如果使用了分叉和线程化,这就不是问题:因为一个进程(线程)等待数据时,其他进程(线程)可继续处理其客户端。然而,另一种做法是只处理正在通信的客户端。你甚至无需不断监听,只需监听后将客户端加入队列即可。

      这就是框架asyncore/asynchat 和 Twisted采取的方法。这种功能的基石是函数select或poll(只支持UNIX系统)。这两个函数都位于模块select中,其中poll的可伸缩性更高。

      函数select接收三个必不可少的参数和一个可选参数,其中前三个参数为序列,而第四个参数为超时时间(单位为秒)。这些序列包含文件描述符整数(也可以是包含返回文件描述符整数的方法fileno),表示我们正在等待的连接。这三个序列分别表示需要输入和输出以及发生异常(错误等)的连接。如果没有指定超时时间,select将阻断(即等待)到有文件描述符准备就绪;如果指定了超时时间,select将最多阻断指定的秒数;如果超时时间为0,select将不断轮询(即不阻断)。select返回三个序列(即一个长度为3的元祖),其中每个序列都包含相应参数中处于活动状态的文件描述符。

      如下所示,一个使用select的简单服务器:import socket, select

    s = socket.socket()
    host = socket.gethostname()
    port = 8080
    s.bind((host,port))
    s.listen(5)
    inputs = [s]
    while True:
       # 方法select返回三个参数,通过序列解包赋值 rs, ws, es
    = select.select(inputs, [], []) for r in rs: if r is s: c, addr = s.accept() print("addr:", addr) inputs.append(c) else: try: data = r.reve(1024) discon = not data except socket.error: discon = True if discon:
              # 1.getpeername():用于获取与某个套接字关联的外地协议地址
             # 2.getsockname():用于获取与某个套接字关联的本地协议地址
    print(r.getpeername(), 'discon') inputs.remove(r) else: print(data)

      方法poll 使用起来比select容易。调用poll 时,将返回一个轮询对象。你可以使用方法register 向这个对象注册文件描述符(或包含方法fileno的对象)。注册后可使用方法unregister 将它们删除。注册对象(如套接字)后,可调用其方法poll(它接受一个可选的超时时间参数)。这将返回一个包含(fd, event)元祖的列表(可能为空),其中fd为文件描述符,event是发生的事件。event是一个位掩码,这意味着它是一个整数,其各个位对应于不同的事件。各种事件是用select 模块中的常量表示的,如下表。要检查指定的位是否为1 (即是否发生了相应的事件),可使用按位与运算符( & ):

      if event & select.POLLIN: pase

      事件名              描述

      POLLIN               文件描述符中有需要读取的数据

      POLLPRI               文件描述符中有需要读取的 紧急数据

      POLLOUT                   文件描述符为写入数据做好了准备

      POLLERR              文件描述符出现了错误状态

      POLLHUP              挂起。连接已断开

      POLLNVAL             无效请求。连接未打开

      

      下面是一个使用poll的简单服务器:

    import socket, select
    s = socket.socket()
    
    host = socket.gethostname()
    port = 8080
    s.bind((host, port))
    
    fdmp = {s.fileno(): s}
    
    s.listen(5)
    p = select.poll()
    p.register(s)
    while True:
        events = p.poll()
        for fd, event in events:
            if fd in fdmp:
                c, addr = s.accept()
                print("addr:", addr)
                p.register(c)
                fdmp[c.fileno()] = c
            elif event & select.POLLIN:
                data = fdmp[fd].recv(1024)
                if not data:
                    print(fdmp[fd].getpeername(),"discon")
                    p.unregister(fd)
                    del fdmp[fd]
                else:
                    print(data)

    4 Twisted

      这是Twisted Matrix Laboratories 开发的一个框架,功能丰富而复杂,支持大多数主要的网络协议。框架Twisted是异步的,因此效率和可伸缩性都非常高。对很多自定义网络应用程序来说,使用Twisted来开发可能是最佳选择。

  • 相关阅读:
    CodeForces 156B Suspects(枚举)
    CodeForces 156A Message(暴力)
    CodeForces 157B Trace
    CodeForces 157A Game Outcome
    HDU 3578 Greedy Tino(双塔DP)
    POJ 2609 Ferry Loading(双塔DP)
    Java 第十一届 蓝桥杯 省模拟赛 19000互质的个数
    Java 第十一届 蓝桥杯 省模拟赛 19000互质的个数
    Java 第十一届 蓝桥杯 省模拟赛 19000互质的个数
    Java 第十一届 蓝桥杯 省模拟赛十六进制转换成十进制
  • 原文地址:https://www.cnblogs.com/www-123456/p/10638868.html
Copyright © 2011-2022 走看看