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,先到这,还会有很多问题等着我们的,到时再写吧!