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後,就須要針對異常現象進行全方面的排查了,在這裏若是有發現某框架的一些蛛絲馬跡,就須要對框架代碼進排查了,下源碼,反編譯,用各類手段看到框架的邏輯代碼,有沒有問題天然就一清二楚了。

相關文章
相關標籤/搜索