Python 核心编程(第二版)——网络编程

1. 介绍

服务器是一个软件或硬件,用于提供客户需要的“服务”。服务器存在的唯一目的就是等待客户的请求,给这些客户服务,然后再等待其它的请求。

2. 套接字:通讯端点

套接字是一种具有之前所说的“通讯端点”概念的计算机网络数据结构。网络化的应用程序在开始任何通讯之前都必需要创建套接字。就像电话的插口一样,没有它就完全没办法通讯。套接字有两种,分别是基于文件型的和基于网络型的。

Unix 套接字是我们要介绍的第一个套接字家族。其“家族名”为AF_UNIX(在POSIX1.g 标准中也叫AF_LOCAL),表示“地址家族:UNIX”。由于两个进程都运行在同一台机器上,而且这些套接字是基于文件的。所以,它们的底层结构是由文件系统来支持的。

另一种套接字是基于网络的,它有自己的家族名字:AF_INET,或叫“地址家族:Internet”。还有一种地址家族AF_INET6 被用于网际协议第6 版(IPv6)寻址上。所有地址家族中,AF_INET 是使用最广泛的一个。Python 2.5 中加入了一种Linux 套接字的支持:AF_NETLINK(无连接[见下])套接字家族让用户代码与内核代码之间的IPC 可以使用标准BSD 套接字接口。而且,相对之前那些往操作系统中加入新的系统调用,proc 文件系统支持或是“IOCTL”等笨重的方案来说,这种方法显得更为优美,更为安全。

合法的端口号范围为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。

与虚电路完全相反的是数据报型的无连接套接字。这意味着,无需建立连接就可以进行通讯。但这时,数据到达的顺序,可靠性及数据不重复性就无法保证了。数据报会保留数据边界,这就表示,数据不会像面向连接的协议那样被拆分成小块。

实现这种连接的主要协议就是用户数据报协议(即UDP)。要创建UDP 套接字就得在创建的时候,指定套接字类型为SOCK_DGRAM。由于这些套接字使用Internet 协议来查找网络中的主机,这样形成的整个系统,一般会由这两个协议(UDP 和IP)来提及,即UDP/IP。

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)

由于socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用'from socket import *',我们就把socket 模块里的所有属性都带到我们的命名空间里了,这样能大幅减短我们的代码。

当我们创建了套接字对象后,所有的交互都将通过对该套接字对象的方法调用进行。

套接字对象的常用函数
函数 描述
服务器端套接字函数
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 Socket Methods
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字关连的文件

核心提示:在运行网络应用程序时,最好在不同的电脑上执行服务器和客户端的程序。

创建一个TCP服务器:

1 ss = socket() # 创建服务器套接字
2 ss.bind() # 把地址绑定到套接字上
3 ss.listen() # 监听连接
4 inf_loop: # 服务器无限循环
5 cs = ss.accept() # 接受客户的连接
6 comm_loop: # 通讯循环
7 cs.recv()/cs.send() # 对话(接收与发送)
8 cs.close() # 关闭客户套接字
9 ss.close() # 关闭服务器套接字(可选)
View Code

所有的套接字都用socket.socket()函数来创建。服务器需要“坐在某个端口上”等待请求。所以它们必需要“绑定”到一个本地的地址上。由于TCP 是一个面向连接的通讯系统,在TCP 服务器可以开始工作之前,要先完成一些设置。TCP 服务器必需要“监听”(进来的)连接,设置完成之后,服务器就可以进入无限循环了。一个简单的(单线程的)服务器会调用accept()函数等待连接的到来。默认情况下,accept()函数是阻塞式的,即程序在连接到来之前会处于挂起状态。套接字也支持非阻塞模式。一旦接收到一个连接,accept()函数就会返回一个单独的客户的套接字用于后续的通讯。使用新的客户套接字就像把客户的电话转给一个客户服务人员。当一个客户打电话进来的时候,总机接了电话,然后把电话转到合适的人那里来处理客户的需求。这样就可以空出总机,也就是最初的那个服务器套接字,于是,话务员就可以等待下一个电话(客户的请求),与此同时,前一个客户与对应的客户服务人员在另一条线路上进行着他们自己的对话。同样的,当一个请求到来时,要创建一个新的端口,然后直接在那个端口上与客户对话,这样就可以空出主端口来接受其它客户的连接。在临时套接字创建好之后,通讯就可以开始了。客户与服务器都使用这个新创建的套接字进行数据的发送与接收,直到通讯的某一方关闭了连接或发送了一个空字符串之后,通讯就结束了。

核心提示:创建线程来处理客户的请求。

创建一个新的线程或进程来完成与客户的通讯是一种非常常用的手段。SocketServer 模块是一个基于socket 模块的高级别的套接字通讯模块,它支持在新的线程或进程中处理客户的请求。

创建TCP客户端:创建TCP 客户端相对服务器来说更为容易。

1 cs = socket() # 创建客户套接字
2 cs.connect() # 尝试连接服务器
3 comm_loop: # 通讯循环
4 cs.send()/cs.recv() # 对话(发送/接收)
5 cs.close() # 关闭客户套接字
View Code

所有的套接字都由socket.socket()函数创建。在客户有了套接字之后,马上就可以调用connect()函数去连接服务器。连接建立后,就可以与服务器开始对话了。在对话结束后,客户就可以关闭套接字,结束连接。

