"If your operating system supports the select() system call in its I/O library (and nearly all do), then you can use it to juggle multiple communication channels at once; doing other work while your I/O is taking place in the “background.” Although this strategy can seem strange and complex, especially at first, it is in many ways easier to understand and control than multi-threaded programming. The asyncore module solves many of the difficult problems for you, making the task of building sophisticated high-performance network servers and clients a snap. For “conversational” applications and protocols the companionasynchat module is invaluable."
异步I/O便于建立先进的高性能网络服务器和客户端。
"The basic idea behind both modules is to create one or more network channels, instances of class asyncore.dispatcher and asynchat.async_chat. Creating the channels adds them to a global map, used by the loop() function if you do not provide it with your own map.
Once the initial channel(s) is(are) created, calling the loop() function activates channel service, which continues until the last channel (including any that have been added to the map during asynchronous service) is closed."
这两个模块基本思想都是创建一个或多个网络信道。通过调用loop()函数激活信道服务,服务会一直运行直到最后一个信道被关闭。
"The dispatcher class is a thin wrapper around a low-level socket object. To make it more useful, it has a few methods for event-handling which are called from the asynchronous loop. Otherwise, it can be treated as a normal non-blocking socket object."
"The firing of low-level events at certain times or in certain connection states tells the asynchronous loop that certain higher-level events have taken place. For example, if we have asked for a socket to connect to another host, we know that the connection has been made when the socket becomes writable for the first time (at this point you know that you may write to it with the expectation of success). The implied higher-level events are:
EventDescriptionhandle_connect()Implied by the first read or write eventhandle_close()Implied by a read event with no data availablehandle_accepted()Implied by a read event on a listening socket During asynchronous processing, each mapped channel’s readable() and writable() methods are used to determine whether the channel’s socket should be added to the list of channels select()ed or poll()ed for read and write events."
通过继承asyncore.dispatcher实现基本的异步I/O服务。
18.4 asynchat-Asynchronous socket command/response handler
"This module builds on the asyncore infrastructure, simplifying asynchronous clients and servers and making it easier to handle protocols whose elements are terminated by arbitrary strings, or are of variable length. asynchat defines the abstract class async_chat that you subclass, providing implementations of the collect_incoming_data() and found_terminator() methods. It uses the same asynchronous loop as asyncore, and the two types of channel,asyncore.dispatcher and asynchat.async_chat, can freely be mixed in the channel map. Typically an asyncore.dispatcher server channel generates new asynchat.async_chat channel objects as it receives incoming connection requests."
asynchat模块基于asyncore莫魁岸构建,简化了异步客户端和服务器,使处理以任意字符串和长度结尾的协议更容易。
"This class is an abstract subclass of asyncore.dispatcher. To make practical use of the code you must subclass async_chat, providing meaningfulcollect_incoming_data() and found_terminator() methods. The asyncore.dispatcher methods can be used, although not all make sense in a message/response context.
Like asyncore.dispatcher, async_chat defines a set of events that are generated by an analysis of socket conditions after a select() call. Once the polling loop has been started the async_chat object’s methods are called by the event-processing framework with no action on the part of the programmer."
asynchat.asyn_chat是asyncore.dispatcher的子类,通过继承asynchat.async_chat类,实现collect_incoming_data() and found_terminator()方法。跟asyncore.dispatcher类似,asynchat.asyn_chat定义的一系列事件也是基于对select()函数和poll()函数的分析。
类图:
可用的命令有:login name,logout,say statement,look,who
其中:
ChatServer:__init__()里创建记录登录用户的字典,主聊天室。handle_accept()处理每一个连接到服务器上的客户端套接字,为每一个客户端新建一个ChatSession
ChatSession:__init__()里记录会话的用户名,设置结束符,进入LoginRoom。enter()为从LoginRoom到ChatRoom和从ChatRoom到LogoutRoom的切换。collect_incoming_data()为接收客户端发送数据。found_terminator()为对用户接收到的数据行进行处理,如果数据内容为空则不作处理。hand_close()为关闭套接字并进入LogoutRoom。
Room:__init__创建记录会话的列表,对应于相同的服务器。add()将会话添加到房间。remove()会话离开房间。broadcast()为向房间中所有会话广播数据。do_logout()相应logout命令。
LoginRoom:add()欢迎新进入房间的会话。unknown()命令不存在时提示重新输入。do_login()检查输入的会话用户名是否合法是否已存在,如果合法且不重复则允许进入主聊天室。
ChatRoom:add()向所有会话提示新用户进入,并将新用户加入服务器上的用户字典。remove()向所有会话提示用户离开,并将用户从服务器上的用户字典删除。do_say()向所有会话广播用户发言。do_look()查看哪些用户在聊天室内。do_who()查看登录用户。
LogoutRoom:add()将当前会话从服务器用户字典中删除。
CommandHandler: unknown()响应未知命令。handle()处理从会话中接收到的数据行,根据数据行提取出响应命令 并 执行对应的方法。
问题总结:
(1)dispatcher和async_chat对象类似基本上都是套接字对象,处理跟套接字相关的操作。不同的是前者侧重于接受客户端连接,后者侧重于响应处理消息。
(2)在调试代码的过程中,发现在客户端与服务器通信过程中发送的数据类型为bytes,而在类里面处理发送的数据时需要处理字符串,这就涉及字符串与bytes对象的转换。注意区别b''.join()与''.join()。
# str to bytes
bytes(s, encoding = "utf8")
# bytes to str
str(b, encoding = "utf-8")
(3)getattr()查看对象是否具有相应属性,通过异常实现。
改进方向:
(1)多个聊天室。此时do_who()和do_look()功能不同。
(2)更多操作命令,回复更人性化。
(3)创建GUI客户端。此时,GUI响应的时间循环和服务器通信的时间循环需要协调工作,涉及到线程处理。