4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPでSQLServerの名前付きインスタンスに接続する

Last updated at Posted at 2017-08-18

概要

  • PHP7.1のPDO/php_sqlsrvではポート番号指定が必要。
  • SQL Server Browserにポート番号を教えてもらって、指定すればOK。

経緯

  • PHP7.1で、SQLServerの名前付きインスタンスに接続できなかった。
  • PHP5.6の時は問題なく接続できていた。
  • PHP.netの説明を読んでも、名前付きインスタンスへの接続については書かれていない。
  • ググってみても、名前付きインスタンスをやめて固定ポートにすれば解決するという情報ばかり。
  • ほかのアプリでも使っているSQLServerなので、インスタンスの公開方法を変えるのは避けたい。

環境

PHPはremiリポジトリからyumでインストールしたもの。

  • CentOS6 / PHP5.6 :php_pdo, php_mssql
  • CentOS7 / PHP7.1 :php_pdo, php_sqlsrv

接続するのにFreeTDSを使っていたのが、microsoft製のドライバ経由に変わったらしい。

接続するコード

// PHP5.6で接続できたDSN
$dsn = "dblib:host={$ipaddr}\\SQLEXPRESS;dbname={$dbname};charset=UTF-8";

// 以前テストしたときにwindows上のphp_pdo_sqlsrvをつかって接続できたDSN
// 当時の環境は不詳。PHP5.3だったような。
$dsn = "sqlsrv:server={$ipaddr}\\SQLEXPRESS;database={$dbname}";

// PDOで接続。
$pdo = new PDO($dsn, 'dbuser', 'password');

PDOで接続する部分は一緒なので、以下はDSNのみ記載する。
windowsで成功していたのと同じように書いてみるが、つながらない。

//接続に失敗する
$dsn = "sqlsrv={$ipaddr}\\SQLEXPRESS;database={$dbname}";

失敗する様子をWiresharkでモニタしてみたところ、tcp/1433に接続しようとして応答がない状態。
インスタンスの動的ポート番号を調べたところ49174だったので、PDO_SQLSRV DSNの説明に従ってポート番号を入れてみると接続に成功した。

//つながった
$dsn = "sqlsrv={$ipaddr}\\SQLEXPRESS,49174;database={$dbname}";

動的ポートに対応する

SQLServerの名前付きインスタンスは動的ポート番号でListenしているため、ポート番号を調べる必要がある。
SQLServerのポート番号を解決するために、SQL Server Browserというサービスが動いているらしい。

SQL Server Browserに問い合わせる方法....

udp/1434でListenしていることは分かったものの、どうすればいいのかは調べきれず。

実際に問い合わせて観察してみた

PHP5.6ではポート番号を指定しなくても接続できたので、PHP5.6 (dblib) はSQL Server Browser に問い合わせているはず。
Wiresharkでパケットをモニタしながら接続してみたところ、

img.png

「SQLEXPRESS」という文字列の前に0x04、後に0x00をつけて送っていた。

手動で問い合わせてみると...

シェルから問い合わせてみた結果 (PC名は伏せ字に修正)。

$ echo -en "\04SQLEXPRESS\00" | nc -u 192.168.18.99 1434
?ServerName;*****;InstanceName;SQLEXPRESS;IsClustered;No;Version;9.00.4035.00;tcp;49174;np;\\*****\pipe\MSSQL$SQLEXPRESS\sql\query;;

セミコロン区切りで、項目名・内容の繰り返しで返ってくるようで、「tcp;<ポート番号>」を拾えばOKな印象。

PHPで接続する

クラスメソッドにして、コンストラクタから呼ぶ形にする。

class MyPDO_SQLSRV extends PDO {
    public function __construct($ipaddr, $dbuser, $password, $dbname) {
        $port = self::querySqlBrowser($ipaddr);
        if ($port === false) {
            throw new Exception('failed to find port number.');
        }
        $dsn = "sqlsrv:Server={$ipaddr}\\SQLEXPRESS,{$port};Database={$dbname}";
        parent::__construct($dsn, $dbuser, $password);
    }
    
    public static function querySqlBrowser($ipaddr) {
        $fp = fsockopen("udp://{$ipaddr}", 1434, $errno, $errstr);
        fwrite($fp, "\04SQLEXPRESS\00");
        $res = fread($fp, 1024);
        fclose($fp);
        if (!preg_match('/;tcp;([1-9][0-9]{0,4});/', $res, $matches)) {
            return false;
        }
        return $matches[1];
    }
}
//接続できた
$mypdo = new MyPDO_SQLSRV($ipaddr, 'dbuser', 'password', 'dbname');
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?