16.4 *SocketServer模块
SocketServer是标准库中一个高级别的模块。用于简化实现网络客户端与服务器所心需的大量样板代码。该模块中,已经实现了一些可供使用的类。
我们将再次实现之前的那个基本TCP的例子,生成一个TCP客户端和服务器。你会注意到新实现与之前有很多相似之处,但你也要注意到,现在很多繁杂的事情已经被封装好了,你不用再去关心那个样板代码了。例子给出的是一个最简单的同步服务器。记得要看看本章最后的把服务器改成异步的习题。
为了要隐藏实现的细节,我们现在写程序时会使用类,这是与之前代码的另一个不同。用面向对象的方法可以帮助我们更好的组织数据与逻辑功能。你也会注意到,我们的程序现在是“事件驱动”了。这就意味着,只有在事件出现的时候,程序才有“反应”。
事件包含发送与接收数据两种。事实上,你会看到,我们的类定义中只包含了接收客户端消息的事件处理器。其他的功能从我们所使用的SocketServer继承而来。GUI编程(第19章)也是事件驱动的。你会注意到有一个相似之处,即在代码的最后一行都有一个服务器的无限循环,等待并处理客户端服务请求。本章之前创建的基本TCP服务器也有一个类似的无限while循环。
在之前的服务器循环中,我们阻塞等待请求,有请求来的时候就处理请求,然后再回去继续等待。现在的服务器循环中,就不用在服务器里写代码了,改成定义一个处理器,服务器在收到进来的请求的时候,可以调用你的处理函数。
16.4.1 创建一个SocketServerTCP服务器
在代码中,先导入我们的服务器类,然后像之前一样定义主机常量。主机常量后就是我们的请求处理器类,然后是启动代码。在下面的代码片段中可以看到更多细节。
逐行解释
1 ~ 9行
最开始的部分是从SocketServer导入需要的类。注意,我们在使用Python 2.4的多行导入的方式。如果你使用老版本的Python,那么你要使用模块的形如module.attribute的名字。或者在导入的时候,把代码写在同一行里:
例16.5 SocketServer时间戳TCP服务器(TsTservss.py)
使用 SocketServer里的 TCPServer和 StreamRequestHandler类创建一个时间戳 TCP服务器。
11 ~ 15行
主要的工作在这里。我们从SocketServer的StreamRequestHandler类中派生出一个子类,并重写handle()函数。在BaseRequest类中,这个函数因没有默认动作而被中断:
在有客户消息进来的时候,handle()函数就会被调用。StreamRequestHandler类支持像操作文件对象那样操作输入输出套接字。我们可以用readline()函数得到客户消息,用write()函数把字符串发给客户端。
为了保持一致性,我们要在客户端与服务器两端的代码里都加上回车与换行。实际上,你在代码中看不到这个,因为,我们重用了客户端传过来的回车与换行。除了这些我们刚刚说到的不同之处外,代码看上去与之前的那个服务器是一样的。
17 ~ 19行
代码的最后部分用给定的主机信息和请求处理类创建TCP服务器。然后进入等待客户端请求与处理客户请求的无限循环中。
16.4.2 创建SocketServerTCP客户端
很自然地,我们的客户端与之前的客户端的代码很相似,比服务器还相似得多。但客户端要做一些相应的调整以适应新的服务器。
逐行解释
1 ~ 8行
没什么特别的,与原来的客户端代码完全相同。
例16.6 SocketServer时间戳TCP客户端(tsTclntSS.py)
这是一个时间戳TCP客户端,它知道如何与类似于文档的SocketServer里StreamRequest Handler对象进行通讯。
10 ~ 21行
SocketServer的请求处理器的默认行为是接受连接,得到请求,然后就关闭连接。这使得我们不能在程序运行时,一直保持连接状态,而是每次发送数据到服务器的时候都要创建一个新的套接字。
这种行为使得TCP服务器的行为有些像UDP服务器。不过,这种行为也可以通过重写请求处理器中相应的函数来改变。我们把这个留在本章最后的练习中。
现在,我们的客户端有点完全不一样了(我们得每次都创建一个连接)。其他的小区别在服务器代码的逐行解释中已经看到了:我们使用的处理器类像文件一样操作套接字,所以我们每次都要发送行结束字符(回车与换行)。服务器只是保留并重用我们发送的行结束字符。当我们从服务器得到数据的时候,我们使用strip()函数去掉它们,然后使用print语句自动提供的回车。
16.4.3 执行TCP服务器和客户端
下面是我们SocketServer TCP客户端的输出:
下面是服务器的输出:
输出与我们之前的TCP客户端与服务器相似。不过,你能看到,我们连接了服务器两次。