第三章 网络编程

终于学到网络编程了!

先上图和程序:

这是今天写的TCP的实现 服务器和客户端分别在两台电脑

这是服务器图:

这是服务器程序:

 1 #-*- coding:utf-8 -*-
 2 from socket import *        #导入socket所有属性
 3 from time import ctime        #导入ctime()
 4 
 5 
 6 host = ''                    #HOST 变量为空,表示bind()函数可以绑定在所有有效的地址上。
 7 port = 21000                #设置端口
 8 bufsize = 1024                #设置缓冲大小
 9 addr = (host,port)
10 flag = 0
11 
12 tcpsersock = socket(AF_INET,SOCK_STREAM)    #创建服务器套接字
13 tcpsersock.bind(addr)            #把地址绑定在套接字上
14 tcpsersock.listen(2)            #监听链接
15 try:
16     while True:
17         if flag!=0 :
18             print u'Sean_H is offline'
19         print "waiting for connection........"
20         tcpCliSock,addr1 = tcpsersock.accept()    ##接受客户的链接
21         #print "...connected from",addr1
22         print u"Sean_H is online:"
23         flag = flag+1
24         while True:
25             data = tcpCliSock.recv(bufsize)    #对话接受
26             if not data:
27                 break
28             print '-----------------'    
29             print ctime()    
30             print data    
31             data = raw_input(">>>")
32             print 
33             tcpCliSock.send("<fanyear>: %s"%(data))        #对话发送
34             
35 except (KeyboardInterupt, SystemExit):                #感觉这里写错了 有发现的朋友请指出来 谢谢
36     print 'out'
37     tcpCliSock.close
38     tcpsersock.close        

 

1.1用套接字进行网络编程

下面是些基础概念:

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

  硬件服务器架构
  打印(机)服务是一个硬件服务器的例子。它们处理打印任务,并把任务发给相连的打印机(或其它打印设备)。这样的电脑一般是可以通过网络访问并且客户机器可以远程发送打印请求给它。

  软件服务器架构
  软件服务器提供的服务主要是程序的运行,数据的发送与接收,合并,升级或其它的程序或数据的操作。

  如今,最常用的软件服务器是Web 服务器。一台机器里放一些网页或Web 应用程序,然后启动服务。这样的服务器的任务就是接受客户的请求,把网页发给客户(如用户计算机上的浏览器),然

后等待下一个客户请求。这些服务启动后的目标就是“永远运行下去”。虽然它们不可能实现这样的目标,但只要没有关机或硬件出错等外力干扰,它们就能运行非常长的一段时间。

  通讯端点:
  在完成服务之前,服务器必需要先完成一些设置动作。先要创建一个通讯端点,让服务器能“监听”请求。你可以把我们的服务器比做一个公司的接待员或回答公司总线电话的话务员,一旦电话和设备安装完成,话务员也到了之后,服务就可以开始了。在网络世界里,基本上也是这样——一旦通讯端点创建好之后,我们在“监听”的服务器就可以进入它那等待和处理客户请求的无限循环中了。当然,我们也不能忘记在信纸上,杂志里,广告中印上公司的电话号码。否则,就没有人会打电话进来了!

  嵌接字:
  套接字是一种具有之前所说的“通讯端点”概念的计算机网络数据结构。网络化的应用程序在开始任何通讯之前都必需要创建套接字。

  套接字有两种,分别是基于文件型的和基于网络型的。
  

  主要的两种套接字家族:


  Unix 套接字是我们要介绍的第一个套接字家族。其“家族名”为AF_UNIX(在POSIX1.g 标准中也叫AF_LOCAL),表示“地址家族:UNIX”。包括Python 在内的大多数流行平台上都使用术语“地址
