Auge: sqlsrv_connect von Ubuntu aus zu einem MS-SQL-Server auf Windows Server 2016

Hallo

Hat hier jemand Erfahrungen damit, von PHP aus Kontakt zu einem MS-SQL-Server aufzunehmen?

Ich habe in einer VM mit Ubuntu 18.04 den Apachen (2.4) und PHP 7.2 mit der MS-SQL-Bibliothek zuzüglich unixodbc installiert und will von dort aus einen MS-SQL-Server auf einer anderen VM (Windows Server 2016) im selben lokalen Netz ansprechen. Der Server ist allerdings mit der Funktion sqlsrv_connect mit dem von MS stammenden Beispielcode nicht erreichbar.

Gekürzter Beispielcode:

#$serverName = "DBSERVER1/SQLEXPRESS";
#$serverName = "192.168.1.5\\SQLEXPRESS";
$serverName = "DBSERVER1\\SQLEXPRESS";
$connectionOptions = array(
	"Database" => "TestDB",
	"UID" => "Benutzer",
	"PWD" => "passwort",
	"LoginTimeout" => 3
);
$conn = sqlsrv_connect($serverName, $connectionOptions);
if ($conn === false) {
  die(formatErrors(sqlsrv_errors()));
} else {
  echo "<p>Connection established.</p>\n";
}
/*
 * irrelevanter und bei mir auch auskommentierter Code für die Testabfrage
 */
sqlsrv_close($conn);

function formatErrors($errors) {
	// Display errors
	echo "<section>\n";
	echo " <h2>Error information</h2>\n";
	echo " <ul>\n";
	foreach ($errors as $error) {
		echo "  <li>";
		echo "SQLSTATE: ". $error['SQLSTATE'] . "<br/>";
		echo "Code: ". $error['code'] . "<br/>";
		echo "Message: ". $error['message'] . "<br/>";
		echo "</li>\n";
	}
	echo " </ul>\n";
	echo "</section>";
}

Fehlermeldung:

SQLSTATE: HYT00
Code: 0
Message: [unixODBC][Microsoft][ODBC Driver 17 for SQL Server]Login timeout expired
SQLSTATE: 08001
Code: 11002
Message: [unixODBC][Microsoft][ODBC Driver 17 for SQL Server]TCP Provider: Error code 0x2AFA
SQLSTATE: 08001
Code: 11002
Message: [unixODBC][Microsoft][ODBC Driver 17 for SQL Server]A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.

Wie die verschiedenen Versionen von $serverName zeigen, habe ich einige Schreibweisen [1], die ich im Internet gefunden habe, ausprobiert. Wie die Meldung zeigt, wird der Server mit keiner der Schreibweisen gefunden. Mit den selben Zugangsdaten kann ich von jedem Windows-Rechner [2] in unserem Netz aus per ODBC-Verbindung den Server erreichen. Alle Diskussionen, die im Internet dazu finde, gehen entweder von einer Windows-Windows-Kombination oder einem Linux-Server aus, auf dem sowohl der Webserver mit PHP als auch der MS-SQL-Server auf der selben (virtuellen) Maschine läuft.

Laut php.ini sind die INIs der MS-eigenen Bibliotheken eingebunden.

… /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini, … /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini

pdo_sqlsrv

|pdo_sqlsrv support|enabled |---| |ExtensionVer|5.3.0

|Directive|Local Value|Master Value |---| |pdo_sqlsrv.client_buffer_max_kb_size|10240|10240 |pdo_sqlsrv.log_severity|0|0

sqlsrv

|sqlsrv support|enabled |---| |ExtensionVer|5.3.0

|Directive|Local Value|Master Value |---| |sqlsrv.ClientBufferMaxKBSize|10240|10240 |sqlsrv.LogSeverity|0|0 |sqlsrv.LogSubsystems|0|0 |sqlsrv.WarningsReturnAsErrors|On|On

Muss auf der Ubuntu-Installation noch irgendwas konfiguriert werden?

Mit dem folgenden Code im selben Skript lautet die Ausgabe übrigens „Server erreichbar.“. Der mit „DBSERVER1“ ist auf dem Standardport von MS-SQL also grundsätzlich erreichbar.

$ping['host'] = 'DBSERVER1';
$ping['port'] = 1433;  # Standardport des MS-SQL-Servers
$ping['timeout'] = 3;

if ($fp = fsockopen($ping['host'], $ping['port'], $errCode, $errStr, $ping['timeout'])){
	$errors[] = 'Server erreichbar.';
} else {
	$errors[] = 'Server ist nicht erreichbar. ' . $errCode . ' (' . $errStr . ')';
}
fclose($fp);

Tschö, Auge

--
Eine Kerze stand [auf dem Abort] bereit, und der Almanach des vergangenen Jahres hing an einer Schnur. Die Herausgeber kannten ihre Leser und druckten den Almanach auf weiches, dünnes Papier.
Kleine freie Männer von Terry Pratchett

  1. Auch mit Portangabe. Generell verwenden wir aber dynamische Ports, da wir (noch) mit -zig Clients (Access, Python-Skripte, potentiell PHP) auf die Datenbank zugreifen. ↩︎

  2. vom Windows-10-Rechner über swämtliche Server-Versionen bis hin zur letzten ranzigen Windows-XP-VM ↩︎

