關於 Subversion 協議動態代理服務器 頂 原 薦

##前言 在2015年下半年的時候,筆者的工作主要轉向 GIT@OSC 分佈式後端服務器的實現,GIT@OSC 分佈式對於不同的接入有有不同的解決方案,HTTP 訪問使用 nginx 模塊實現動態代理,對於 ssh ,則是使用的端口轉發。對於 svn, 早先是不使用分佈式解決方案,也就是使用舊式的 NFS 方案,對於分佈式來說,這非常的不徹底,NFS 依然是系統的一個瓶頸,並且 svn 服務器的奔潰影響的是所有的用戶,某日開會,老大參與後,就問我,能不能實現 svn 協議的代理,我想了想,於是就答應下來,後來就有了 svnsrv 項目,2016 年春節前,svnsrv 作爲 GIT@OSC svn 分佈式解決方案率先上線,經過多次改進,svnsrv 逐漸穩定,目前已經穩定運行至今,春節期間遇到的訪問故障經過分析均不是 svnsrv 的問題,而是後端 sserver 服務器的問題。

OSChina 創立在開源分享的精神之上,我們也將回饋社區。將 svnsrv 剝離核心路由庫開源出來,項目地址: https://git.oschina.net/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 協議代理實現 筆者曾經寫過一篇博客:Subversion 和 GIT 開發者演進 在文中曾經介紹了 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_read
downstream_write
upstream_read
upstream_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 也期待其他用戶參與進來改進。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章