家族”及其缩写“AF”。而老一点的系统中,地址家族被称为“域”或“协议家族”,并使用缩写“PF”而不是“AF”。同样的,AF_LOCAL(在2000-2001 年被列为标准)将会代替AF_UNIX。不过,为了向后
兼容,很多系统上,两者是等价的。Python 自己则仍然使用AF_UNIX。
  由于两个进程都运行在同一台机器上,而且这些套接字是基于文件的。所以,它们的底层结构是由文件系统来支持的。这样做相当有道理,因为,同一台电脑上,文件系统的确是不同的进程都
能访问的。
  另一种套接字是基于网络的,它有自己的家族名字:AF_INET,或叫“地址家族:Internet”。还有一种地址家族AF_INET6 被用于网际协议第6 版(IPv6)寻址上。

Python 只支持AF_UNIX,AF_NETLINK,和AF_INET 家族。由于我们只关心网络编程,所以在本章的大部分时候,我们都只用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。


  无连接
  与虚电路完全相反的是数据报型的无连接套接字。这意味着,无需建立连接就可以进行通讯。但这时,数据到达的顺序,可靠性及数据不重复性就无法保证了。数据报会保留数据边界,这就表
示,数据不会像面向连接的协议那样被拆分成小块。
  使用数据报来传输数据就像邮政服务一样。邮件和包裹不一定会按它们发送的顺序到达。事实上,它们还有可能根本到不了!而且,由于网络的复杂性,数据还可能被重复传送。
既然数据报有这么多缺点,为什么还要使用它呢?(一定有什么方面能胜过流套接字的!)由于面向连接套接字要提供一些保证,以及要维持虚电路连接,这都是很重的额外负担。数据报没有这
些负担,所以它更“便宜”。通常能提供更好的性能,更适合某些应用场合。
  实现这种连接的主要协议就是用户数据报协议(即UDP)。要创建UDP 套接字就得在创建的时候,指定套接字类型为SOCK_DGRAM。SOCK_DGRAM 这个名字,也许你已经猜到了,来自于单词“datagram”
(“数据报”)。由于这些套接字使用Internet 协议来查找网络中的主机,这样形成的整个系统,一般会由这两个协议(UDP 和IP)来提及,即UDP/IP。


2.1Python 中的网络编程

本节我们将主要使用socket模块,其中socket()函数被使用来创建套接字。套接字也有自己的一套函数来提供网络通讯

1: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 模块里的所有属性都带到我们的命名空间里了,这样能
大幅减短我们的代码。

tcpSock = socket(AF_INET, SOCK_STREAM)

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


套接字对象(内建)方法:

表3.1 套接字对象的常用函数
函数 描述
服务器端套接字函数

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()a      设置阻塞套接字操作的超时时间
s.gettimeout()a     得到阻塞套接字操作的超时时间面向文件的套接字的函数
s.fileno()      套接字的文件描述符
s.makefile()      创建一个与该套接字关连的文件

a. Python 2.3 版本新加入的函数

核心提示:在运行网络应用程序时,最好在不同的电脑上执行服务器和客户端的程序。在本章的例子中,你将大量看到代码和输出中提及“localhost”主机和127.0.0.1 IP 地址。
我们的例子把客户与服务器运行在同一台电脑上,我们建议读者改掉主机名,并把代码放到不同的电脑上运行。眼见着自己的代码在不同的电脑上进行通讯,这一时刻,你更能体会到开发的乐趣。

 

创建TCP服务器:
来来来!
嗨起来!!

我们首先将给出一个关于如何创建一个通用的TCP 服务器的伪代码,然后解释我们都做了些什么。要注意的是,这只是设计服务器的一种方法,当你对服务器的设计有了一定的了解之
后,你就能用你所希望的方式来修改这段伪代码:

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

所有的套接字都用socket.socket()函数来创建。服务器需要“坐在某个端口上”等待请求。所以它们必需要“绑定”到一个本地的地址上。由于TCP 是一个面向连接的通讯系统,在TCP 服务器
可以开始工作之前,要先完成一些设置。TCP 服务器必需要“监听”(进来的)连接,设置完成之后,服务器就可以进入无限循环了。
一个简单的(单线程的)服务器会调用accept()函数等待连接的到来。默认情况下,accept()函数是阻塞式的,即程序在连接到来之前会处于挂起状态。套接字也支持非阻塞模式。请参阅相关
文档或操作系统手册以了解为何及如何使用非阻塞套接字。


