Mono环境下System.Data.Sqlclient没法链接带有实例和端口的SqlServer链接字符串的BUG修复

一、问题现象

本人为了学习租用了一台国外(日本linode)机房的服务器(vps),不要问我为何,就是喜欢折腾。node

服务器做为WebServer,系统为CentOS(linux),为了跑.net网站,使用了Mono的.net环境,和jexus网站容器。系统为CentOS 7 + Mono 3.8.0 + jexus 5.6linux

AWS有个1年免费的ECS,本着无事找事,而且有便宜白占不白占的原则,也去弄了一年AWS,万般折腾下,配了windows 2008 + Sql Server Express 2005 Adv用作测试数据库服务器,数据库实例名为SQLEXPRESS2005web

公司在武汉电信机房有托管服务器,系统为Windows 2008 + IIS + SqlServer 2008,数据库实例为默认实例MSSQLSERVERsql

上述简称 日本WebServer,新加坡SqlServer,武汉WebServer,武汉SqlServer数据库

前几日,新作了个MVC4测试项目,FTP传到 日本WebServer,数据库附加到 新加坡SqlServer,登录提交时出现异常信息“sqlserver server does not exist or connection refused.”,而后各类记录log,各类重传DLL重启jexus重启系统,故障信旧,查看微软的在线帮助也没有解答。windows

而后,把网站上传到公司武汉WebServer,登录,竟然正常,进入后各类与数据库相关的操做,均正常。服务器

这可奇了怪了,怒把数据库附加到武汉SqlServer,修改web.config中的链接字符串,武汉WebServer访问依旧正常,日本Webserver竟然也正常!网络

新加坡SqlServer链接字符串部分connectionString="Data Source=52.74.251.34\SqlExpress2005,14433;框架

武汉SqlServer链接字符串部分connectionString="Data Source=61.22.65.22,14433;ssh

以下图:

当时的第一思路,新加坡SqlServer或日本WebServer是否是没法互通,或者是否各自把对方的IP加入了黑名单,但通过各类排查,发现网络和防火墙均正常。

而后怒用wireshark对武汉和新加坡的sqlServer进行抓包,发现新加坡SqlServer的数据库端口竟然没有入站报文。

网络正常,但又没有数据库入站链接,这真是机关用尽。

最后在日本WebServer使用tcpdump进行抓包,down下来一看,忽然让人眼前一亮。以下图,服务器竟然把Data source的主机部分,识别成了域名,调用了DNS进行解析,但这地址固然是不存在的,这样也就解释了为啥没法链接的问题。

问题果真如此么,此时,对日本WebServer的Web.config中的数据库链接进行测试,原武汉SqlServer的data source部分由"61.22.65.22,11433"修改成"61.22.65.22\MSSQLSERVER,11433",果真也不能成功访问。

二、问题解决

因为SqlClient程序集,被封装在System.Data.dll中,win和Mono的此dll文件内包含的内容不尽相同,因此用win版的System.Data.dll进行替换也无济于事。

从http://download.mono-project.com/sources/mono/mono-3.8.0.tar.bz2下载mono3.8.0源码,找到目录mcs/class/System.Data/System.Data.SqlClient/,打开其中SqlConnection.cs文件,通过一番定位,终于找到了其解析数据库链接的方法ParseDataSource(),代码以下

 1 private bool ParseDataSource (string theDataSource, out int thePort, out string theServerName)
 2         {
 3             theServerName = string.Empty;
 4             string theInstanceName = string.Empty;
 5     
 6             if (theDataSource == null)
 7                 throw new ArgumentException("Format of initialization string does not conform to specifications");
 8 
 9             thePort = DEFAULT_PORT; // default TCP port for SQL Server
10             bool success = true;
11 
12             int idx = 0; 13             if ((idx = theDataSource.IndexOf (',')) > -1) { 14                 theServerName = theDataSource.Substring (0, idx); 15                 string p = theDataSource.Substring (idx + 1); 16                 thePort = Int32.Parse (p); 17             } else if ((idx = theDataSource.IndexOf ('\\')) > -1) { 18                 theServerName = theDataSource.Substring (0, idx); 19                 theInstanceName = theDataSource.Substring (idx + 1); 20 
21                 // do port discovery via UDP port 1434
22                 port = DiscoverTcpPortViaSqlMonitor (theServerName, theInstanceName); 23                 if (port == -1) 24                     success = false; 25             } else
26                 theServerName = theDataSource; 27 
28             if (theServerName.Length == 0 || theServerName == "(local)" || theServerName == ".")
29                 theServerName = "localhost";
30 
31             if ((idx = theServerName.IndexOf ("tcp:")) > -1)
32                 theServerName = theServerName.Substring (idx + 4);
33 
34             return success;
35         }

代码中绿色背景部分,是否有问题呢,固然是有问题的,哈哈~~~

好比,上面的链接字符串,用这个方法解析出来,theServerName=52.74.251.34\SqlExpress2005,固然不会正常运行。

知道了问题,就知道解决方法啦

修改代码以下

private bool ParseDataSource(string theDataSource, out int thePort, out string theServerName)
        {
            theServerName = string.Empty;
            string theInstanceName = string.Empty;

            if (theDataSource == null)
                throw new ArgumentException("Format of initialization string does not conform to specifications");

            thePort = DEFAULT_PORT; // default TCP port for SQL Server
            bool success = true;

            theServerName = theDataSource;
            int idx = 0;
            if ((idx = theServerName.IndexOf(',')) > -1)
            {
                string p = theServerName.Substring(idx + 1);
                thePort = Int32.Parse(p);
                theServerName = theServerName.Substring(0, idx);
            }
            if ((idx = theServerName.IndexOf('\\')) > -1)
            {
                theInstanceName = theDataSource.Substring(idx + 1);
                theServerName = theServerName.Substring(0, idx);
                if (thePort <= 0)
                {
                    // do port discovery via UDP port 1434
                    port = DiscoverTcpPortViaSqlMonitor(theServerName, theInstanceName);
                    if (port == -1)
                        success = false;
                }
            }

            if (theServerName.Length == 0 || theServerName == "(local)" || theServerName == ".")
                theServerName = "localhost";

            if ((idx = theServerName.IndexOf("tcp:")) > -1)
                theServerName = theServerName.Substring(idx + 4);

            return success;
        }

到此,故障排查就到此了,这样就结束了么,固然没有

因为mono是在linux编译安装,因此这个改动,还得ssh进服务器进行修改并从新编译才能生效。

使用putty,登录日本WebServer,找到mono编译的源码目录(辛亏当时编译好后没有删除),依次进入mcs->class->System.Data->System.Data.SqlClient,vi SqlConnection.cs,修改掉上述代码,保存退出。

随便到mono主目录,分别执行make和make install,漫长的等待后,mono即编译成功。

重启jexus服务,/usr/jexus/jws restart,重启成功后,再次访问,新加坡SqlServer成功访问。

据此,这个BUG被修复。

三、感悟

在使用了成熟团队开发的框架时,项目相关部分出现异常,第一时间必定要相信他人的代码,由于不少时候,项目跑不起来,排查到最后可能都是本身犯了某低级错误。

在肯定排除掉本身的BUG后,就须要针对异常现象进行全方面的排查了,在这里若是有发现某框架的一些蛛丝马迹,就须要对框架代码进排查了,下源码,反编译,用各类手段看到框架的逻辑代码,有没有问题天然就一清二楚了。

相关文章
相关标签/搜索