?? 引言:客户/服务器架构
?? 套接字:通信终点
?? 面向连接与无连接套接字
?? Python 中的网络编程
?? Socket 模块
?? 套接字对象方法
?? TCP/IP 客户端和服务器
?? UDP/IP 客户端和服务器
?? SocketServer 模块
?? Twisted 框架介绍
?? 相关模块
16.1 介绍
什么是客户/服务器架构?
服务器是一个软件或硬件,用于提供客户需要的“服 务”。
服务器存在的唯一目的就是等待客户的请求,给这些客户服务,然后再等待其它的请求。
客户连上一个(预先已知的)服务器,提出自己的请求,发送必要的数据,然后就 等待服务器的完成请求或说明失败原因的反馈。
服务器不停地处理外来的请求,而客户一次只能提出一个服务的请求,等待结果。然后结束这个事务。
客户之后也可以再提出其它的请求,只是,这个请求会被视为另一个不同的事务了。
图16-1 Internet 上典型的客户/服务器概念。
硬件的客户/服务器架构
打印(机)服务
文件服务器
软件客户/服务器架构
软件服务器提供的服务主要是程序的运行,数据的发送与接收,合并,升级或其它的程序或 数据的操作。
Web 服务器
数据库服务器
窗口服务器
银行出纳是服务器
图16-2 在此图中的银行出纳“永远不停歇”地为客户提供服务
出纳运行在一个接收请求,处 理请求然后再处理其它请求或等待其它客户的无限循环中。
客户有可能已经排起了长龙,也有可能根本就没有客户。
无论如何,服务器都不会结束。
客户/服务器网络编程
在完成服务之前,服务器必需要先完成一些设置动作。先要创建一个通讯端点,让服务器能“监 听”请求。
服务器在准备好之后,也要通知潜在的客户,让它们知道服务器已经准备好处理服务 了。否则,没有人会提请求的。
比方说,你建立了一个全新的网站。这个网站非常的出色,非常的吸引人,非常的有用,是所有网站中最酷的一个。
但如果你不把网站的网址或者说统一资源定位符(URL)广而告之的话,没有人会知道这个网站的存在的。
所有的客户只要创建一个通讯端点,建立到服务器的连接,然后客户就可以提出请求,请求中,也可以包含必要的数据交互。
一旦请求处理完成,客户收到了 结果,通讯就结束了。
16.2 套接字:通讯端点
什么是套接字?
套接字起源于20 世纪70 年代加利福尼亚大学伯克利分校版本的Unix,即人们所说的BSD Unix,因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。
一开始,套接字被设计用在同一台主机上多个应用程序之间的通讯,这也被称进程间通讯,或IPC。
套接字有两种,分别是基于文件型的和基于网络型的。
Python 只支持AF_UNIX,AF_NETLINK,和AF_INET 家族。
套接字地址:主机与端口
一个Internet 地址由网络通讯所必需的主机与端口组成。
合法的端口号范围为0 到65535。
其中,小于1024 的端口号为系统保留端口。
如果你所使用的 是Unix 操作系统,保留的端口号(及其对应的服务/协议和套接字类型)可以通过/etc/services 文件获得。
常用端口号列表可以从下面这个网站获得:
http://www.iana.org/assignments/port-numbers
面向连接与无连接
面向连接
无论你使用哪一种地址家族。套接字的类型只有两种。一种是面向连接的套接字,即在通讯之 前一定要建立一条连接,就像跟朋友打电话时那样。这种通讯方式也被称为“虚电路”或“流套接 字”。面向连接的通讯方式提供了顺序的,可靠的,不会重复的数据传输,而且也不会被加上数据边 界。这也意味着,每一个要发送的信息,可能会被拆分成多份,每一份都会不多不少地正确到达目 的地。然后被重新按顺序拼装起来,传给正在等待的应用程序。 实现这种连接的主要协议就是传输控制协议(即TCP)。要创建TCP 套接字就得在创建的时候, 指定套接字类型为SOCK_STREAM。TCP 套接字采用SOCK_STREAM 这个名字,表达了它做为流套接字的 特点。由于这些套接字使用Internet 协议(IP)来查找网络中的主机,这样形成的整个系统,一般 会由这两个协议(TCP 和IP)来提及,即TCP/IP。
面向连接的套接字,即在通讯之 前一定要建立一条连接,就像跟朋友打电话时那样,这种通讯方式也被称为“虚电路”或“流套接字”
面向连接的通讯方式提供了顺序的,可靠的,不会重复的数据传输,而且也不会被加上数据边 界。
实现这种连接的主要协议就是传输控制协议(即TCP)。
无连接
与虚电路完全相反的是数据报型的无连接套接字。这意味着,无需建立连接就可以进行通讯。 但这时,数据到达的顺序,可靠性及数据不重复性就无法保证了。数据报会保留数据边界,这就表 示,数据不会像面向连接的协议那样被拆分成小块。 使用数据报来传输数据就像邮政服务一样。邮件和包裹不一定会按它们发送的顺序到达。事实 上,它们还有可能根本到不了!而且,由于网络的复杂性,数据还可能被重复传送。 既然数据报有这么多缺点,为什么还要使用它呢?(一定有什么方面能胜过流套接字的!)由于 面向连接套接字要提供一些保证,以及要维持虚电路连接,这都是很重的额外负担。数据报没有这 些负担,所以它更“便宜”。通常能提供更好的性能,更适合某些应用场合。 实现这种连接的主要协议就是用户数据报协议(即UDP)。要创建UDP 套接字就得在创建的时候, 指定套接字类型为SOCK_DGRAM。SOCK_DGRAM 这个名字,也许你已经猜到了,来自于单词“datagram” (“数据报”)。由于这些套接字使用Internet 协议来查找网络中的主机,这样形成的整个系统,一 般会由这两个协议(UDP 和IP)来提及,即UDP/IP。
无连接套接字与虚电路完全相反的是数据报型的无连接套接字,就像邮政服务一样,这意味着,无需建立连接就可以进行通讯。
无连接的通讯方式的数据到达的顺序,可靠性及数据不重复性就无法保证了,而且数据报会保留数据边界(数据不会像面向连接的协议那样被拆分成小块)。
实现这种连接的主要协议就是用户数据报协议(即UDP)
16.3 Python 中的网络编程
socket()模块函数
要使用socket.socket()函数来创建套接字。其语法如下:
socket(socket_family, socket_type, protocol=0)
socket_family 可以是AF_UNIX 或AF_INET。
socket_type 可以是SOCK_STREAM 或SOCK_DGRAM。
protocol 一般不填,默认值为0。
创建一个TCP/IP 的套接字,你要这样调用socket.socket():
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
创建一个UDP/IP 的套接字,你要这样:
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
当我们创建了套接字对象后,所有的交互都将通过对该套接字对象的方法调用进行。
套接字对象(内建)方法
套接字对象的常用函数 函数 描述 服务器端套接字函数 s.bind() 绑定地址(主机,端口号对)到套接字 s.listen() 开始 TCP 监听 s.accept() 被动接受 TCP 客户的连接,(阻塞式)等待连接的到来 客户端套接字函数 s.connect() 主动初始化 TCP 服务器连接 s.connect_ex() connect() 函数的扩展版本, 出错是返回错误码,而不是抛异常 公共用途的套接字函数 s.recv() 接收 TCP 数据 s.send() 发送 TCP 数据 s.sendall() 完整发送 TCP 数据 s.recvfrom() 接收 UDP 数据 s.sendto() 发送 UDP 数据 s.getpeername() 连接到当前套接字的远端的地址 s.getsockname() 当前套接字的地址 s.getsockopt() 返回指定套接字的参数 s.setsockopt() 设置指定套接字的参数 s.close() 关闭套接字 面向阻塞的套接字方法 (Blocking-Oriented Scoket Methods) s.setblocking() 设置套接字的阻塞或非阻塞的模式 s.settimeout() 设置阻塞套接字操作的超时时间 s.gettimeout() 得到阻塞套接字操作的超时时间 面向文件的套接字的函数 s.fileno() 套接字的文件描述符 s.makefile() 创建一个与该套接字关联的文件 核心提示:在运行网络应用程序是,最好在不同的电脑上执行服务器和客户端程序
核心提示:在运行网络应用程序时,最好在不同的电脑上执行服务器和客户端的程序。
创建一个TCP 服务器
ss = socket() # 创建服务器套接字 ss.bind() # 把地址绑定到套接字上 ss.listen() # 监听连接 inf_loop: # 服务器无限循环 cs = ss.accept() # 接受客户连接 comm_loop: # 通讯循环 cs.recv()/cs.send() # 对话(接收与发送) cs.close() # 关闭客户套接字 ss.close() # 关闭服务器套接字(可选)
1 #!/usr/bin/env python 2 3 from socket import * 4 from time import ctime 5 6 HOST = ‘‘ # ‘‘ indicate bind() any effient available address 7 PORT = 21569 8 BUFSIZ = 1024 # 1K 9 ADDR = (HOST, PORT) 10 11 tcpSerSock = socket(AF_INET, SOCK_STREAM) 12 tcpSerSock.bind(ADDR) 13 tcpSerSock.listen(5) # max 5 connection concurrence 14 15 while True: 16 print ‘waiting for connection...‘ 17 tcpCliSock, addr = tcpSerSock.accept() # accept() block and wait 18 print ‘...connected from:‘, addr 19 20 while True: 21 data = tcpCliSock.recv(BUFSIZ) 22 if not data: 23 break 24 tcpCliSock.send(‘[%s] %s‘ % (ctime(), data)) 25 26 tcpCliSock.close() 27 tcpSerSock.close() # don‘t forget to close
核心提示:创建线程来处理客户的请求。
创建一个新的线程或进程来完成与客户的通讯是一种非常常用的手段。SocketServer 模块
是一个基于socket 模块的高级别的套接字通讯模块,它支持在新的线程或进程中处理客户的请求。
创建TCP 客户端
cc = socket() # 创建客户套接字 cc.connect() # 尝试连接服务器 comm_loop: # 通讯循环 cs.send()/cs.recv() # 对话(发送/接收) cs.close() # 关闭客户套接字
#!/usr/bin/env python from socket import * HOST = ‘localhost‘ PORT = 21569 # should correspond to the server socket BUFSIZ = 1024 # 1K ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) tcpCliSock.connect(ADDR) while True: data = raw_input(‘>‘) if not data: break tcpCliSock.send(data) data = tcpCliSock.recv(BUFSIZ) if not data: break print data tcpCliSock.close()
执行TCP 服务器和客户端
创建一个UDP 服务器
ss = socket() # 创建服务器套接字 ss.bind() # 把地址绑定到套接字上 ss.listen() # 监听连接 inf_loop: # 服务器无限循环 cs.recvfrom()/cs.sendfrom() # 对话(接收与发送) ss.close() # 关闭服务器套接字(可选)
1 #!/usr/bin/env python 2 3 from socket import * 4 from time import ctime 5 6 HOST = ‘‘ 7 PORT = 21567 8 BUFSIZ = 1024 9 ADDR = (HOST, PORT) 10 11 udpSerSock = socket(AF_INET, SOCK_DGRAM) 12 udpSerSock.bind(ADDR) 13 14 while True: 15 print ‘waiting for message...‘ 16 data, addr = udpSerSock.recvfrom(BUFSIZ) 17 udpSerSock.sendto(‘[%s] %s‘ % (18 ctime(), data), addr) 19 20 udpSerSock.close()
创建UDP 客户端
cc = socket() # 创建客户套接字 comm_loop: # 通讯循环 cs.sendto()/cs.recvfrom() # 对话(发送/接收) cs.close() # 关闭客户套接字
1 #!/usr/bin/env python 2 3 from socket import * 4 5 HOST = ‘localhost‘ 6 PORT = 21567 7 BUFSIZ = 1024 8 ADDR = (HOST, PORT) 9 10 udpCliSock = socket(AF_INET, SOCK_DGRAM) 11 12 while True: 13 data = raw_input(‘>‘) 14 if not data: 15 break 16 udpCliSock.sendto(data, ADDR) 17 data, ADDR = udpCliSock.recvfrom(BUFSIZ) 18 if not data: 19 break 20 print data, ADDR 21 udpCliSock.close()
执行UDP 服务器和客户端
套接字模块属性
socket 模块属性 属性名字 描述 数据属性 AF_UNIX, AF_INET, AF_INET6 Python 支持的套接字家族 SO_STREAM, SO_DGRAM 套接字类型(TCP = 流,UDP = 数据报) has_ipv6 表示是否支持IPv6 的标志变量 异常 error 套接字相关错误 herror 主机和地址相关的错误 gaierror 地址相关的错误 timeout 超时 函数 socket() 用指定的地址家族,套接字类型和协议类型(可选)创建一个套接字对象 socketpair() 用指定的地址家族,套接字类型和协议类型(可选)创建一个套接字对象 fromfd 用一个已经打开的文件描述符创建一个套接字对象 数据属性 ssl() 在套接字初始化一个安全套接字层(SSL)。不做证书验证。 getaddrinfo() 得到地址信息 getfqdn() 返回完整的域的名字 gethostname() 得到当前主机名 gethostbyname() 由主机名得到对应的ip 地址 gethostname_ex() gethostname()的扩展版本,返回主机名,主机所以的别名和IP 地址列表 gethostbyaddr() 由IP 地址得到DNS 信息,返回一个类型gethostbyname_ex()的3元组 getprotobyname() 由协议名(如‘TCP’)得到对应的号码 getservbyname()/getservbyport() 由服务名得到对应的端口号或相反 ntohl()/ntohs() 把一个整数由网络字节序转为主机字节序 htonl()/htons() 把一个整数由主机字节序转为网络字节序 inet_aton()/inet_ntoa() 把IP 地址转为32位整数,以及反向函数。(仅对IPv4地址有效) inet_pton()/inet_ntop() 把IP 地址转为二进制格式以及反向函数。(仅对IPv4地址有效) getdefaulttimeout()/setdefaulttimeout() 得到/设置默认的套接字超时时间,单位秒(浮点数)
16.4 *SocketServer 模块
SocketServer 是标准库中一个高级别的模块。用于简化网络客户与服务器的实现。
为了要隐藏实现的细节。我们现在写程序时会使用类,这是与之前代码的另一个不同。用面向 对象的方法可以帮助我们更好的组织数据与逻辑功能。
SocketServer 模块的类 类 描述 BaseServer 包含服务器的核心功能与混合(mix-in)类的钩子功能。这个类用于 派生,不要直接生成 TCPServer/UDPServer 基本的网络同步 TCP/UDP 服务器 UnixStreamServer/UnixDatagramServer 基本的基于文件同步的 TCP/UDP 服务器 ForkingMixIn/ThreadingMixIn 实现了核心的进程化或线程化的功能,用于 与服务器类进行混合(mix-in),已提供一些异步特性。不要直接生成这个类的对象 ForkingTCPServer/ForkingUDPServer ForkingMixIn 和 TCPServer/UDPServer 的组合 ThreadingTCPServer/ThreadingUDPServer ThreadingMixIn 和 TCPServer/UDPServer 的组合 BaseRequestHandler 包含处理服务请求的核心功能。只用于派生新的类,不要直接 生成这个类的对象,可以考虑使用 StreamRequestHandler 或 DatagramRequestHandler StreamRequestHandler/DatagramRequestHandler TCP/UDP 服务器的请求处理类的一个实现
创建一个SocketServerTCP 服务器
1 #!/usr/bin/env python 2 3 from SocketServer import (TCPServer as TCP, 4 StreamRequestHandler as SRH) 5 from time import ctime 6 7 HOST = ‘‘ 8 PORT = 21567 9 ADDR = (HOST, PORT) 10 11 class MyRequestHandler(SRH): 12 def handle(self): 13 print ‘...connected from:‘, self.client_address 14 self.wfile.write(‘[%s] %s‘ % (ctime(),15 self.rfile.readline()) 16 17 tcpServ = TCP(ADDR, MyRequestHandler) 18 print ‘waiting for connection...‘ 19 tcpServ.serve_forever()
创建SocketServerTCP 客户端
1 #!/usr/bin/env python 2 3 from socket import * 4 5 HOST = ‘localhost‘ 6 PORT = 21567 7 BUFSIZ = 1024 8 ADDR = (HOST, PORT) 9 10 tcpCliSock = socket(AF_INET, SOCK_STREAM) 11 tcpCliSock.connect(ADDR) 12 while True: 13 data = raw_input(‘>‘) 14 if not data: 15 break 16 tcpCliSock.send(‘%s\r\n‘ % data) 17 data = tcpCliSock.recv(BUFSIZ) 18 if not data: 19 break 20 print data.strip() 21 tcpCliSock.close()
16.5 Twisted 框架介绍
Twisted 是一个完全事件驱动的网络框架。它允许你使用和开发完全异步的网络应用程序和协议。
在写本书的时候,它还不是Python 标准库的一部分,要使用它,你必需另外下载并安装它。它为你
创建一个完整系统提供了很大的帮助。系统中可以有:网络协议,线程, 安全和认证,聊天/即时通讯,
数据库管理,关系数据库集成,网页/互联网,电子邮件,命令行参数,图形界面集成等。
16.6 相关模块
网络/套接字编程相关模块 模块 描述 socket 底层网络接口。 asyncore/asynchat 为能异步处理客户请求的网络应用程序提供底层功能。 select 在单线程网路服务器中,管理多个套接字连接。 SocketServer 包含了网络应用服务器所需要的高级别模块。提供了完整的进程和线程的版本。