一旦接收到一个连接,accept()函数就会返回一个单独的客户的套接字用于后续的通讯。使用新的客户套接字就像把客户的电话转给一个客户服务人员。当一个客户打电话进来的时候,总机接
了电话,然后把电话转到合适的人那里来处理客户的需求。
这样就可以空出总机,也就是最初的那个服务器套接字,于是,话务员就可以等待下一个电话(客户的请求),与此同时,前一个客户与对应的客户服务人员在另一条线路上进行着他们自己的对
话。同样的,当一个请求到来时,要创建一个新的端口,然后直接在那个端口上与客户对话,这样就可以空出主端口来接受其它客户的连接。

核心提示:创建线程来处理客户的请求。
我们不打算在例子实现这样的功能。但是,创建一个新的线程或进程来完成与客户的通讯是一种非常常用的手段。SocketServer 模块是一个基于socket 模块的高级别的套接字通讯模块,它支持
在新的线程或进程中处理客户的请求。建议读者参阅相关文章及第17 章多线程编程的习题,以了解更多的信息。
在临时套接字创建好之后,通讯就可以开始了。客户与服务器都使用这个新创建的套接字进行数据的发送与接收,直到通讯的某一方关闭了连接或发送了一个空字符串之后,通讯就结束了。
在代码中,当客户连接关闭后,服务器继续等待下一个客户的连接。代码的最后一行,会把服务器的套接字关闭。由于服务器处在无限循环中,不可能会走到这一步,所以,这一步是可选的。
我们写这一句话的主要目的是要提醒读者,在设计一个更智能的退出方案的时候,比方说,服务器被通知要关闭的时,要确保close()函数会被调用。

在例16.1 tsTserv.py 文件中,会创建一个TCP 服务器程序,这个程序会把客户发送过来的字符串加上一个时间戳(格式:'[时间]数据')返回给客户。

 

 

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 tcpSerSock = socket(AF_INET, SOCK_STREAM)
12 tcpSerSock.bind(ADDR)
13 tcpSerSock.listen(5)
14
15 while True:
16 print 'waiting for connection...'
17 tcpCliSock, addr = tcpSerSock.accept()
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' % (
25 ctime(), data))
26
27 tcpCliSock.close()
28 tcpSerSock.close()

逐行解释
1-4 行
第1 行是Unix 的启动信息行,随后我们导入了time.ctime()函数和socket 模块的所有属性。

6-13 行
HOST 变量为空,表示bind()函数可以绑定在所有有效的地址上。我们还选用了一个随机生成的未被占用的端口号。在程序中,我们把缓冲的大小设定为1K。你可以根据你的网络情况和应用的需
要来修改这个大小。listen()函数的参数只是表示最多允许多少个连接同时连进来,后来的连接就会被拒绝掉。
TCP 服务器的套接字(tcpSerSock)在第11 行被生成。随后把套接字绑定到服务器的地址上,然后开始TCP 监听。

15-28 行
在进入到服务器的无限循环后,我们(被动地)等待连接的到来。当有连接时,我们进入对话循环,等待客户发送数据。如果消息为空,表示客户已经退出,那就再去等待下一个客户的连接。得
到客户的消息后,我们在消息前加一个时间戳然后返回。最后一行不会被执行到,放在这里用于提醒读者,在服务器要退出的时候,要记得调用close()函数。

 


创建TCP客户端

创建TCP 客户端相对服务器来说更为容易。与TCP 服务器那段类似,我们也是先给出伪代码及其解释,然后再给出真正的代码。

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


