replace dependency to sockets extension to built-in stream API

pull/2/head
toimtoimtoim 8 years ago
parent c7c4431b1c
commit 604ba8586c
  1. 11
      README.md
  2. 3
      composer.json
  3. 9
      src/ModbusMaster.php
  4. 127
      src/ModbusSocket.php
  5. 2
      tests/ModbusMaster/ModbusExceptionTest.php

@ -2,11 +2,13 @@
Implementation of the basic functionality of the Modbus TCP and UDP based protocol using PHP. Implementation of the basic functionality of the Modbus TCP and UDP based protocol using PHP.
**NOTE: This is a fork to fix & update the library code (and code alone).** **NOTE: This is a fork to fix & update the library code.**
> **What's new** ##What's new
>
> This fork adds a namespace and fixes issues encountered when porting to PHP 7, fixes old MS Windows specific tests * This fork adds a namespace and fixes issues encountered when porting to PHP 7
* Removes dependency to [sockets extension](http://at2.php.net/manual/en/book.sockets.php). Now uses built-in [Stream API](http://www.php.net/manual/en/function.stream-socket-client.php)
* Fixes/replaces old MS Windows specific tests
## Implemented features ## Implemented features
@ -25,7 +27,6 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protoc
## Requirements ## Requirements
* The PHP extension php_sockets.dll should be enabled (server php.ini file)
* PHP 5.5+ * PHP 5.5+

@ -4,8 +4,7 @@
"description": "PhpModbus with namespaces and updated to PHP 7", "description": "PhpModbus with namespaces and updated to PHP 7",
"license": "LGPL", "license": "LGPL",
"require": { "require": {
"php": "^5.5 || ^7.0", "php": "^5.5 || ^7.0"
"ext-sockets": "*"
}, },
"require-dev": { "require-dev": {
"react/socket": "~0.4.0", "react/socket": "~0.4.0",

@ -76,14 +76,18 @@ class ModbusMaster
* @var float Total response timeout (seconds, decimals allowed) * @var float Total response timeout (seconds, decimals allowed)
*/ */
public $timeout_sec = 5; public $timeout_sec = 5;
/**
* @var float Socket connect timeout (seconds, decimals allowed)
*/
public $socket_connect_timeout_sec = 1;
/** /**
* @var float Socket read timeout (seconds, decimals allowed) * @var float Socket read timeout (seconds, decimals allowed)
*/ */
public $socket_read_timeout_sec = 0.3; public $socket_read_timeout_sec = 0.3; // 300 ms
/** /**
* @var float Socket write timeout (seconds, decimals allowed) * @var float Socket write timeout (seconds, decimals allowed)
*/ */
public $socket_write_timeout_sec = 1; // 300 ms public $socket_write_timeout_sec = 1;
/** /**
* @var int Endianness codding (0 = little endian = 0, 1 = big endian) * @var int Endianness codding (0 = little endian = 0, 1 = big endian)
*/ */
@ -470,6 +474,7 @@ class ModbusMaster
->setTimeoutSec($this->timeout_sec) ->setTimeoutSec($this->timeout_sec)
->setSocketReadTimeoutSec($this->socket_read_timeout_sec) ->setSocketReadTimeoutSec($this->socket_read_timeout_sec)
->setSocketWriteTimeoutSec($this->socket_write_timeout_sec) ->setSocketWriteTimeoutSec($this->socket_write_timeout_sec)
->setSocketConnectTimeoutSec($this->socket_connect_timeout_sec)
->build(); ->build();
$socket->connect(); $socket->connect();

@ -33,14 +33,18 @@ class ModbusSocket
* @var float Total response timeout (seconds, decimals allowed) * @var float Total response timeout (seconds, decimals allowed)
*/ */
protected $timeout_sec = 5; protected $timeout_sec = 5;
/**
* @var float Socket connect timeout (seconds, decimals allowed)
*/
protected $socket_connect_timeout_sec = 1;
/** /**
* @var float Socket read timeout (seconds, decimals allowed) * @var float Socket read timeout (seconds, decimals allowed)
*/ */
protected $socket_read_timeout_sec = 0.3; protected $socket_read_timeout_sec = 0.3; // 300 ms
/** /**
* @var float Socket write timeout (seconds, decimals allowed) * @var float Socket write timeout (seconds, decimals allowed)
*/ */
protected $socket_write_timeout_sec = 1; // 300 ms protected $socket_write_timeout_sec = 1;
/** /**
* @var string Socket protocol (TCP, UDP) * @var string Socket protocol (TCP, UDP)
*/ */
@ -56,13 +60,14 @@ class ModbusSocket
/** /**
* @var resource Communication socket * @var resource Communication socket
*/ */
protected $sock; private $streamSocket;
/** /**
* @var array status messages * @var array status messages
*/ */
protected $statusMessages = []; protected $statusMessages = [];
public static function getBuilder() { public static function getBuilder()
{
return new ModbusSocketBuilder(); return new ModbusSocketBuilder();
} }
@ -77,45 +82,55 @@ class ModbusSocket
*/ */
public function connect() public function connect()
{ {
// Create a protocol specific socket $protocol = null;
if ($this->socket_protocol === 'TCP') { switch ($this->socket_protocol) {
// TCP socket case 'TCP':
$this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); case 'UDP':
} elseif ($this->socket_protocol === 'UDP') { $protocol = strtolower($this->socket_protocol);
// UDP socket break;
$this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); default:
} else {
throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'");
} }
// Bind the client socket to a specific local port
$opts = [];
if (strlen($this->client) > 0) { if (strlen($this->client) > 0) {
$result = socket_bind($this->sock, $this->client, $this->client_port); // Bind the client stream to a specific local port
if ($result === false) { $opts = array(
throw new IOException( 'socket' => array(
"socket_bind() failed. Reason: ($result)" . 'bindto' => "{$this->client}:{$this->client_port}",
socket_strerror(socket_last_error($this->sock)) ),
); );
} else {
$this->statusMessages[] = 'Bound';
}
} }
$context = stream_context_create($opts);
$this->streamSocket = @stream_socket_client(
"$protocol://$this->host:$this->port",
$errno,
$errstr,
$this->socket_connect_timeout_sec,
STREAM_CLIENT_CONNECT,
$context
);
// Socket settings (send/write timeout) if (false === $this->streamSocket) {
$writeTimeout = $this->secsToSecUsecArray($this->socket_write_timeout_sec); $message = "Unable to create client socket to {$protocol}://{$this->host}:{$this->port}: {$errstr}";
socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, $writeTimeout); throw new IOException($message, $errno);
}
// Connect the socket if (strlen($this->client) > 0) {
$result = @socket_connect($this->sock, $this->host, $this->port); $this->statusMessages[] = 'Bound';
if ($result === false) { }
throw new IOException(
"socket_connect() failed. Reason: ($result)" .
socket_strerror(socket_last_error($this->sock))
);
} else {
$this->statusMessages[] = 'Connected'; $this->statusMessages[] = 'Connected';
stream_set_blocking($this->streamSocket, false); // use non-blocking stream
$writeTimeoutParts = $this->secsToSecUsecArray($this->socket_write_timeout_sec);
// set as stream timeout as we use 'stream_select' to read data and this method has its own timeout
// this call will only affect our fwrite parts (send data method)
stream_set_timeout($this->streamSocket, $writeTimeoutParts['sec'], $writeTimeoutParts['usec']);
return true; return true;
} }
}
/** /**
* receive * receive
@ -127,35 +142,37 @@ class ModbusSocket
*/ */
public function receive() public function receive()
{ {
socket_set_nonblock($this->sock);
$readsocks[] = $this->sock;
$writesocks = null;
$exceptsocks = null;
$rec = '';
$totalReadTimeout = $this->timeout_sec; $totalReadTimeout = $this->timeout_sec;
$lastAccess = microtime(true); $lastAccess = microtime(true);
$readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec);
while (false !== socket_select($readsocks, $writesocks, $exceptsocks, $readTout['sec'], $readTout['usec'])) { $readTimeout = $this->secsToSecUsecArray($this->socket_read_timeout_sec);
while (true) {
$read = array($this->streamSocket);
$write = null;
$except = null;
if (false !== stream_select($read, $write, $except, $readTimeout['sec'], $readTimeout['usec'])) {
$this->statusMessages[] = 'Wait data ... '; $this->statusMessages[] = 'Wait data ... ';
if (in_array($this->sock, $readsocks, false)) {
if (@socket_recv($this->sock, $rec, 2000, 0)) { // read max 2000 bytes if (in_array($this->streamSocket, $read, false)) {
$data = fread($this->streamSocket, 2048); // read max 2048 bytes
if (!empty($data)) {
$this->statusMessages[] = 'Data received'; $this->statusMessages[] = 'Data received';
return $rec; return $data; //FIXME what if we are waiting for more than that?
} }
$lastAccess = microtime(true); $lastAccess = microtime(true);
} else { } else {
$timeSpentWaiting = microtime(true) - $lastAccess; $timeSpentWaiting = microtime(true) - $lastAccess;
if ($timeSpentWaiting >= $totalReadTimeout) { if ($timeSpentWaiting >= $totalReadTimeout) {
throw new IOException( throw new IOException(
"Watchdog time expired [ $totalReadTimeout sec ]!!! " . "Watchdog time expired [ {$totalReadTimeout} sec ]!!! " .
"Connection to $this->host:$this->port is not established." "Connection to {$this->host}:{$this->port} is not established."
); );
} }
} }
$readsocks[] = $this->sock; } else {
throw new IOException("Failed to read data from {$this->host}:{$this->port}.");
}
} }
return null; return null;
} }
@ -168,7 +185,7 @@ class ModbusSocket
*/ */
public function send($packet) public function send($packet)
{ {
socket_write($this->sock, $packet, strlen($packet)); fwrite($this->streamSocket, $packet, strlen($packet));
$this->statusMessages[] = 'Send'; $this->statusMessages[] = 'Send';
} }
@ -179,8 +196,8 @@ class ModbusSocket
*/ */
public function close() public function close()
{ {
if (is_resource($this->sock)) { if (is_resource($this->streamSocket)) {
socket_close($this->sock); fclose($this->streamSocket);
$this->statusMessages[] = 'Disconnected'; $this->statusMessages[] = 'Disconnected';
} }
} }
@ -322,4 +339,14 @@ class ModbusSocketBuilder extends ModbusSocket
return $this; return $this;
} }
/**
* @param float $socket_connect_timeout_sec
* @return ModbusSocketBuilder
*/
public function setSocketConnectTimeoutSec($socket_connect_timeout_sec)
{
$this->modbusSocket->socket_connect_timeout_sec = $socket_connect_timeout_sec;
return $this;
}
} }

@ -21,7 +21,7 @@ class ModbusExceptionTest extends MockServerTestCase
public function testPortClosedException() public function testPortClosedException()
{ {
$this->expectException(IOException::class); $this->expectException(IOException::class);
$this->expectExceptionMessage('socket_connect() failed. Reason:'); $this->expectExceptionMessage('Unable to create client socket to');
$modbus = new ModbusMasterTcp('127.0.0.1'); $modbus = new ModbusMasterTcp('127.0.0.1');
$modbus->setSocketTimeout(0.2, 0.2); $modbus->setSocketTimeout(0.2, 0.2);

Loading…
Cancel
Save