Python基础教程(第2版)第十四章 网络编程

1.网络设计模块

1.1 socket模块

套接字(socket)主要是两个程序之间的“信息通道”,程序可能(通过网络连接)分布在不同的计算机上通过套接字相互发送信息

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

        第1个参数是地址族(默认是socket.AF_ INET)

        第2个参数是流 (socket.SOCK_STREAM,默认值)或数据报(socket.SOCK_DGRAM)套接字

        第3个参数是使用的协议(默认是0,使用默认值即可)

        对于一个普通的套接字,不需要提供任何参数

1.1.1 服务器套接字(同步网络编程)

        服务器必须准备随时处理客户端的连接,同时还要处理多个连接

        创建一个服务器套接字后,让它等待连接,它就会在某个网络地址 (IP地址和端口号的组合)处监听

        服务器端套接字使用bind方法后,再调用listen方法去监听给定的地址

                地址:一个格式为(host, port)的元组,host是主机名, port是端口号(一个整数)

                listen方法只有一个参数:服务器未处理的连接的长度(即允许排队等待的连接数目,这些连接在停止接收之前等待接收)

        服务器端套接字开始监听后,它就可以接受客户端的连接,这个步骤使用accept方法来完成

                accept方法会阻塞(等待)直到客户端连接,然后该方法就返回一个格式为 (client, address)的元组,client是一个客户端套接字,address是一个前面解释过的地址

                服务器能处理客户端到它满意的程度,然后调用另一个accept方法开始等待下一个连接

1.1.2 客户端套接字

        处理客户端套接字通常比处理服务器端套接字容易,因为客户机只是简单地连接,完成事务,断开连接

        客户端套接字使用connect方法连接到服务器(connect方法中的地址与bind中的地址相同)

1.1.3 套接字方法

send:使用字符串参数调用send以发送数据

revc:使用一个所需的(最大)字节数做参数调用recv来接收数据,如果不能确定使用哪个数
字比较好,那么1024是个很好的选择

#一个小型服务器
import socket
s=socket.socket()
host=socket.gethostname()
port=1234

s.bind((host,port))	#服务器(host,port)
s.listen(5)	#监听给定的地址(允许排队等待的连接数目: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))	#连接到服务器(host,port)
print(s.recv(1024))	#接收数据(最大字节数:1024)

能在服务器还在运行时运行多个客户机,通过用服务器端所在机器的实际主机名来代替客户端调用gethostname所得到的主机名,就可以让两个运行在不同机器上的程序通过网络连接起来

1.2 urllib和urllib2模块

通过一个简单的函数调用,几乎可以把任何URL指向的东西用做程序的输人,即能通过网络访问文件,就像那些文件存在于你的电脑上一样

如果只使用简单的下载,urllib就足够了

        使用urllib模块的urlopen,可以像打开本地文件一样打开远程文件,不同的是可以使用只读模式(也可以以file开头的URL访问本地文件)

        urlopen返回的是一个能从中读取数据的类文件对象,该对象支持close、read、readline、readlines和迭代

from urllib import urlopen
webpage = urlopen('http://www.python.org')
page = urlopen('file:C:\\text\\somefile.txt')

        如果想提取前面打开的Python页中“ About”链接的 (相对)URL,可以用正则表达式实现 

import re
text=webpage.read()
m=re.search('<a href="([^"]+)" .*?>about</a>',text,re.IGNORECASE)	#如果网页内容发生变化,则需要自行修改正则表达式
print(m.group(1))

如果希望urllib为你下载文件并在本地文件中存储一个文件的副本,那么可以使用urlretrieve

        urlretrieve返回一个元组 (filename,headers)而不是类文件对象,filename是本地文件的名宇(由urllib自动创建),headers包含一些远程文件的信息

        如果想要为下载的副本指定文件名,可以在urlretrieve函数的第2个参数中给出

urlretrieve('http://www.python.org','C:\\python_webpage.html')

        如果没有指定文件名,文件就会放在临时的位置,用open函数可以打开它,但如果完成了对它的操作,就可以删除它以节省硬盘空间

        要清理临时文件,可以调用urlcleanup函数,但不要提供参数,该函数会负责清理工作

如果需要使用HTTP验证 (HTTP authentication) 或cookie或者要为自己的协议写扩展程序
话,那么urllib2是个好的选择

1.3其他模块