akzeptierte Antworten

  1. Tach!

    Hat hier jemand Erfahrungen damit, von PHP aus Kontakt zu einem MS-SQL-Server aufzunehmen?

    Das geht auf drei Weisen: Named Pipes, Shared Memory und TCP/IP. Shared Memory geht gehen nur auf derselben Maschine, Named Pipes gehen wohl auch übergreifend. Die Schreibweisen mit Instance-Namen dürften auch nur in der Windows-Welt gehen. Für anderweitige Verbindungen ist TCP/IP möglich. Das muss so konfiguriert sein, dass der Port, der verwendet werden soll, auch auf eine konkrete Instanz konfiguriert werden muss. Man kann ja mehrere Instanzen auf einem Server laufen lassen, braucht dafür dann aber je einen eigenen Port. Man verbindet sich dann nur zu dieser IP plus Port, ohne Instanznamen. Der ist in dem Fall gar nicht mehr von Belang.

    dedlfix.

    1. Hallo

      Hat hier jemand Erfahrungen damit, von PHP aus Kontakt zu einem MS-SQL-Server aufzunehmen?

      Das geht auf drei Weisen: Named Pipes, Shared Memory und TCP/IP.

      Soweit, so klar. Alle drei Protokolle sind in der MS-SQL-Server-Konfiguration aktiviert.

      … Named Pipes gehen wohl auch übergreifend. Die Schreibweisen mit Instance-Namen dürften auch nur in der Windows-Welt gehen.

      So hatte ich das über die Jahre hinweg auch hier und dort gelesen, aber in den Codebeispielen für die PHP-Lib wurde das oft ohne die übliche Warnung vor Einschränkungen auf bestimmte OS gezeigt. Also habe ich das „der Vollständigkeit halber“ auch ausprobiert. Zudem hat der Zugriff auf den Server mit fsockopen auch mit dem Namen funktioniert. Das verleitet ja regelrecht zu der Annahme, dass das schon gehen wird. 😕

      Für anderweitige Verbindungen ist TCP/IP möglich. Das muss so konfiguriert sein, dass der Port, der verwendet werden soll, auch auf eine konkrete Instanz konfiguriert werden muss. Man kann ja mehrere Instanzen auf einem Server laufen lassen, braucht dafür dann aber je einen eigenen Port.

      Gut, wir haben auf der fraglichen VM nur den einen SQL-Server zu laufen. Das ist ja kein Multifunktions-Server™️, auf dem -zig Dienste laufen, die alle jeweils ihre eigene SQLEXPRESS-Instanz installieren.

      Man verbindet sich dann nur zu dieser IP plus Port, ohne Instanznamen. Der ist in dem Fall gar nicht mehr von Belang.

      Das war's! Nur mit der IP, selbst ohne Angabe des Ports [1], kann ich die Verbindung herstellen. … Nächte Hürde genommen, danke.

      Testcode:

      #$serverName = "192.168.1.5, 1433";
      $serverName = "192.168.1.5";
      $connectionOptions = array(
      	"Database" => "TestDB",
      	"UID" => "Benutzer",
      	"PWD" => "passwort",
      	"LoginTimeout" => 3
      );
      $conn = sqlsrv_connect($serverName, $connectionOptions);
      if ($conn === false) {
        die(formatErrors(sqlsrv_errors()));
      } else {
        echo "<p>Connection established.</p>\n";
      }
      // Select Query
      $tsql = "SELECT @@Version AS SQL_VERSION";
      // Executes the query
      $stmt = sqlsrv_query($conn, $tsql);
      // Error handling
      if ($stmt === false) {
      	die(formatErrors(sqlsrv_errors()));
      }
      
      echo "<section>" . PHP_EOL;
      echo " <h2>Results</h2>" . PHP_EOL;
      echo " <ul>" . PHP_EOL;
      while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
      	echo "  <li>". $row['SQL_VERSION'] . "</li>" . PHP_EOL;
      }
      echo " </ul>" . PHP_EOL;
      echo "</section>" . PHP_EOL;
      
      sqlsrv_free_stmt($stmt);
      sqlsrv_close($conn);
      
      function formatErrors($errors) {
      	// Display errors
      	echo "<section>" . PHP_EOL;
      	echo " <h2>Error information</h2>" . PHP_EOL;
      	echo " <ul>" . PHP_EOL;
      	foreach ($errors as $error) {
      		echo "  <li>";
      		echo "SQLSTATE: ". $error['SQLSTATE'] . "<br/>";
      		echo "Code: ". $error['code'] . "<br/>";
      		echo "Message: ". $error['message'] . "<br/>";
      		echo "</li>" . PHP_EOL;
      	}
      	echo " </ul>" . PHP_EOL;
      	echo "</section>" . PHP_EOL;
      }
      

      Ausgabe:

      Connection established.
      Results
      Microsoft SQL Server 2017 (RTM) …
      

      Tschö, Auge

      --
      Eine Kerze stand [auf dem Abort] bereit, und der Almanach des vergangenen Jahres hing an einer Schnur. Die Herausgeber kannten ihre Leser und druckten den Almanach auf weiches, dünnes Papier.
      Kleine freie Männer von Terry Pratchett

      1. Wie bereits geschrieben, verwenden wir dynamische Portzuweisung. Bei einem festgelegten Port muss dieser auch angegeben werden (siehe Beispielcode). ↩︎

      1. Tach!

        Wie bereits geschrieben, verwenden wir dynamische Portzuweisung. Bei einem festgelegten Port muss dieser auch angegeben werden (siehe Beispielcode).

        Beim Default-Port 1433 wird man eine Angabe wohl auch weglassen können.

        Generell verwenden wir aber dynamische Ports, da wir (noch) mit -zig Clients (Access, Python-Skripte, potentiell PHP) auf die Datenbank zugreifen.

        Das ist keine Begründung für dynamische Ports. Ein serverseitiger Port kann wie unter TCP/IP üblich mit vielen Client umgehen. Ohne festen Port muss erstmal der SQL Browser Service konnektiert werden (UDP port 1434), um die aktuelle Portnummer in Erfahrung zu bringen.

        Wenn sowieso nur eine Instanz läuft, sehe ich keinen Sinn für eine dynamische Portzuweisung. Das hat eher Vorteile, wenn viele Instanzen laufen sollen, denen man nicht allen einzeln händisch den Port konfigurieren möchte.

        dedlfix.

        1. Hallo

          Wie bereits geschrieben, verwenden wir dynamische Portzuweisung. Bei einem festgelegten Port muss dieser auch angegeben werden (siehe Beispielcode).

          Beim Default-Port 1433 wird man eine Angabe wohl auch weglassen können.

          Generell verwenden wir aber dynamische Ports, da wir (noch) mit -zig Clients (Access, Python-Skripte, potentiell PHP) auf die Datenbank zugreifen.

          Das ist keine Begründung für dynamische Ports. Ein serverseitiger Port kann wie unter TCP/IP üblich mit vielen Client umgehen.

          Mit Named Pipes auch? Wir benutzen beide Protokolle.

          Ohne festen Port muss erstmal der SQL Browser Service konnektiert werden (UDP port 1434), um die aktuelle Portnummer in Erfahrung zu bringen.

          Wenn sowieso nur eine Instanz läuft, sehe ich keinen Sinn für eine dynamische Portzuweisung. Das hat eher Vorteile, wenn viele Instanzen laufen sollen, denen man nicht allen einzeln händisch den Port konfigurieren möchte.

          Das muss ich erstmal mit unseren Netzwerkern besprechen. Das ist nicht so mein Metier.

          Tschö, Auge

          --
          Eine Kerze stand [auf dem Abort] bereit, und der Almanach des vergangenen Jahres hing an einer Schnur. Die Herausgeber kannten ihre Leser und druckten den Almanach auf weiches, dünnes Papier.
          Kleine freie Männer von Terry Pratchett
          1. Tach!

            Das ist keine Begründung für dynamische Ports. Ein serverseitiger Port kann wie unter TCP/IP üblich mit vielen Client umgehen.

            Mit Named Pipes auch? Wir benutzen beide Protokolle.

            Wie die genau funktionieren, weiß ich nicht, aber meines Wissens ist dafür kein Port notwendig, wenn's über den Namen geht. Wie das maschinenübergreifend geht, entzieht sich meiner Kenntnis.

            dedlfix.

      2. Das war's! Nur mit der IP, selbst ohne Angabe des Ports [^1], kann ich die Verbindung herstellen. … Nächte Hürde genommen, danke.

        Das war es noch nicht. Das sieht so aus, als müsstest solltest Du was mit der DNS-Auflösung unternehmen. Sonst kommt es an anderen Stellen zu Problemen.

        1. Tach!

          Das war's! Nur mit der IP, selbst ohne Angabe des Ports [^1], kann ich die Verbindung herstellen. … Nächte Hürde genommen, danke.

          Das war es noch nicht. Das sieht so aus, als müsstest solltest Du was mit der DNS-Auflösung unternehmen. Sonst kommt es an anderen Stellen zu Problemen.

          Das Prinzip ist, nur den DNS-Servernamen oder die IP-Adresse zu nehmen, ohne Instanznamen. Ob Name oder Adresse ist eher im Bereich Vorliebe und Netzwerkkonfiguration anzusiedeln.

          Welche Probleme sollten das denn sein, außer dass die IP-Adresse sich durch irgendeinen Umstand ändert? Prinzipiell kann ja auch ein DNS-Name falsch/nicht aufgelöst werden. Aber das sind alles Netzwerkprobleme, die generell gelöst werden müssen und nicht spezifisch für die DBMS-Verbindung sind.

          dedlfix.