状态模式:一个Epoll边缘触发的代理服务器设计
温馨提示:这篇文章已超过599天没有更新,请注意相关的内容是否还可用!
Linux服务器开发相关视频解析:
linux下epoll的秘密——支持亿级IO的底层基石
支撑互联网的基石tcpip,5个方面全面剖析
设计模式是一门大众化的知识,但往往不太容易掌握何时使用哪一种。 本文以设计一个Socks5代理服务器为例,介绍状态模式的实际使用。
软件功能介绍 提供Socks5代理功能,同时支持TCP和UDP,可以转发代理连接,将代理服务器连接成代理链,使用Linux epoll edge trigger API使用,期待增加更多非标Socks5代理握手步骤或握手数据代理网络拓扑模型
Socks5代理协议
Socke5代理协议同时支持TCP和UDP。 因此,代理握手交互也是由两种不同的协议承载的。 其TCP握手阶段包括:
客户端发起代理请求在线代理服务器,服务器响应代理请求,返回用户认证方式。 客户端根据认证方法发送认证消息。 服务器验证客户端发起TCP代理请求在线代理服务器,提交目标地址和端口,服务器响应是否连接到目标地址成功。 TCP客户端,服务器端开始转发TCP流数据
UDP协议握手也是通过TCP连接运行的,TCP连接的状态表明客户端是否还需要使用这个UDP代理通道。 由于UDP是无连接的,所以proxy的使用状态不得不使用TCP连接:
握手前4步与TCP相同,客户端发起UDP代理请求,提交客户端自己的UDP端口,服务器回复UDP代理请求,创建新的UDP端口用于向客户端发送数据,并将其返回给客户端。 客户端使用上述端口使用UDP协议发送数据 a) 客户端发送的每个UDP包都会添加一个10字节的包头,其中包含目标服务器的地址 b) 服务器回复的每个UDP包也会添加一个10字节的头部,包含源服务器的地址 c) 如果客户端与代理服务器之间的TCP连接中断,则本次代理服务将被关闭,服务器将关闭所有UDP端口
可见,Socks5代理协议的握手其实更复杂。 如果需要增加代理转发功能,服务端软件需要实现服务端协议和客户端协议。 这样整个系统代理握手阶段的代码至少有十个进程。
另外,由于我们期望使用“边沿触发”的epoll API来转发网络数据,因此我们必须记录网络中的各种异步状态。 由于“Edge Trigger”只会在事件发生时发起一个事件,而一个代理连接有两个方向,可能在双方都存在拥塞和畅通。 因此,在握手完成后,每个代理连接仍然要处理各种网络拥塞状态。
虽然问题比较麻烦,但是如果我们把系统看成一个流水线模型,然后把这些过程总结成状态,问题就迎刃而解了。 因为每个管道只有两个动作:读数据和写数据。读写操作只会有两个目标:客户端和目标服务器。
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++、Linux、golang技术、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、流媒体、CDN、P2P、 K8S、Docker、TCP/IP、协程、DPDK、ffmpeg等)
状态流
一旦我们了解了管道的模型和状态,我们就可以开始枚举各种状态。 在等待客户端连接的握手阶段,我们有三种状态:“等待认证方法”、“等待登录”和“等待代理目标”。 然后进入“等待转发连接”状态,根据客户端的需要进入转发代理、TCP或UDP三个不同的阶段。
转发代理阶段是实现客户端握手的过程,所以有三种状态:“握手完成”、“认证方式协商”和“登录完成”。 最后根据agent的需求,返回到TCP和UDP两个阶段。
TCP阶段首先进入“TCP直连就绪”状态,或者“TCP转发就绪”状态,根据上面不同的状态,依次进入“双向畅通”、“单向阻塞”和“双向阻塞”流水线过程的“方式阻塞”三种流状态。
UDP阶段比较简单,进入“UDP直连就绪”,开始转发数据即可。
最后,都会进入“关闭”状态。
状态模式的实现
至此,我们的实现就很清晰了,就是创建一个“session”类型来代表一个通道,而这个通道一共有15个状态,所以我们需要定义15个类; 每个“state”类型都有一个“read”“Write”两个接口需要实现; 每个状态都可以跳转到其他状态
在编写每个状态的读写代码时,只需要关注当前读写数据应该如何解析和处理,然后适当地跳转到其他状态即可。 比如在登录验证状态下,WaitingAuth::OnRead()方法就是对网络中的数据进行解码,然后检查用户名和密码,最后进入新的状态WatingCmd。
在已经建立的TCP通道的状态码中,根据读写数据的read()和write()系统调用的返回值判断如何修改会话状态。 在单向阻塞状态下,可以根据不同端的数据,以不同的方式继续处理读写。
摘要状态模式
优势:
缺点
最后总结一下状态模式的思路: