上一篇"跨平台(Windows+Linux)的Socket通讯程序"给出了Socket通讯底层的一些函数的包装方法/类,同时屏蔽了操作系统(Windows/Linux)的不同。
上一篇只是对通讯底层方法的封装,并没用涉及应用,这一篇将基于上一篇,并结合"跨平台(Windows+Linux)的线程辅助程序",讨论一个实用化的Socket通讯程序的体系结构。
一、结构图
下面给出了一个Socket通讯应用的大致体系结构图。
图中,给出了一些概念类和它们所处的层次,大致上,分为三个处理层次以及一个辅助层次。
底层封装函数:这个层次的内容在(一)中已论述,主要是对Socket原生函数的封装,之所以既有CSocketWrap类,又有独立的辅助函数,是考虑到一般性与特殊性兼顾,对通常的通讯逻辑,可在CSocketWrap类中处理,对特殊的要求,可直接调用底层的封装函数。
通用的Server和Client层:因为Server端与Client端通讯逻辑不同,宜采用两个类分别处理,本文没有将UDP与TCP协议分开,如果逻辑要更清晰一些,也可以写成4个类。
以上两层都未涉及业务逻辑,完成的是通用的通讯功能,发送或接收函数“看到”的是“数据流”或“数据包”,而忽视其意义。最核心的功能函数有3个:发送函数、接收函数、通讯失败判断函数,通常情况下,发送、接收函数都是采用非阻塞方式,通讯失败函数被更上层的业务逻辑层所调用,有助于判断是否需要重发、继续保持连接等业务逻辑。
业务逻辑层:该层完成通讯的业务逻辑,每个应用都会有所不同,这里分了4个类,每个类都将包含一个协议封装/解析类,用于无意义的“数据流”或“数据包”与有意义的“数据结构”之间的转换。
协议解析/封装层:该层实际上不属于通讯程序本身,是对外通讯协议与对内通讯协议之间的转换。对外的通讯协议格式与应用内部使用的数据格式通常是不同的,需要两个类分别进行转换。
对通讯协议的解析,UDP与TCP一般情况下是不同的(见(一)中UDP与TCP的区别)。对于比较复杂的协议,协议解析/封装类可能有多个,本文只是示意性的给出一个。关于通讯协议的解析和封装,这里不做介绍。
二、一些讨论
1.数据的发送与接收宜与业务逻辑分开,这样就可以编写一个较为通用的发送与接收过程类(业务逻辑一般不能通用),要做到这点,比较合适的做法是采用非阻塞的发送与接收函数,并且将发送与接收过程放到单独的一个线程中,通过线程间信息共享,与业务线程传递要发送与接收的数据。
2.实际上,通用Server/Client层完成的是Socket的“业务”,如果采用多线程,宜在这一层完成,上面的业务层只需调用这一层的发送或接收方法即可。