2.SocketSever和它的朋友们

SocketServer模块是标准库中很多服务器框架的基础,这些服务器框架都为基础服务器增加了特定的功能

        BaseHTTTPServer、SimpleHTTPServer、 CGIHTTPServer、SimpleXMLRPCServer、
DocXMLRPCServer

SocketServer包含了4个基本的类:针对 TCP套接字流的TCPServer,针对UDP数据报套接字
的UDPServer,以及针对性不强的UnixStreamServer 和UnixDatagramServer

写一个使用SocketServer框架的服务器时,大部分代码会在一个请求处理程序中

        每当服务器收到一个来自客户端的请求时,就会实例化一个请求处理程序,此时它的各种处理方法会在处理请求时被调用,具体调用哪个方法取决于特定的服务器和使用的处理程序类

        这样可以把它们子类化,使得服务器调用自定义的处理程序集

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

SocketServer框架中的其他类实现了对HTTP服务器的基本支持,其中包括运行CGI脚本,包括对XML RPC(在第27章讨论)的支持

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((' ',1234),Handler)
server.serve_forever()

        StreamRequestHandler在连接处理完后会负责关闭连接 

3.多连接

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

有三种主要的方法能实现这个目的:分叉(forking)、线程(threading)以及异步I/O

通过对SocketServer服务器使用mix-in类,派生进程和线程很容易处理。即使要自己实现它们,这些方法也很容易使用

        同时,它们也有缺点:分叉会占据资源,并且如果有太多的客户端时分叉不能很好分叉

        尽管如此,对于合理数量的客户端,分叉在现代的UNIX或者Linux系统中是很高效的,如果有一个多CPU系统,那效率会更高

3.1 使用SocketSever进行分叉和线程处理

分叉是一个UNIX术语,当分叉一个进程(一个运行的程序)时,基本上是复制了它,并且分叉后的两个进程都从当前的执行点继续运行,每个进程都有自己的内存副本 (比如变量)

        原来的进程称为父进程,复制的进程称为子进程

        在一个使用分叉的服务器中,每一个客户端连接都利用分叉创造一个子进程

        父进程继续监听新的连接,子进程处理客户端,当客户端的请求结束时,子进程就退出了

        分叉的进程是并行运行的,客户端之间不必互相等待

#使用了分叉技术的服务器
from SocketServer import TCPServer,ForkingMixIn,StreamRequestHandler
class Server(ForkingMixIn,TCPServer): 
	pass
class Handler (StreamRequestHandler):
	def handle(self):
		addr=self.request.getpeername()
		print("Got connection from",addr)
		self.wfile.write('Thank you for connecting')
server=TCPServer(('',1234),Handler)
server.serve_forever()

因为分叉有点耗费资源(每个分叉出来的进程都需要自己的内存),这就存在了另一个选择:线程

        线程是轻量级的进程或子进程,所有的线程都存在于相同的(真正的)进程中,共享内存     

         资源消耗的下降伴随着一个缺陷:因为线程共享内存,所以必须确保它们的变量不会冲突,或者是在同一时间修改同一内容,这会造成混乱,这些问题都可以归结为同步问题

在现代操作系统中(除了Windows,它不支持分叉),分叉实际是很快的,现代的硬件能比以往更好地处理资源消耗,如果不想被同步问题所困扰,分叉是一个很好的选择

#使用了线程处理的服务器
from SocketServer import TCPServer,ThreadingMixIn,StreamRequestHandler
class Server(ThreadingMixIn,TCPServer): 
	pass
class Handler (StreamRequestHandler):
	def handle(self):
		addr=self.request.getpeername()
		print("Got connection from",addr)
		self.wfile.write('Thank you for connecting')
server=TCPServer(('',1234),Handler)
server.serve_forever()

避免线程和分叉的另外一种方法是转换到Stackless Python,一个为了能够在不同的上下文之间快速、方便切换而设计的Python版本

        它支持一个叫做微线程(microthreads)的类线程的并行形式,微线程比真线程的伸缩性要好

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

异步I/O在底层的实现有点难度,基本的机制是select模块的select函数

        还存在更高的层次处理异步I/O的框架,这为处理一个强大可伸缩的机制提供了一个简单的、抽象的接口

        包含在标准库中的这种类型的基本框架由asyncore和asynchat模块组成

        Twisted是一个非常强大的异步网络编程框架

