Python提供了强大的网络编程支持,有很多库实现了常见的网络协议以及基于这些协议的抽象层,让你
能够专注于程序的逻辑,而无需关心通过线路来传输比特的问题。
低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
1、格式:
socket.socket(family,type,proto)
2、参数:
family(地址族): 套接字家族可以是 AF_UNIX 或者 AF_INET
type(套接字类型): 根据是面向连接的还是非连接分为SOCK_STREAM(流套接字)或SOCK_DGRAM(数据报套接字)
protocol(协议): 一般不填默认为0
1、所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。
2、套接字分为两类:服务器套接字和客户端套接字。创建服务器套接字后,让它等待连接请求
的到来。这样,它将在某个网络地址(由IP地址和端口号组成)处监听,直到客户端套接字建立
连接。随后,客户端和服务器就能通信了。客户端套接字处理起来通常比服务器端套接字容易些,因为服务器必须准备随时处理客户端连接,还必须处理多个连接;而客户端只需连接,完成任务后再断开连接即可。
3、Socket 对象(内建)方法
服务器端套接字:
函数 | ,描述 |
---|---|
s.bind() | 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 |
s.listen() | 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 |
s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 |
客户端套接字:
函数 | ,描述 |
---|---|
s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数:
函数 | ,描述 |
---|---|
s.recv() | 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。 |
s.send() | 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 |
s.sendall() | 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 |
s.recvfrom() | 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 |
s.sendto() | 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 |
s.close() | 关闭套接字 |
s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 |
s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr,port) |
s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 |
s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 如果 flag 为 False,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用 recv() 没有发现任何数据,或 send() 调用无法立即发送数据,那么将引起 socket.error 异常。 |
s.makefile() | 创建一个与该套接字相关连的文件 |
1、服务器套接字先调用方法 bind ,再调用方法 listen 来监听特定的地址。然后,客户端套接字就可连接到服务器了,办法是调用方法 connect 并提供调用方法 bind 时指定的地址(在服务器端,可使用函数 socket.gethostname 获取当前机器的主机名)。这里的地址是一个格式为 (host, port)的元组,其中 host 是主机名(如 www.example.com ),而 port 是端口号(一个整数)。方法 listen 接受一个参数——待办任务清单的长度(即最多可有多少个连接在队列中等待接纳,到达这个数量后将开始拒绝连接)。
2、服务器套接字开始监听后,就可接受客户端连接了,这是使用方法 accept 来完成的。这个方法将阻断(等待)到客户端连接到来为止,然后返回一个格式为 (client, address) 的元组,其中client 是一个客户端套接字,而 address 是前面解释过的地址。服务器能以其认为合适的方式处理客户端连接,然后再次调用 accept 以接着等待新连接到来。这通常是在一个无限循环中完成的。
3、最简单的客户端程序和服务器程序
服务器端:
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)
str = 'Thank you for connecting'
str = str.encode()
c.send(str)
c.close()
客户端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print(s.recv(1024))
分析:
1、如果在同一台机器上运行它们(先运行服务器程序),服务器程序将打印一条收到连接请求的消息,然后客户端程序将打印它从服务器那里收到的消息。
2、在服务器还在运行时,可运行多个客户端
在可供使用的网络库中, urllib 和 urllib2 可能是投入产出比最高的两个。它们让你能够通过网络访问文件,就像这些文件位于你的计算机中一样。只需一个简单的函数调用,就几乎可将统一资源定位符(URL)可指向的任何动作作为程序的输入。
模块 urllib 和 urllib2 的功能差不多,但 urllib2 更好一些。对于简单的下载, urllib 绰绰有余。如果需要实现HTTP身份验证或Cookie,抑或编写扩展来处理自己的协议, urllib2 可能是更好的选择。
使用模块urllib.request 中的函数 urlopen打开网页文件,并且读取其中的HTML结构:
from urllib.request import urlopen
webpage = urlopen('http://www.python.org')
#遍历http://www.python.org网页的HTNL结构
for line in webpage:
print(line)
结果:
原网页检查结果:
分析:函数 urlopen 返回一个类似于文件的对象,可从中读取数据。并且支持方法 close 、 read 、 readline 和 readlines ,还支持迭代等。
下载http://www.python.org网页HTML文件:
from urllib.request import urlretrieve
#下载http://www.python.org网页HTML文件
urlretrieve('http://www.python.org', r'F:\开发工具\python\project\wangluo\python_webpage.html')
结果:
分析:urlretrieve(filename, headers)函数中,filename是本地文件的名称(由 urllib自动创建,就是网址),headers是headers 包含一些有关远程文件的信息(这里是指保存的路径及文件名)
函数 | ,描述 |
---|---|
quote(string[, safe]) | 返回一个字符串,其中所有的特殊字符(在URL中有特殊意义的字符)都已替换为对URL友好的版本(如将~替换为 7E % )。如果要将包含特殊字符的字符串用作URL,这很有用。参数safe是一个字符串(默认为 ‘/’ ),包含不应像这样对其进行编码的字符。 |
quote_plus(string[, safe]) | 类似于 quote ,但也将空格替换为加号。 |
unquote(string) | 与 quote 相反。 |
unquote_plus(string) | 与 quote_plus 相反。 |
urlencode(query[, doseq]) | 将映射(如字典)或由包含两个元素的元组(形如 (key,value) )组成的序列转换为“使用URL编码的”字符串。这样的字符串可用于CGI查询中(详细信息请参阅Python文档)。 |
Python库等地方还包含很多与网络相关的模块。
高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
模块 SocketServer 是标准库提供的服务器框架的基石,这个框架包括BaseHTTPServer 、SimpleHTTPServer 、 CGIHTTPServer 、 SimpleXMLRPCServer 和 DocXMLRPCServer 等服务器,它们在基本服务器的基础上添加了各种功能。
SocketServer 包含4个基本的服务器: TCPServer (支持TCP套接字流)、 UDPServer (支持UDP数据报套接字)以及更难懂的 UnixStreamServer 和 UnixDatagramServer 。
使用模块 SocketServer 编写服务器时,大部分代码都位于请求处理器中。每当服务器收到客户端的连接请求时,都将实例化一个请求处理程序,并对其调用各种处理方法来处理请求。
具体调用哪些方法取决于使用的服务器类和请求处理程序类;还可从这些请求处理器类派生出子类,
从而让服务器调用一组自定义的处理方法。
基本请求处理程序类 BaseRequestHandler 将所有操作都放在一个方法中——服务器调用的方法 handle 。这个方法可通过属性 self.request 来访问客户端套接字。如果处理的是流(使用 TCPServer 时很可能如此),可使用 StreamRequestHandler 类,它包含另外两个属性: self.rfile (用于读取)和 self.wfile (用于写入)。你可使用这两个类似于文件的对象来与客户端通信。
基于 SocketServer 的极简服务器:
from socketserver import TCPServer, StreamRequestHandler
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print('Got connection from', addr)
str = 'Thank you for connecting'
str = str.encode()
self.wfile.write(str)
server = TCPServer(('', 1234), Handler)
server.serve_forever()
客户端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print(s.recv(1024))
运行三次客户端后,服务器接收结果:
Got connection from ('192.168.3.7', 65282)
Got connection from ('192.168.3.7', 65283)
Got connection from ('192.168.3.7', 65285)
处理多个连接的主要方式有三种:分叉(forking)、线程化和异步I/O。
分叉是一个UNIX术语。对进程(运行的程序)进行分叉时,基本上是复制它,而这样得到的两个进程都将从当前位置开始继续往下执行,且每个进程都有自己的内存副本(变量等)。原来的进程为父进程,复制的进程为子进程。如果你是科幻小说迷,可将它们视为并行的宇宙:分叉操作在时间轴上创建一个分支,最终得到两个独立存在的宇宙(进程)。所幸进程能够判断它们是原始进程还是子进程(通常查看函数 fork 的返回值),因此能够执行不同的操作。
分叉服务器:
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)
str = 'Thank you for connecting'
str = str.encode()
self.wfile.write(str)
server = Server(('', 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)
str = 'Thank you for connecting'
str = str.encode()
self.wfile.write(str)
server = Server(('', 1234), Handler)
server.serve_forever()
函数 select 接受三个必不可少的参数和一个可选参数,其中前三个参数为序列(第一个是所有的输入的data,就是指外部发过来的数据;第2个是监控和接收所有要发出去的data(outgoing data);第3个监控错误信息),而第四个参数为超时时间(单位为秒)。
如果没有指定超时时间, select 将阻断(即等待)到有文件描述符准备就绪;如果指定了超时时间, select 将最多阻断指定的秒数;如果超时时间为零, select将不断轮询(即不阻断)。
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)
inputs.append(c)
else:
try:
data = c.recv(1024)
disconnected = not data
except socket.error:
disconnected = True
if disconnected:
str = 'disconnected'
str = str.encode()
print(c.getpeername(), str)
inputs.remove(c)
else:
print(data)
使用 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)
分析: module ‘select’ has no attribute ‘poll’,运行后报错,因为Windows不支持poll。
Twisted是由Twisted Matrix Laboratories(http://twistedmatrix.com)开发的,这是一个事件驱动的Python网络框架,最初是为编写网络游戏开发的,但现被各种网络软件使用。
Twisted是一个功能极其丰富的框架,支持Web服务器和客户端、SSH2、SMTP、POP3、IMAP4、AIM、ICQ、IRC、MSN、Jabber、NNTP、DNS等
访问Twisted Matrix网站(http://twistedmatrix.com),并单击其中的一个下载链接。
如果你只想创建自定义协议类的实例,可使用Twisted自带的工厂——模块twisted.internet.protocol 中的 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()
1、套接字和模块 socket :套接字是让程序(进程)能够通信的信息通道,这种通信可能需要通过网络进行。模块 socket 让你能够在较低的层面访问客户端套接字和服务器套接字。服务器套接字在指定的地址处监听客户端连接,而客户端套接字直接连接到服务器。
2、 urllib 和 urllib2 :这些模块让你能够从各种服务器读取和下载数据,为此你只需提供指向数据源的URL即可。模块 urllib 是一种比较简单的实现,而 urllib2 功能强大、可扩展性极强。这两个模块都通过诸如 urlopen 等函数来完成工作。
3、框架 SocketServer :这个框架位于标准库中,包含一系列同步服务器基类,让你能够轻松地编写服务器。它还支持使用CGI的简单Web(HTTP)服务器。如果要同时处理多个连接,必须使用支持分叉或线程化的混合类。
4、select 和 poll :这两个函数让你能够在一组连接中找出为读取和写入准备就绪的连接。这意味着你能够以循环的方式依次为多个连接提供服务,从而营造出同时处理多个连接的假象。另外,相比于线程化或分叉,虽然使用这两个函数编写的代码要复杂些,但解决方案的可伸缩性和效率要高得多。
5、Twisted:这是Twisted Matrix Laboratories开发的一个框架,功能丰富而复杂,支持大多数主要的网络协议。虽然这个框架很大且其中使用的一些成例看起来宛如天书,但其基本用法简单而直观。框架Twisted也是异步的,因此效率和可伸缩性都非常高。对很多自定义网络应用程序来说,使用Twisted来开发很可能是最佳的选择。