如之前所说,所有的套接字都由socket.socket()函数创建。在客户有了套接字之后,马上就可以调用connect()函数去连接服务器。连接建立后,就可以与服务器开始对话了。在对话结束后,客
户就可以关闭套接字,结束连接。在例16.2 中,我们给出了tcTlnt.py 的代码。程序连接到服务器,提示用户输入要传输的数据,然后显示服务器返回的加了时间戳的结果。


逐行解释
1-3 行
第1 行是Unix 的启动信息行,随后我们导入了socket 模块的所有属性。

例16.2 TCP 时间戳客户端(tsTclnt.py)
创建一个TCP 客户端,程序会提示用户输入要传给服务器的信息,显示服务器返回的加了时间戳的结果。


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
13 while True:
14 data = raw_input('> ')
15 if not data:
16 break
17 tcpCliSock.send(data)
18 data = tcpCliSock.recv(BUFSIZ)
19 if not data:
20 break
21 print data
22
23 tcpCliSock.close()

5-11 行
HOST 和PORT 变量表示服务器的主机名与端口号。由于我们在同一台电脑上进行测试,所以HOST里放的是本机的主机名(如果你的服务器运行在其它电脑上,要做相应的修改)。端口号要与服务器
上的设置完全相同(不然就没办法通讯了)。缓冲区的大小还是设为1K。TCP 客户套接字(tcpCliSock)在第10 行创建。然后就去连接服务器。

13-23 行
客户端也有一个无限循环,但这跟服务器的那个不期望退出的无限循环不一样。客户端的循环在以下两个条件的任意一个发生后就退出:用户没有输入任何内容(14-16 行),或服务器由于某种
原因退出,导致recv()函数失败(18-20 行)。否则,在一般情况下,客户端会把用户输入的字符串发给服务器进行处理,然后接收并显示服务器传回来的,加了时间戳的字符串。

 

接下来就到了激动人心的时刻!!
运行我们的客户端与服务器程序


先开服务器 后开客户端

我们在运行客户和服务器的例子中,使用了同一台电脑。其实也可以把服务器放在其它的电脑上,这时,只要改改主机名就好了。(看到自己写的第一个网络程序运行在不同的电脑上,那是多么
激动人心的事啊)。

下面就是客户端的输入与输出,不输入数据,直接按回车键就可以退出程序:
$ tsTclnt.py
> hi
[Sat Jun 17 17:27:21 2006] hi
> spanish inquisition
[Sat Jun 17 17:27:37 2006] spanish inquisition
>
$
服务器的输出主要用于调试目的:
$ tsTserv.py
waiting for connection...
...connected from: ('127.0.0.1', 1040)
waiting for connection...
当有客户连接上来的时候,会显示一个“... connected from ...”信息。在客户接受服务的时候,服务器又回去等待其它客户的连接。在从服务器退出的时候,我们要跳出那个无限循环,这
时会触发一个异常。避免这种错误的方法是采用一种更优美的退出方式。

核心提示:优美的退出和调用服务器的close()函数
“友好地”退出的一个方法就是把服务器的无限循环放在一个try-except 语句的try 子句当中,并捕获EOFError 和KeyboardInterrupt 异常。在异常处理子句中,调用close()函数关闭服务
器的套接字。这个简单的网络应用程序的有趣之处并不仅仅在于我们演示了数据怎样从客户传到服务器,然后又传回给客户,而且我们还把这个服务器当成了“时间服务器”,因为,字符串中的时间戳完全是
来自于服务器的。

 

 

 


创建一个UDP服务器

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

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

从伪代码中可以看出,使用的还是那套先创建套接字然后绑定到本地地址(主机/端口对)的方法。无限循环中包含了从客户那接收消息,返回加了时间戳的结果和回去等下一个消息这三步。
同样的,由于代码不会跳出无限循环,所以,close()函数调用是可选的。我们写这一句话的原因是要提醒读者,在设计一个更智能的退出方案的时候,要确保close()函数会被调用。

例16.3 UDP 时间戳服务器 (tsUserv.py)创建一个能接收客户的消息,在消息前加一个时间戳后返回的UDP 服务器。

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' % (ctime(), data), addr)
19 print '...received from and returned to:', addr
20
21 udpSerSock.close()

UDP 和TCP 服务器的另一个重要的区别是,由于数据报套接字是无连接的,所以无法把客户的连接交给另外的套接字进行后续的通讯。这些服务器只是接受消息,需要的话,给客户返回一个结果
就可以了。
例16.3 的tsUserv.py 是之前那个TCP 服务器的UDP 版本,它接收客户的消息,加时间戳后返回给客户。

逐行解释
1-4 行
就像TCP 服务器的设置那样,在Unix 的启动信息行后,我们导入了time.ctime()函数和socket

6-12 行
HOST 和PORT 变量与之前完全一样。socket()函数的调用有一些不同,我们现在要的是一个数据报/UDP 的套接字类型。不过bind()函数还是跟TCP 版本的一样。同样地,由于UDP 是无连接的,就
不用调用listen()函数来监听进来的连接了。

14-21 行
在进入到服务器的无限循环后,我们(被动地)等待(数据报)消息的到来。当有消息进来时,就处理它(在前面加时间戳),把结果返回回去,然后再去等等下一个消息。就像之前一样,那个close()
函数只是一个演示而已。


创建一个UDP 客户端

这一节中介绍的4 段程序中,下面的这段UDP 客户的代码是最短的。伪代码如下:

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

在套接字对象创建好之后,我们就进入一个与服务器的对话循环。在通讯结束后,套接字就被关闭了。tsUclnt.py 真实的代码在例16.4 中给出。

逐行解释
1-3 行
还是跟TCP 版本的客户一样,在Unix 的启动信息行后,我们导入了socket 模块的所有属性。

5-10 行
因为我们的服务器也是运行在本机,我们的客户还是使用本机和相同的端口号。自然地,缓冲的大小也还是1K。创建套接字的方法跟UDP 服务器中的一样。

12-22 行
UDP 客户的循环基本上与TCP 客户的完全一样。唯一的区别就是,我们不用先去跟UDP 服务器建立连接,而是直接把消息发送出去,然后等待服务器的回复。得到加了时间戳的字符串后,把它显
示到屏幕上,然后再继续其它的消息。在输入结束后,退出循环,关闭套接字。

例16.4 UDP 时间戳客户 (tsUclnt.py)

创建一个UDP 客户端,程序会提示用户输入要传给服务器的信息,显示服务器返回的加了时间戳的结果。

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 dataudpCliSock.close()
21
22 udpCliSock.close()


套接字模块属性:

除了我们已经很熟悉的socket.socket()函数之外,socket 模块还有很多属性可供网络应用程序使用。表16.2 中列出了最常用的几个。
请参考Python 手册中socket 模块的文档以了解更多的信息。

表16.2 socket 模块属性
属性名字 描述
数据属性
AF_UNIX, AF_INET, AF_INET6a Python 支持的套接字家族
SO_STREAM, SO_DGRAM 套接字类型 (TCP = 流, UDP = 数据报)
has_ipv6b 表示是否支持IPv6 的标志变量


异常
error 套接字相关错误
herrora 主机和地址相关的错误
gaierrora 地址相关的错误
timeoutb 超时

函数
socket() 用指定的地址家族,套接字类型和协议类型(可选)创建一个套接字对象
socketpair()c 用指定的地址家族,套接字类型和协议类型(可选)创建一对套接字对象
fromfd() 用一个已经打开的文件描述符创建一个套接字对象

数据属性
ssl()d 在套接字初始化一个安全套接字层(SSL)。不做证书验证。
getaddrinfo()a 得到地址信息
getfqdn()e 返回完整的域的名字
gethostname() 得到当前主机名