当一个服务器与一个客户端通信时,来自客户端的数据可能是不连续的,我们可以只处理在给定时间内真正要进行通信的客户端:不需要一直监听,只要监听一会,然后把它放在其他客户端的后面

上述功能的基础是来自select模块的select函数poll函数(poll的伸缩性更好,但它只能在UNIX系统中使用)

3.2.1 select函数

select函数需要3个序列(文件描述符整数)作为它的必选参数,此外还有一个可选的以秒为单位的超时时间作为第4个参数

        3个序列用于输入、输出以及异常情况

         序列能包含文件对象(在Windows中行不通)或者套接字

        如果没有给定超时时间,select会处于等待状态,直到其中的一个文件描述符已经为行动做好了准备

        如果给定了超时时间,select最多阻塞给定的超时时间,如果给定的超时时间是0,那么就给出一个连续的poll

        select的返回值是3个序列(也就是一个长度为3的元组),每个代表相应参数的一个活动子集,如返回的第1个序列是一个输入文件描述符的序列,其中有一些可以读取的东西

#使用了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)
			inputs.append(c)
		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)

3.2.2 poll函数 

poll方法使用起来比select简单,在调用poll时,会得到一个poll对象

        可以使用poll对象的register方法注册一个文件描述符(或者是带有fileno方法的对象)

        注册后可以使用unregister方法移除注册的对象

        注册了一些对象(比如套接字)以后,就可以调用poll方法(带有一个可选的超时时间参数)并得到一个(fd,event)格式列表(可能是空的)

        fd是文件描述符

        event会告诉你发生了什么,它是一个位掩码(整数),这个整数的每个位对应不同的事件,那些不同的事件是select模块的常量,为了验证一个给定的事件是否发生了,可以使用按位与操作符(&):if event & select.POLLIN : ...        

 

#使用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 in fdmap:
			c.addr = s.accept()
			print("Got connection from",addr)
			p,register(c)
			fdmap[c.fileno()]=c
		elif event & select.POLLIN:
			data=fdmap[fd].recv(1024)
			if not data:	#没有数据——关闭连接
				print(fdmap[fd].getpeername(),'disconnected')
				p.unregister(fd)
				del fdmap[fd]
		else:
			print(data)

4.Twisted

来自于Twisted Matrix实验室的Twisted是一个事件驱动的Python网络框架,原来是为网络游戏开发的,现在被所有类型的网络软件使用

在Twisted中,需要实现事件处理程序,事件处理程序在一个协议中定义,在一个新的连接到达时,同样需要一个创建这种协议对像的工厂

但如果只是想要创建一个通用的协议类的实例,那么就可以使用Twited自带的工厂(factory类在twisted.internet.protocol模块中)

当编写自己的协议时,要使用和超类一样的模块中的protocol

        得到了一个连接后,事件处理程序connectionMade就会被调用

        丢失了一个连接后,connectionLost就会被调用

        来自客户端的数据是通过处理程序dataReceived接收的

        如果想要使用事件处理策略来把数据发回到客户端,可以使用对象self.transport,这个对象有一个write方法,也有一个包含客户机地址(主机名和端口号)的client属性

#使用Twisted的简单服务器
from twisted.internet import reactor
from twisted.internet.protocol import Protocol,Factory
class SimpleLogger(Protocol):
	def connectionMade(self):
		print("Got connection from",self.transport.client)
	def connectionLost(self,reason):
		print(self.transport.client,'disconnected')
	def dataReceived(self,data):
		print(data)
factory=Factory()
factory.protocol=SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()

twisted.protoccls.basic模块中包含一个有用的预定义协议LineReceiver,它实现了dataReceived 并且只要收到了一整行就调用事件处理程序lineReceived

如果要在接受数据时做些事情,可以使用由LineReceiver定义的叫做rawDataReceived的事件处理程序,也可以使用lineReceived

#使用了LineReceiver协议改进的记录服务器
from twisted.internet import reactor
from twisted.internet.protocol import Factory
from twisted. protocols.basic import LineReceiver
class SimpleLogger(LineReceiver):
	def connectionMade(self):
		print("Got connection from",self.transport.client)
	def connectionLost(self,reason):
		print(self.transport.client,'disconnected')
	def dataReceived(self,line):
		print(line)
factory=Factory()
factory.protocol=SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值