Python进阶第三课--网络编程(一)

1.前言

    上节内容我们介绍的是关于数据库方面的Python编程知识,记住数据库操作的几个关键步骤便可。Python是一个很强大的网络编程工具,Python内有狠毒针对常见的网络协议的库,对网络协议的各个层次进行了抽象封装。其次,Python很擅长处理字节流的各种模式,使用Python可以很容易地写出处理各种协议格式的代码,有可能这些协议格式现在还不存在在处理代码。在这节的内容里面,我会介绍一些Python标准课中可用的一些网络模块,然后具体看看它们的一些用法,接着是能同时处理多个连接的方法和使用。

2.socket模块

    在网络编程中一个最基本的组件就是套接字,也就是我们称的socket。套接字基本上是两个端点的程序之间的“信息通道”。程序可能分布在不同的计算机上,但是通过网络连接,通过套接字相互发送信息。在Python中大多都隐藏了socket模块的基本细节,不直接和套接字交互。套接字包括两个:服务器套接字和客户机套接字。在创建一个服务器套接字以后,让它等待连接,这样它就在某个网络地址处监听,直到有客户端套接字连接,连接完成后,两者就可以进行交互了。

    一个套接字就是socket模块中的socket类的一个实例。它的实例化需要三个参数:

  • 地址族(默认是socket.AF_INET)
  • 流(默认是socket.SOCK_STREAM)
  • 协议(默认是0)

对于一个普通的套接字而言,不需要提供任何参数。套接字有两个方法:send和recv,用于传输数据,可以使用字符串参数调用send以发送数据,用一个所需的字节数做参数调用recv来接受数据。如果不能确定用哪个数字比较好,1024是一个不错的选择。来看看一些具体的列子:

一个小型的服务器:

import socket

s=socket.socket()

host=socket.gethostname()
port=1234
s.bind(host,port)


s.listen(5)
while True:
    c,addr=s.accept()
    print 'Got connection from',addr
    c.send('Thank you for connecting')
    c.close()

一个小型的客户机:

import socket

s=socket.socket

host=socket.gethostname()
port=1234

s.connect(host,port)
print s.recv(1024)

    服务器套接字使用bind方法后,再调用listen方法去监听某个特定的地址。客户端套接字使用connect方法连接到服务器,在connect方法中使用的地址与服务器在bind方法中的地址相同。服务器套接字开始监听后,就可以接受来自客户端的连接。这个步骤使用accept方法来完成。这个方法会阻塞直到客户端连接,然后返回一个格式为(client,address)的元组。这个大过程往往都是在一个无限循环中进行的。

3.urllib和urllib2模块

    在能使用的各种网络函数库中,功能最强大最丰富可能就是urllib和urllib2了。通过它们在网络上访问文件就好像访问本地电脑上的文件一样,通过一个简单的函数调用,几乎可以把任何URL所指向的东西作为程序输入。这两个没模块的功能都差不多,但urllib2更好一些。

3.1打开远程文件

    可以像打开本地文件一样打开远程文件,不同之处是只能使用只读模式。使用来自urllib模块的urlopen,而不是简单的open。

from urllib import urlopen

webpage=urlopen('http://www.baidu.com')

urlopen返回的类文件对象支持close、read、readline和readlines方法,当然也支持迭代。

3.2获取远程文件

    函数urlopen返回一个能从中读取数据的类文件对象。假如你希望urllib为你下载文件并在本地文件中存储一个文件的副本,那么可以使用urlretrieve。urlretrieve返回一个元组(filename,headers)而不是类文件对象。filename是本地文件的名字,headers包含一些远程文件的信息。

urlretrieve('http://www.baidu.com','C:\\python_webpage.html')

这个语句获取百度搜索引擎的主页并把存储在C:\\python_webpage.html中。如果没有指定文件名,文件就会被放在临时地位置,用open函数可以打开它,但如果完成了对它的操作,就可以删除它以节省空间。这时可以调用urlcleanup函数,不需要提供参数,该函数负责清理工作。

4.SocketServer的超基础功能

    对于基础的套接字的服务器的实现并不是很难,但是如果你想再获取一些新的功能,就需要SocketServer模块中的一些服务框架来为你服务:BasedHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer,所有这些服务器框架都为基础服务器增加了特定的功能。

    SocketServer包含了4个基本的类:针对TCP流式套接字的TCPServer,针对UDP数据报套接字的UDPServer,以及针对性不强的UnixStreamServer和UnixDatagramServer,一般情况下很少用到后三个。来看看具体的例子:

from SocketServer import TCPServer,StreamRequestHandler