gethostbyname() 由主机名得到对应的ip 地址
gethostbyname_ex() gethostbyname()的扩展版本,返回主机名,主机所有的别名和IP 地址列表。
gethostbyaddr() 由IP 地址得到DNS 信息,返回一个类似gethostbyname_ex()的3 元组。
getprotobyname() 由协议名(如'tcp')得到对应的号码。
getservbyname()/ 由服务名得到对应的端口号或相反
getservbyport() 两个函数中,协议名都是可选的。
ntohl()/ntohs() 把一个整数由网络字节序转为主机字节序
htonl()/htons() 把一个整数由主机字节序转为网络字节序
inet_aton()/ 把IP 地址转为32 位整型,以及反向函数。(仅对IPv4 地址有效)
inet_ntoa()
inet_pton()/ 把IP 地址转为二进制格式以及反向函数。(仅对IPv4 地址有效)
inet_ntop()b
getdefaulttimeout()/ 得到/设置默认的套接字超时时间,单位秒(浮点数)
setdefaulttimeout()b
a. Python 2.2 新增.
b. Python 2.3 新增.
c. Python 2.4 新增.
d. Python 1.6 新增.
e. Python 2.0 新增.

 

 


SocketServer 模块:

SocketServer 是标准库中一个高级别的模块。用于简化网络客户与服务器的实现。模块中,已经实现了一些可供使用的类。


表16.3 SocketServer 模块的类
类 描述
BaseServer 包含服务器的核心功能与混合(mix-in)类的钩子功能。这个类用于派生,不要直接生成这个类的类对象,可以考虑使用TCPServer 和UDPServer。
TCPServer/ 基本的网络同步TCP/UDP 服务器
UDPServer
UnixStreamServer/ 基本的基于文件同步TCP/UDP 服务器
UnixDatagramServer
Edit By Vheavens
Edit By Vheavens
ForkingMixIn/ 实现了核心的进程化或线程化的功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。
ThreadingMixIn 不要直接生成这个类的对象
ForkingTCPServer/ ForkingMixIn 和TCPServer/UDPServer 的组合
ForkingUDPServer
ThreadingTCPServer/ ThreadingMixIn 和TCPServer/UDPServer 的组合
ThreadingUDPServer
BaseRequestHandler 包含处理服务请求的核心功能。只用于派生新的类,不要直接生成这个类的对象,可以考虑使用StreamRequestHandler 或DatagramRequestHandler
StreamRequestHandler/ TCP/UDP 服务器的请求处理类的一个实现
DatagramRequestHandler


我们将再次实现之前的那个基本TCP 的例子。你会注意到新实现与之前有很多相似之处,但你也要注意到,现在很多繁杂的事情已经被封装好了,你不用再去关心那个样板代码了。例子给出的
是一个最简单的同步服务器。
为了要隐藏实现的细节。我们现在写程序时会使用类,这是与之前代码的另一个不同。用面向对象的方法可以帮助我们更好的组织数据与逻辑功能。你也会注意到,我们的程序现在是“事件驱
动”了。这就意味着,只有在事件出现的时候,程序才有“反应”。
事件包含发送与接收数据两种。事实上,你会看到,我们的类定义中只包含了接收客户消息的事件处理器。其它的功能从我们所使用的SocketServer 继承而来。界面编程(第18 章)也是事件
驱动的。你会注意到有一个相似之处,即在代码的最后一行都有一个服务器的无限循环,等待并处理客户的服务请求。本章之前创建的基本TCP 服务器也有一个类似的无限while 循环。
在之前的服务循环中,我们阻塞等待请求,有请求来的时候就处理请求,然后再回去继续等待。
现在的服务循环中,就不用在服务器里写代码了,改成定义一个处理器,服务器在收到进来的请求的时候,可以调用你的处理函数。

 

3.3.1 创建一个SocketServerTCP 服务器
在代码中,先导入我们的服务器类,然后像之前一样定义主机常量。主机常量后就是我们的请求处理器类,然后是启动代码。在下面的代码片断中可以看到更多细节。

逐行解释
1-9 行
最开始的部分是从SocketServer 导入需要的类。注意,我们在使用Python2.4 的多行导入的方式。如果你使用老版本的Python,那么你要使用模块的形如module.attribute 的名字。或者在导入
的时候,把代码写在同一行里:


from SocketServer import TCPServer as TCP, StreamRequestHandler as SRH

例16.5 SocketServer 时间戳服务器(tsTservSS.py)
使用SocketServer 里的TCPServer 和StreamRequestHandler 类创建一个时间戳TCP 服务器。

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()


11-15 行
主要的工作在这里。我们从SocketServer 的StreamRequestHandler 类中派生出一个子类,并重写handle()函数。在BaseRequest 类中,这个函数什么也不做:

def handle(self):
pass

在有客户消息进来的时候,handle()函数就会被调用。StreamRequestHandler 类支持像操作文件对象那样操作输入输出套接字。我们可以用readline()函数得到客户消息,用write()函数把字
符串发给客户。
为了保持一致性,我们要在客户与服务器两端的代码里都加上回车与换行。实际上,你在代码中看不到这个,因为,我们重用了客户传过来的回车与换行。除了这些我们刚刚说到的不同之处外,
代码看上去与之前的那个服务器是一样的。

17-19 行
代码的最后部分用给定的主机信息和请求处理类创建TCP 服务器。然后进入等待客户请求与处理客户请求的无限循环中。


创建SocketServerTCP 客户端:

很自然地,我们的客户端与之前的客户端的代码很相似,比服务器相似得多。但客户端要做一些相应地调整以适应新的服务器。

逐行解释
1-8 行
没什么特别的,与原来的客户端完全相同。

例16.6 SocketServer 时间戳TCP 客户端(tsTclntSS.py)
这是一个时间戳TCP 客户端,它知道如何与SocketServer 里StreamRequestHandler 对象进行通讯。
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()

10-21 行

SocketServer 的请求处理器的默认行为是接受连接,得到请求,然后就关闭连接。这使得我们不能在程序的运行时,一直保持连接状态,要每次发送数据到服务器的时候都要创建一个新的套接
字。
这种行为使得TCP 服务器的行为有些像UDP 服务器。不过,这种行为也可以通过重写请求处理器中相应的函数来改变。我们把这个留在本章最后的练习中。
现在,我们的客户端有点完全不一样了(我们得每次都创建一个连接)。其它的小区别在服务器代码的逐行解释中已经看到了:我们使用的处理器类像文件一样操作套接字,所以我们每次都要发
送行结束字符(回车与换行)。服务器只是保留并重用我们发送的行结束字符。当我们从服务器得到数据的时候,我们使用strip()函数去掉它们,然后使用print 语句提供的回车。


Twisted 框架介绍:
Twisted 是一个完全事件驱动的网络框架。它允许你使用和开发完全异步的网络应用程序和协议。在写本书的时候,它还不是Python 标准库的一部分,要使用它,你必需另外下载并安装它(在本章
最后能找到链接)。它为你创建一个完整系统提供了很大的帮助。系统中可以有:网络协议,线程,安全和认证,聊天/即时通讯,数据库管理,关系数据库集成,网页/互联网,电子邮件,命令行参
数,图形界面集成等。
使用Twisted 来实现我们这个简单的例子有牛刀宰鸡的感觉。不过,学东西总要有切入点吧,我们先实现一个“Hello World”的网络应用程序。
像SocketServer 一样,Twisted 的大部分功能都在它的类里面。在我们的例子中,我们将使用Twisted 的Internet 组件中reactor 和protocol 包的类。


3.4.1 创建一个Twisted Reactor TCP 服务器
你会发现我们的代码与SocketServer 例子有些相似。我们创建一个协议类,并像安装回调函数那样重写几个函数,而不是写一个处理器类。同样的,我们的例子是异步的。先来看服务器:

逐行解释
1-6 行
一开始的代码照常是模块导入部分。要注意twisted.internet 中protocol 和reactor 包和端
口号常量。
8-14 行
我们从Protocol 类中派生出TSServProtocol 类做为时间戳服务器。然后重写connectionMade()函数,这个函数在有客户连接的时候被调用,以及 dataReceived()函数,这个函数在客户通过网络
发送数据过来时被调用。reactor 把数据当成参数传到这个函数中,这样我们就不用自己去解析数据了。

例16.7 Twisted Reactor 时间戳TCP 服务器(tsTservTW.py)
这是一个使用Twisted Internet 类的时间戳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' % (
14 ctime(), data))
15
16 factory = protocol.Factory()
17 factory.protocol = TSServProtocol
18 print 'waiting for connection...'
19 reactor.listenTCP(PORT, factory)
20 reactor.run()
我们通过transport 对象与客户进行通讯。你可以看到在connectionMade()函数中,我们如何得到主机的信息,以及在dataReceived()函数中,我们如何把数据传回客户端的。
16-20 行
在服务器的最后一部分,我们创建一个protocol 工厂。它被称为“工厂”是因为,每次我们有连接进来的时候,它都会“生产”一个我们的protocol 对象。然后在reactor 中安装一个TCP
监听器以等待服务请求。当有请求进来时,创建一个TSServProtocol 实例来服务那个客户。

创建一个Twisted Reactor TCP 客户端:
与SocketServer TCP 客户不一样的是,这个例子与之前的客户端看上去不大一样。它是完全Twisted 的。

例16.8 Twisted Reactor Timestamp TCP 客户端(tsTclntTW.py)
用Twisted 重写我们已经熟悉的时间戳TCP 客户端。
1 #!/usr/bin/env python
2
3 from twisted.internet import protocol, reactor
4
5 HOST = 'localhost'
6 PORT = 21567
7
8 class TSClntProtocol(protocol.Protocol):
9 def sendData(self):
10 data = raw_input('> ')
11 if data:
12 print '...sending %s...' % data
13 self.transport.write(data)
14 else:
15 self.transport.loseConnection()
16
17 def connectionMade(self):
18 self.sendData()
19
20 def dataReceived(self, data):
21 print data
22 self.sendData()
23
24 class TSClntFactory(protocol.ClientFactory):
25 protocol = TSClntProtocol
26 clientConnectionLost = clientConnectionFailed = \
27 lambda self, connector, reason: reactor.stop()
28
29 reactor.connectTCP(HOST, PORT, TSClntFactory())
30 reactor.run()
逐行解释
1-6 行
跟之前所有的客户端程序类似,这里还是导入Twisted 的组件。
8-22 行
与服务器一样,我们扩展Protocol,重写同样的函数connectionMade()和dataReceived()。这两个函数的用途也跟服务器一样。我们新加一个自己的函数sendData(),用于在需要发送数据时调用。
由于我们现在是客户,所以我们要主动初始化跟服务器的对话。一旦连接建立好之后,我们先发送一个消息,服务器回复这个消息,我们把收到的回复显示在屏幕上,然后再发送其它消息给服务器。
这个过程会一直循环,直到用户没有给任何输入时,连接结束。结束时,就不是调用transport对象的write()函数传数据给服务器了,而是调用loseConnection()函数来关闭套接字。这时
厂的clientConnectionLost()函数会被调用,同时,reactor 就被关闭,脚本的执行就结束了。由于某些原因,clientConnectionFailed()被调用时,reactor 也会被关闭。
脚本的最后一部分是创建一个客户工厂,连接到服务器,然后运行reactor。注意,我们在这里实例化了客户端工厂,而不是像在服务器里那样把它传到reactor 中。这是因为,我们不是等待客
户连接的服务器,服务器在有连接时要为每个连接创建一个新的protocol 对象。我们只是一个客户,所以我们只要创建一个protocol 对象,连接到服务器,服务器的工厂会创建一个protocol 对象来
与我们对话。
你可以在下面的网站找到更多有关Twisted 的信息:
http://twistedmatrix.com

posted @ 2015-12-12 21:30  Fanyear  阅读(501)  评论(1编辑  收藏  举报