意大利面条代码是一种很直接的实现方法。但是,毕竟日后维护会引发不便。
不包含加密和文件传输飞鸽程序可以分解成两部分:
1. GUI。呈现图形界面,相应用户输入。实际上就是把读取键盘的线程变成GUI而已,但是已经不包含了任何与网络相关的部分,如socket维护等。这里维护系统状态,如邻居表:就是通讯录,记录每个邻居的IP地址、用户名、主机名、昵称、群组、在线情况(以及可用的加密算法等)。
2. Communicator,通信器。负责维护socket,接收和解析数据报、并根据GUI发来的请求,组装相应的报文,发出相应的报文。本身唯一的状态就是socket本身。
这里GUI作为主对象,创建和控制Communicator。它们之间有如下通信接口:
实现仍然用Python语言实现。
当然,在Python语言里是没有所谓的Interface这样的东西的。(一定想要在Python里用Interface的请找Zope Interface)全靠对象自己自觉。执行的时候对象只管调用方法,不会考虑它是否存在。但是Python里面没有异步的Event这种机制。上述UML里的events也是用同步的方法调用来实现。
程序中一共有两个活跃的线程:一个是GUI线程;另一个是socket接收线程。Python的线程间通信明显不如Erlang语言强大(废话)。如果引入MessageQueue,必然要引入Mutex和Condition的话,对于这样的小程序太overkill了。事实上现在的这个程序就是线程不安全的。
GUI用PyGTK实现,设计如下:
[img]http://cloverprince.iteye.com/upload/attachment/125836/ab464429-20a2-38ba-9fab-770c96522c83.png[/img]
本来是觉得,把所有东西都放在屏幕上,有一种一目了然,很爽的感觉。现在看看似乎也不行,太挤了。
这样的设计,还是有问题的
- GUI,感觉扩充已经是问题了。塞的满满的。
另一个问题是
- 目前线程的结构,是按照输入来区分的线程:一个用户输入,另一个网络输入。输入之后,执行者分属不同的对象,但方法调用却位于各个模块中。换句话说,两个不同线程在同时忙于多个互相交叠的一组对象。这使得两个线程的责任不清(在对象级别降低耦合了,但在线程级别没有),还使锁不可缺少。另外在GTK中使用线程本身就会遇到一些问题。
可行的解决方法:
* 是否可能将socket IO集成到GTK的MainLoop中,使得数据包的到达成为一个事件。这样就可以用单线程事件轮询的方式,以处理GUI事件相同的方法处理IO。
优点是避免多线程的麻烦。缺点也是抛弃了多线程的方便。IO的处理必须以“有限状态自动机”的形式构造(即设计模式里的State模式)。明显,用Python语言实现状态机会遇到困难,也有可能给调试添加麻烦。
下一个问题:
- GUI中,状态(通讯录/邻居列表)如何保存。目前的实现方法是使用GTK的ListStore。虽然不是什么问题,但是这使得程序和UI耦合更加紧密——毕竟ListStore是为GTK的TreeView专门设计的结构。
理想的模块化,是将程序分为至少3部分:通信器/核心/GUI。核心保存状态,执行协议操作,而GUI只是表现这样的状态。耦合度松到一定程度,使得只要在一个XML说用哪个GUI,用哪个核心,用哪个通信其,它们如何如何连接,就可以执行。(依赖注入?)
用MVC的语言说,通讯录就是M,GUI就是V,核心就是C。
* 我相信,软件必须有一定的耦合度。高耦合的代码不易应对变化;而低耦合的代码必须用足够多的“胶水”连接它们,反而麻烦。
不包含加密和文件传输飞鸽程序可以分解成两部分:
1. GUI。呈现图形界面,相应用户输入。实际上就是把读取键盘的线程变成GUI而已,但是已经不包含了任何与网络相关的部分,如socket维护等。这里维护系统状态,如邻居表:就是通讯录,记录每个邻居的IP地址、用户名、主机名、昵称、群组、在线情况(以及可用的加密算法等)。
2. Communicator,通信器。负责维护socket,接收和解析数据报、并根据GUI发来的请求,组装相应的报文,发出相应的报文。本身唯一的状态就是socket本身。
这里GUI作为主对象,创建和控制Communicator。它们之间有如下通信接口:
----------------------------
<< interface >>
ICore // GUI实现这个接口
----------------------------
PROPERTIES:
+ get_context() : {username,host,nick,...}
// 取得当前主机的信息,包括用户名等。
----------------------------
EVENTS:
+ node_notify(ipaddr,username,host,nick,group,away)
// node_notify通知GUI新的节点加入。
+ incomming_message(ipaddr,message_text,sealed)
// 当有新消息到达时,这个方法会调用。
----------------------------
----------------------------
<< interface >>
ICommunicator // Communicator实现这个接口
----------------------------
EVENTS:
+ refresh()
// 命令通信器广播当前主机加入的命令IPMSG_BR_ENTRY。
+ send_message(ipaddr,message_text,sealed)
// 向某个用户发送消息。
+ read_notify(ipaddr,packet_num)
// 告知某邻居,它发出的消息已读。
----------------------------
实现仍然用Python语言实现。
当然,在Python语言里是没有所谓的Interface这样的东西的。(一定想要在Python里用Interface的请找Zope Interface)全靠对象自己自觉。执行的时候对象只管调用方法,不会考虑它是否存在。但是Python里面没有异步的Event这种机制。上述UML里的events也是用同步的方法调用来实现。
程序中一共有两个活跃的线程:一个是GUI线程;另一个是socket接收线程。Python的线程间通信明显不如Erlang语言强大(废话)。如果引入MessageQueue,必然要引入Mutex和Condition的话,对于这样的小程序太overkill了。事实上现在的这个程序就是线程不安全的。
GUI用PyGTK实现,设计如下:
[img]http://cloverprince.iteye.com/upload/attachment/125836/ab464429-20a2-38ba-9fab-770c96522c83.png[/img]
本来是觉得,把所有东西都放在屏幕上,有一种一目了然,很爽的感觉。现在看看似乎也不行,太挤了。
这样的设计,还是有问题的
- GUI,感觉扩充已经是问题了。塞的满满的。
另一个问题是
- 目前线程的结构,是按照输入来区分的线程:一个用户输入,另一个网络输入。输入之后,执行者分属不同的对象,但方法调用却位于各个模块中。换句话说,两个不同线程在同时忙于多个互相交叠的一组对象。这使得两个线程的责任不清(在对象级别降低耦合了,但在线程级别没有),还使锁不可缺少。另外在GTK中使用线程本身就会遇到一些问题。
可行的解决方法:
* 是否可能将socket IO集成到GTK的MainLoop中,使得数据包的到达成为一个事件。这样就可以用单线程事件轮询的方式,以处理GUI事件相同的方法处理IO。
优点是避免多线程的麻烦。缺点也是抛弃了多线程的方便。IO的处理必须以“有限状态自动机”的形式构造(即设计模式里的State模式)。明显,用Python语言实现状态机会遇到困难,也有可能给调试添加麻烦。
下一个问题:
- GUI中,状态(通讯录/邻居列表)如何保存。目前的实现方法是使用GTK的ListStore。虽然不是什么问题,但是这使得程序和UI耦合更加紧密——毕竟ListStore是为GTK的TreeView专门设计的结构。
理想的模块化,是将程序分为至少3部分:通信器/核心/GUI。核心保存状态,执行协议操作,而GUI只是表现这样的状态。耦合度松到一定程度,使得只要在一个XML说用哪个GUI,用哪个核心,用哪个通信其,它们如何如何连接,就可以执行。(依赖注入?)
用MVC的语言说,通讯录就是M,GUI就是V,核心就是C。
* 我相信,软件必须有一定的耦合度。高耦合的代码不易应对变化;而低耦合的代码必须用足够多的“胶水”连接它们,反而麻烦。