设计模式是一门热门的知识,但是何时应该用哪个,却往往不容易掌握,本文以一个Socks5代理服务器的设计为例,介绍状态模式的实践用法。
Socke5代理协议同时支持TCP和UDP。因此代理握手交互也由2个不同协议承载。其TCP握手阶段包括:
UDP协议握手也是通过TCP连接运行,并且从这个TCP连接的状态来表示客户端是否还需要用此UDP代理通道。由于UDP是无连接状态的,所以代理的使用状态就只好使用一个TCP连接了:
可以看到,Socks5代理协议的握手其实是比较复杂。如果需要加上代理转发的功能,这个服务器软件处理实现服务器端协议,还需要实现客户端协议。这样整个系统的代理握手阶段的代码就有至少十个以上的过程。
另外,由于我们期望使用“边缘触发”的Epoll API来转发网络数据,所以我们必须要记录网络中的各种异步状态。由于“边缘触发”只会在发生事件时发起一次事件,而一个代理连接有两个方向,两个对端都可能存在堵塞和畅通。所以每个代理连接在握手完成后,还是要处理各种网络堵塞状态的过程。
虽然问题比较繁琐,但是如果我们把系统看成是一个管道的模型,然后把这些过程归纳成状态,问题就很好解决了。因为每个管道,无非只有两种动作:读数据、写数据。而读写操作的目标端也只会有2个:客户端、目标服务端
一旦我们确定了管道的模型和状态的想法,我们就能开始列举各种状态。在等待客户端连接的握手阶段,我们有“等待验证方法”“等待登录”“等待代理目标”三个状态。然后进入“等待转发连接”状态,根据客户端的需求,进入转发代理、TCP或者UDP三个不同的阶段。
转发代理阶段,就是实现客户端握手的过程,因此有“握手完成”“验证方法商定”“登录完成”三种状态,最后根据代理的需求,重新回到TCP和UDP两个阶段。
TCP阶段首先就是进入“TCP直连就绪”状态,或者“TCP转发就绪”状态,根据从上面不同的状态而来,然后就进入了管道流程的“双向畅通”“单向堵塞”“双向堵塞”三种流式状态。
UDP阶段比较简单,就是进入“UDP直连就绪”后开始转发数据就好了。
最后,都会进入一个“关闭”的状态。
至此我们的实现已经很清楚了,就是先建立一个“会话”类型代表一个通道,而这个通道一共可能有15个状态,因此要定义15个类;每个“状态”的类型,都有“读”“写”两个接口需要实现;每个状态都可以跳转到其他状态
在编写每个状态的读写代码的时候,只要关注当前的读写数据应该如何解析和处理就好了,然后适当的跳转到其他状态就好了。比如在登录验证状态中,WaitingAuth::OnRead()方法就是解码出网络中的数据,然后检查用户名、密码,最后进入新的状态WatingCmd。
在已经建立好TCP通道的状态代码里,根据读写数据read()和write()的系统调用的返回值,判断应该如何修改会话的状态。而在单向堵塞的状态里,又可以根据不同端的数据,以不同的方式来继续处理读写。
优点:
缺点
最后,总结一下状态模式的思考:
感谢大家的阅读,如觉得此文对你有那么一丁点的作用,麻烦动动手指转发或分享至朋友圈。如有不同意见,欢迎后台留言探讨。