##前言 在2015年下半年的时候,笔者的工作主要转向 GIT@OSC 分布式后端服务器的实现,GIT@OSC 分布式对于不同的接入有有不同的解决方案,HTTP 访问使用 nginx 模块实现动态代理,对于 ssh ,则是使用的端口转发。对于 svn, 早先是不使用分布式解决方案,也就是使用旧式的 NFS 方案,对于分布式来说,这非常的不彻底,NFS 依然是系统的一个瓶颈,并且 svn 服务器的奔溃影响的是所有的用户,某日开会,老大参与后,就问我,能不能实现 svn 协议的代理,我想了想,于是就答应下来,后来就有了 svnsrv 项目,2016 年春节前,svnsrv 作为 GIT@OSC svn 分布式解决方案率先上线,经过多次改进,svnsrv 逐渐稳定,目前已经稳定运行至今,春节期间遇到的访问故障经过分析均不是 svnsrv 的问题,而是后端 sserver 服务器的问题。
OSChina 创立在开源分享的精神之上,我们也将回馈社区。将 svnsrv 剥离核心路由库开源出来,项目地址:
##svnsrv 概览 svnsrv 是基于C++11 开发的 svn 协议动态代理服务器软件,原本运行在 linux 平台,网络框架使用了 Boost.Asio 库,全异步模式,在上线初期,我们曾经使用同步和异步混合的方式使用 Boost.Asio ,那段时间 svnsrv 经常运行若干个小时就无法接受新的连接,后来改成全异步模式就没有这个问题了。核心使用 Boost.Asio 库给移植到 windows 平台带来了便利,春节过后,笔者就将 svnsrv 移植到 Windows 平台了,支持 Visual Studio 2013, Visual Studio 2015, 使用 nuget 工具下载 boost 依赖库,同样也支持 MSYS2 的 Mingw64 编译,不支持 cygwin, 对于 BSD 类系统,daemon 功能是没有实现的。其他功能未测试。开发者可以 fork 改进后发起 Pull Request,实现 svnsrv 对于其他平台的支持和对 svnsrv bug 的修复,功能的改进。
##svn 协议代理实现 笔者曾经写过一篇博客: 在文中曾经介绍了 svn 协议的主要内容,并且提出里 svn 代理服务器的实现思路。svn 客户端连接到服务器时,最先会握手(handshake)交换握手信息,客户端的数据携带了访问的 URL, 我们解析 数据取得 URL 后,然后解析 URL 获得用户名,通过查询路由模块,便可以获得后端的服务器地址和端口,与后端建立连接后,svnsrv 交换两方数据,即实现了 svn 的代理。
svnsrv 使用线程池并发,源码在 SubversionServer, 并发线程数目可以在配置文件中配置。
顺序的操作有如下函数:
echo_downstream_handshake --> read_downstream_handshake -->async_connect_upstream -->read_upstream_handshake -->echo_upstream_handshake
代理的核心函数是这几个:
downstream_readdownstream_writeupstream_readupstream_write
代理功能实现 SubversionSession.cc 源码总共 200多行代码,使用 C++11 智能指针,避免了手动释放内存。
##服务器程序的套路 与 helloworld 显著不同的是,服务器程序通常需要在后台运行,从终端启动后需要脱离终端的掌控,在 posix 系统,可以实现 daemon (守护进程)机制的程序。
一个知识我们得知道,无论是 Linux 还是 Windows , 终端或者命令行启动进程本质上与 C 函数 system 是一致的,system 函数不讨论内部机制如何,启动进程后都将等待进程执行完毕,在 Windows 平台上,subsystem 不是 console 的进程除外( GUI 程序就是如此)。守护进程的第一步就是让终端认为 进程已经结束,在 posix 平台,使用 fork 后结束父进程,道理就是如此,然后,为了避免子进程成为僵尸进程,让子进程被 init 进程收养就变得很重要,后面的就是修改 stdout stderr stdin 标准输入输出,重定向到特定设备文件,比如 /dev/null。
posix 的 fork 是一种写时复制的进程创建机制,fork 出来的程序会从 紧接着 fork() 调用后面的代码执行,除了 fork() 函数返回值和一些特殊的变量不一样,其他都是一致的。
在 Windows 上,可以使用 Windows 服务,为了避免需要修改更多的代码实现兼容,我在 daemonize 中,先取得 进程的 STARTUPINFO 信息,判断 wShowWindow 是否等于 SW_HIDE 来决定是否结束到进程自身,创建新的进程,在 svnsrv 代码中,调用 daemonize 前没有操作目录,可以直接使用 GetCommandLine 或得自身的 commandline ,将此值传递到 CreateProcess, 调用 CreateProcess 前设置 STARTINFO wShowWindow 的值为 SW_HIDE 即可。启动子进程成功后 使用 ExitProcess(0) 结束自身,这样便实现了 Windows 版的 daemon。
关于命令行的获取, linux 使用 read /proc/pid/cmdline , Windows 中有两种,一个是 MSVC 使用 COM WMI 类 Win32_Process 获得 cmdline, 另一种 Mingw 使用 ZwQueryInformationProcess 读取 特定进程 PEB ,得到 cmdline ,这个有个限制,目标进程和自身的架构得一致,也就是 32位智能读取 32位,64位智能读取 64位。
由于没有实现 BSD 的命令行读取,所以,没有实现 BSD 的 daemon 功能,命令行主要用于重启。
##结尾 svnsrv 也期待其他用户参与进来改进。