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

1、问题现象

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

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

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

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

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

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

然后,把网站上传到公司武汉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;

如下图:

当时的第一思路,新加坡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",果然也不能成功访问。

2、问题解决

由于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被修复。

3、感悟

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

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

 

posted @ 2015-09-11 16:09    阅读(1372)  评论(4)    收藏  举报