Mono on Linux 開發與實踐札記(1)
最近有個政府專案,客戶指定伺服器程式必須跑在Linux上面,於是乎我們這幫Linux菜鳥立馬開裝Linux系統並部署Mono環境。因為對Linux實在不熟的緣故,故在RedHat Enterprise Linux 6中始終沒有將MonoDevelop跑起來,於是團隊中有同事提議“乾脆轉Java平臺算噠”,哎呦,這麼“反動”的想法必須得鎮壓在萌芽狀態。好吧,我承認有些許的個人主觀因素作祟,但終究還是需要些冠冕堂皇理由的:
- 因為公司現有所有產品都是基於.NET平臺,絕大數開發人員也只有.NET開發經驗,這樣的平臺遷移是個非常大成本的事件,而且涉及公司的整個軟體開發的方向性轉移,絕不可貿然行事。
- 我一直主張將所有產品統一開發平臺,最終將各條產品線全部基於自己研發的外掛框架,如果並行Java和.NET兩個開發平臺,其中涉及的開發量和程式碼移植、版本同步、雙向相容……想想就是一個浩大而繁瑣的工程,這樣的焦油坑一定要避免陷入進去。
- 目前的問題其實只是團隊對Mono on Linux的經驗匱乏,這個問題還不至於嚴重到需要遷移開發平臺,起碼我們還有Mono這樣一根救命稻草。所以,我們要做的只是儘快熟悉Linux和積累Mono on Linux上面的一些移植經驗,當然,我知道把C#程式碼移植過去通過Mono的編譯並不難,問題在於有些API的細節差異和某些程式碼契約需要通過一定時間的經驗來規避某些小陷阱,但是,這些都是可以解決的。
- Mono現在最新穩定版本是2.10.2,對C# 4.0已經支援的很完整了。而且後面很快會有ASP.NET MVC3的完整實現,雖然沒有看到對ADO.NET EntityFramework的支援計劃,但是,我們曾經實現過以DataSet為資料載體的資料訪問框架,所以完全可以借鑑ADO.NET EF的設計思想去實現一個適度ORM資料引擎,當然這個事情得推後一點才有精力去做,但是想想就是一件多麼有趣的事情啊。
上面曉之以理,下面就該動之以情了,你看這次的Android版的警務通就是用Java來搞的,沒有使用那個MonoDroid吧,為啥?因為Mono for Android實在太不成熟了,用它寫的程式的無論是體積還是執行效率都沒法跟人家比,確實不在一個等級。總而言之,量體裁衣、具體對待。
部門會餐後,俺決定從這個專案組開始逐步進行雙機開發,每人配兩臺電腦,一臺裝Windows、一臺裝Linux,在Windows中用VS開發,除錯通過後簽入SVN中,再在Linux中籤出使用MonoDevelop進行一遍單元測試,不用虛擬機器,一切原生態同步開發,提升每個開發人員對Linux的熟悉和積累對Mono on Linux的開發經驗,於公於私都是非常給力的!
RHEL6中MonoDevelop裝不上,咱沒功夫跟它耗,趕緊上OpenSUSE和CentOS,這兩個都是MonoDevelop官網的主薦平臺,果然很給力相當的順。下面是今天(2011-7-5)將一些後臺程式碼移植到Mono on Linux中的實踐札記:
- 眾所周知Linux的檔案系統路徑是區分大小寫的,所以在程式碼中千萬不能簡單拼接檔案路徑,推薦使用 System.IO.Path 類中的相關方法進行路徑操作,另外,對於文字中的分行要使用 Environment.NewLine屬性,或者 AppendLine 之類的方法來處理以規避作業系統的差異。
- 避免使用Win32 API的P/Invoke操作,這點好在Linux上只是跑伺服器端程式碼,所以基本不會涉及。
今天移植的兩個類庫時,只碰到下面兩個問題花了一點時間,就執行測試通過了,好爽。
一、在.NET 4.0的System.Type中新增了這個GetType(…)方法過載:
public static Type GetType(string typeName, Func<AssemblyName, Assembly> assemblyResolver, Func<Assembly, string, bool, Type> typeResolver, bool throwOnError)
我的程式碼是這樣使用的:
public static Type GetType(string typeFullName)
{
if(string.IsNullOrWhiteSpace(typeFullName))
return null;return Type.GetType(typeFullName, assemblyName =>
{
Assembly assembly = ResolveAssembly(assemblyName);if(assembly == null)
assembly = LoadAssembly(assemblyName);return assembly;
}, null, false);
}
在.NET中一切正常,但是在Mono on Linux中無法正常解析,經檢視微軟的原始碼和Mono原始碼後,發現他們的對於第三個typeResolver引數的處理策略不同,故而將上述程式碼改成:
public static Type GetType(string typeFullName)
{
if(string.IsNullOrWhiteSpace(typeFullName))
return null;return Type.GetType(typeFullName, assemblyName =>
{
Assembly assembly = ResolveAssembly(assemblyName);if(assembly == null)
assembly = LoadAssembly(assemblyName);return assembly;
}, (assembly, typeName, ignoreCase) =>
{
if(assembly == null)
return null;
else
return assembly.GetType(typeName, false, ignoreCase);
}, false);
}
後來發現對解析像 System.Int32 這樣的簡寫型別名失敗,原因就在於typeResolver引數對應的委託回撥中傳入的assembly引數為空(null),故而將其後的程式碼(黃色高亮)行改為:return Type.GetType(typeName, false, ignoreCase);即可!
二、在.NET中使用System.Net.HttpListener進行偵聽後,如果程序被強制關閉Windows作業系統會回收它所佔用的地址和埠(注意:該點未經證實,純屬個人瞎猜),故重新執行該程式後順利重啟HttpListener。但是在Mono on Linux中卻行不通,當強制關閉程序後,再次執行會丟擲 System.Net.Sockets.SocketException,提示“Address already in use.”這表示Socket已經被佔用,需要手動將佔用的Socket關閉,那該怎麼做呢?首先查出機器中的網路狀態,然後找到佔用的該Socket的程序,再將該程序Kill掉即可。找到如下操作步驟可解決之:
在Linux終端中,鍵入這個命令:lsof –i
如果當前使用者不是root可能什麼也看不到,那麼請切換到root使用者許可權,如:sudo lsof -i
上面命令會提示你輸入root根使用者的密碼,然後即可看到當前機器中的網路佔用列表了,這時如果人品沒問題的話,應該可以看到cammand為mono的佔用記錄,如果你清楚自己佔用的埠號的話,請這麼檢視(以88埠為例):lsof –i:88 或者 sudo lsof –i:88
你找到佔用程式的程序Id(PID),然後使用kill命令幹掉它即可,假設PID為1234:kill 1234 或者 sudo kill 1234
好了,再次執行程式,又跑起來了!Good,先到這,還會有很多問題等著我們的,到時再寫吧!