运行客户端与服务器程序:服务器是一个被动端,它先创建自己然后被动地等待连接。而客户则是主动端,由它主动地建立一个连接。所以:要先开服务器,后开客户。

核心提示:优美的退出和调用服务器的close()函数

“友好地”退出的一个方法就是把服务器的无限循环放在一个try-except 语句的try 子句当中,并捕获EOFError 和KeyboardInterrupt 异常。在异常处理子句中,调用close()函数关闭服务器的套接字。

创建一个UDP服务器:由于UDP 服务器不是面向连接的,所以不用像TCP 服务器那样做那么多设置工作。事实上,并不用设置什么东西,直接等待进来的连接就好了。

1 ss = socket() # 创建一个服务器套接字
2 ss.bind() # 绑定服务器套接字
3 inf_loop: # 服务器无限循环
4 cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
5 ss.close() # 关闭服务器套接字
View Code

UDP 和TCP 服务器的另一个重要的区别是,由于数据报套接字是无连接的,所以无法把客户的连接交给另外的套接字进行后续的通讯。这些服务器只是接受消息,需要的话,给客户返回一个结果就可以了。

创建一个UDP客户端:

1 cs = socket() # 创建客户套接字
2 comm_loop: # 通讯循环
3 cs.sendto()/cs.recvfrom() # 对话(发送/接收)
4 cs.close() # 关闭客户套接字
View Code

UDP 客户的循环基本上与TCP 客户的完全一样。唯一的区别就是,我们不用先去跟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 地址
gethostbyname_ex() gethostbyname()的扩展版本,返回主机名,主机所有的别名和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() 得到/设置默认的套接字超时时间,单位秒(浮点数)

4. *SocketServer 模块

SocketServer 是标准库中一个高级别的模块。用于简化网络客户与服务器的实现。

SocketServer 模块的类
描述
BaseServer 包含服务器的核心功能与混合(mix-in)类的钩子功能。这个类用于派生,不要直接生成。这个类的类对象,可以考虑使用TCPServer 和UDPServer。
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, StreamRequestHandler as SRH)
 4  from time import ctime
 5 
 6 HOST = ''
 7 PORT = 21567
 8 ADDR = (HOST, PORT)
 9 
10 class MyRequestHandler(SRH):
11     def handle(self):
12         print '...connected from:', self.client_address
13         self.wfile.write('[%s] %s' % (ctime(),self.rfile.readline()))
14 
15 tcpServ = TCP(ADDR, MyRequestHandler)
16 print 'waiting for connection...'
17 tcpServ.serve_forever()
View Code

创建一个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 while True:
11     tcpCliSock = socket(AF_INET, SOCK_STREAM)
12     tcpCliSock.connect(ADDR)
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()
View Code

5. Twisted框架介绍

 Twisted 是一个完全事件驱动的网络框架。它允许你使用和开发完全异步的网络应用程序和协议。

创建一个Twisted Reactor TCP服务器:

 1 #!/usr/bin/env python
 2 
 3 from twisted.internet import protocol, reactor
 4 from time import ctime
 5 
 6 PORT = 21567
 7 
 8 class TSServProtocol(protocol.Protocol):
 9     def connectionMade(self):
10         clnt = self.clnt = self.transport.getPeer().host
11         print '...connected from:', clnt
12     def dataReceived(self, data):
13         self.transport.write('[%s] %s' % (ctime(), data))
14 
15 factory = protocol.Factory()
16 factory.protocol = TSServProtocol
17 print 'waiting for connection...'
18 reactor.listenTCP(PORT, factory)
19 reactor.run()
View Code

创建一个Twisted Reactor TCP客户端:

 #!/usr/bin/env python

from twisted.internet import protocol, reactor

HOST = 'localhost'
PORT = 21567

class TSClntProtocol(protocol.Protocol):
    def sendData(self):
    data = raw_input('> ')
    if data:
        print '...sending %s...' % data
        self.transport.write(data)
    else:
        self.transport.loseConnection()

def connectionMade(self):
    self.sendData()

def dataReceived(self, data):
    print data
    self.sendData()

class TSClntFactory(protocol.ClientFactory):
    protocol = TSClntProtocol
    clientConnectionLost = clientConnectionFailed = \
    lambda self, connector, reason: reactor.stop()

    reactor.connectTCP(HOST, PORT, TSClntFactory())
    reactor.run()

6. 相关模块

select 模块通常在底层套接字程序中与socket 模块联合使用。它提供的select()函数可以同时管理多个套接字对象。它最有用的功能就是同时监听多个套接字的连接。select()函数会阻塞,直到有至少一个套接字准备好要进行通讯的时候才退出。

async*和SocketServer 模块在创建服务器方面都提供了高层次的功能。由于是基于socket 和(或)select 模块,封装了所有的底层的代码,它们使得你可以快速开发客户/服务器的系统。你所需要做的只是从适当的基类中派生出一个新的类。所有的东西就已经就绪了。

网络/套接字编程相关模块
模块 描述
socket 底层网络接口。
asyncore/asynchat 为能异步处理客户请求的网络应用程序提供底层功能。
select 在单线程网络服务器程序中,管理多个套接字连接。
SocketServer 包含了写网络应用程序服务器所需要的高级别模块。提供了完整的进程和线程的版本。
posted @ 2017-10-24 15:11  Christal_11  阅读(158)  评论(0编辑  收藏  举报