class Handler(StreamRequestHandler):

    def handle(self):
        addr=self.request.getpeername()
        print 'Got connection from', addr
        self.wfile.write('Thank you for connecting')
server=TCPServer(('', 52763), Handler)
server.serve_forever()

    BaseRequestHandler类把所有的操作都放到了处理器的一个叫做handle的方法中,这个方法会被服务器调用。然后这个方法就会访问属性self.request中的客户端套接字。如果使用的是流,那么可以使用StreamRequestHandler类,创建了其他两个新属性:self.rfile和self.wfile。然后就能使用这类文件对象和客户机进行通信。

5.多个连接

    前面讨论的服务器解决方案都是同步的,也就是一次只能连接一个客户机并处理它的请求。假如每个请求只是花费很少的时间,一个完整的聊天会话,那么同时能处理多个连接就很重要。

    有三种方法能实现这个目的:分叉(forking)、线程(threading)、异步I/O。通过对SocketServer服务器使用混入类,派生进程和线程很容易处理。

5.1使用SocketServer进行分叉和线程操作

    如果是handle方法要花费很长时间的话,那么分叉和线程的行为就有很有。下面展示如何使用线程和分叉。

分叉操作:

from SocketServer import TCPServer,ForkingMixIn,StreamRequestHandler

class Server(ForkingMixIn,TCPServer):
    pass
class Handler(StreamRequestHandler):
    def handle(self):
        addr=self.request.getpeername()
        print 'Got connection form', addr
        self.wfile.write('Thank you for connecting')
server=Server(('',52763),Handler)
server.serve_forever()

线程操作:

from SocketServer import TCPServer,ThreadingMixIn,StreamRequestHandler

class Server(ThreadingMixIn, TCPServer):
    pass
class Handler(StreamRequestHandler):
    def handle(self):
        addr=self.request.getpeername
        print 'Got connect from', addr
        self.wfile.write('Thank you for connecting')
server=Server(('',52763), Handler)
server.serve_forever()

5.2带有select和poll的异步I/O

    当一个服务器和一个客户端通信时,来自客户端的数据可能是不连续的。如果使用分叉或者线程处理,那就不是问题。当一个程序在等待数据,另一个并行的程序可以继续处理它们自己的客户端。另外的处理方法是只处理在给定时间内真正要进行通信的客户端。不需要一直监听,只要监听一会儿,然后把它放到其他客户端的后面。

    这是asyncore/asynchat框架和Twisted框架采用的方法,这种功能的基础是select函数,如果poll函数可用,那也可以是它,这两个函数都来自与select模块。这两个函数中,poll的伸缩性更好,但它只能在UNIX系统中使用。

    select函数需要3个序列作为它的必选参数,此外还有一个可选的以秒为单位的超时间作为第四个参数,这些序列是文件描述符整数,也就是我们等待的连接。

    下面来看一个使用了select的简单服务器:

import  socket,select

s=socket.socket()

host=socket.gethostname()
port=1234
s.bind((host,port))

s.listen(5)
inputs=[s]
while True:
    rs, ws, es=select.select(inputs,[],[])
    for r in rs:
        if r is s:
            c,addr=s.accept()
            print 'Got connection from',addr
        else:
            try:
                data=r.recv(1024)
                disconnected=not data
            except socket.error:
                disconnected=True
            if disconnected:
                print r.getpeername(), 'disconnected'
                inputs.remove(r)
            else:
                print data

    poll方法使用起来比select简单,在调用poll时候,会得到一个poll对象。然后就可以使用poll对象的register方法注册一个文件描述符。注册后可以调用unregister方法移除注册的对象。

使用poll的简单服务器:

import socket,select

s=socket.socket()
host=socket.gethostname()
port=1234
s.bind((host,port))

fdmap={s.fileno(): s}


s.listen(5)
p=select.poll()
p.register(s)
while True:
    events=p.poll()
    for fd,event in events:
        if fd==s.fileno():
            c,addr=s.accept()
            print 'Got connection from',addr
            p.register(c)
            fdmap[c.fileno()]=c
        elif event & select.POLLLIN:
            data=fdmap[fd].recv(1024)
            if not data:
                print fdmap[fd].getpeername(), 'disconnected'
                p.unregister(fd)
                del fdmap[fd]
        else:
                print (data)

    更多关于select和poll的参考信息可以在官方文档中查看到,在这里不做赘述。好了,关于网络编程的部分问题先介绍到这里,这是第一部分,接着下一章节还会介绍关于网络编程的问题--Twisted。敬请期待!


你可能感兴趣的:(Python,Python学习之路)