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. 153
      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.
**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**
>
> This fork adds a namespace and fixes issues encountered when porting to PHP 7, fixes old MS Windows specific tests
##What's new
* 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
@ -25,7 +27,6 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protoc
## Requirements
* The PHP extension php_sockets.dll should be enabled (server php.ini file)
* PHP 5.5+

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

@ -76,14 +76,18 @@ class ModbusMaster
* @var float Total response timeout (seconds, decimals allowed)
*/
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)
*/
public $socket_read_timeout_sec = 0.3;
public $socket_read_timeout_sec = 0.3; // 300 ms
/**
* @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)
*/
@ -470,6 +474,7 @@ class ModbusMaster
->setTimeoutSec($this->timeout_sec)
->setSocketReadTimeoutSec($this->socket_read_timeout_sec)
->setSocketWriteTimeoutSec($this->socket_write_timeout_sec)
->setSocketConnectTimeoutSec($this->socket_connect_timeout_sec)
->build();
$socket->connect();

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

Loading…
Cancel
Save