2020-10-09 10:58:37 +08:00
<? php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// +---------------------------------------------------------------------------
// | SWAN [ $_SWANBR_SLOGAN_$ ]
// +---------------------------------------------------------------------------
// | Copyright $_SWANBR_COPYRIGHT_$
// +---------------------------------------------------------------------------
// | Version $_SWANBR_VERSION_$
// +---------------------------------------------------------------------------
// | Licensed ( $_SWANBR_LICENSED_URL_$ )
// +---------------------------------------------------------------------------
// | $_SWANBR_WEB_DOMAIN_$
// +---------------------------------------------------------------------------
namespace Kafka ;
2020-10-10 10:45:30 +08:00
use HttpServer\Http\Context ;
2020-10-09 12:06:05 +08:00
use Snowflake\Event ;
2020-10-10 10:55:29 +08:00
use Snowflake\Exception\ComponentException ;
2020-10-09 12:06:05 +08:00
use Snowflake\Snowflake ;
2020-10-10 10:45:30 +08:00
use Swoole\Coroutine ;
2020-10-09 10:58:37 +08:00
use Swoole\Coroutine\Client ;
/**
* +------------------------------------------------------------------------------
* Kafka protocol since Kafka v0.8
* +------------------------------------------------------------------------------
*
* @package
* @version $_SWANBR_VERSION_$
* @copyright Copyleft
* @author $_SWANBR_AUTHOR_$
* +------------------------------------------------------------------------------
*/
class SocketSync
{
const READ_MAX_LEN = 5242880 ; // read socket max length 5MB
2020-10-10 10:55:29 +08:00
const SOCKET_NAME = 'client_socket' ;
2020-10-09 10:58:37 +08:00
/**
* max write socket buffer
* fixed:send of 8192 bytes failed with errno=11 Resource temporarily
* fixed:'fwrite(): send of ???? bytes failed with errno=35 Resource temporarily unavailable'
* unavailable error info
*/
const MAX_WRITE_BUFFER = 2048 ;
/**
* Send timeout in seconds.
*
* @var float
* @access private
*/
private $sendTimeoutSec = 0 ;
/**
* Send timeout in microseconds.
*
* @var float
* @access private
*/
private $sendTimeoutUsec = 100000 ;
/**
* Recv timeout in seconds
*
* @var float
* @access private
*/
private $recvTimeoutSec = 0 ;
2020-10-10 10:55:29 +08:00
2020-10-09 10:58:37 +08:00
/**
* Recv timeout in microseconds
*
* @var float
* @access private
*/
private $recvTimeoutUsec = 750000 ;
2020-10-09 11:19:16 +08:00
/**
* Stream resource
*
* @var \Swoole\Coroutine\Socket
* @access private
*/
2020-10-09 10:58:37 +08:00
private $stream = null ;
/**
* Socket host
*
* @var mixed
* @access private
*/
private $host = null ;
/**
* Socket port
*
* @var mixed
* @access private
*/
private $port = - 1 ;
/**
* Max Write Attempts
* @var int
* @access private
*/
private $maxWriteAttempts = 3 ;
/**
* __construct
*
* @access public
* @param $host
* @param $port
* @param int $recvTimeoutSec
* @param int $recvTimeoutUsec
* @param int $sendTimeoutSec
* @param int $sendTimeoutUsec
*/
public function __construct ( $host , $port , $recvTimeoutSec = 0 , $recvTimeoutUsec = 750000 , $sendTimeoutSec = 0 , $sendTimeoutUsec = 100000 )
{
$this -> host = $host ;
$this -> port = $port ;
$this -> setRecvTimeoutSec ( $recvTimeoutSec );
$this -> setRecvTimeoutUsec ( $recvTimeoutUsec );
$this -> setSendTimeoutSec ( $sendTimeoutSec );
$this -> setSendTimeoutUsec ( $sendTimeoutUsec );
}
/**
* @param float $sendTimeoutSec
*/
2020-10-10 10:55:29 +08:00
public function setSendTimeoutSec ( float $sendTimeoutSec )
2020-10-09 10:58:37 +08:00
{
$this -> sendTimeoutSec = $sendTimeoutSec ;
}
/**
* @param float $sendTimeoutUsec
*/
2020-10-10 10:55:29 +08:00
public function setSendTimeoutUsec ( float $sendTimeoutUsec )
2020-10-09 10:58:37 +08:00
{
$this -> sendTimeoutUsec = $sendTimeoutUsec ;
}
/**
* @param float $recvTimeoutSec
*/
2020-10-10 10:55:29 +08:00
public function setRecvTimeoutSec ( float $recvTimeoutSec )
2020-10-09 10:58:37 +08:00
{
$this -> recvTimeoutSec = $recvTimeoutSec ;
}
/**
* @param float $recvTimeoutUsec
*/
2020-10-10 10:55:29 +08:00
public function setRecvTimeoutUsec ( float $recvTimeoutUsec )
2020-10-09 10:58:37 +08:00
{
$this -> recvTimeoutUsec = $recvTimeoutUsec ;
}
/**
* @param int $number
*/
2020-10-10 10:55:29 +08:00
public function setMaxWriteAttempts ( int $number )
2020-10-09 10:58:37 +08:00
{
$this -> maxWriteAttempts = $number ;
}
/**
* Optional method to set the internal stream handle
*
* @static
* @access public
* @param $stream
2020-10-10 10:55:29 +08:00
* @return SocketSync
2020-10-09 10:58:37 +08:00
*/
public static function createFromStream ( $stream )
{
$socket = new self ( 'localhost' , 0 );
$socket -> setStream ( $stream );
return $socket ;
}
/**
* Optional method to set the internal stream handle
*
* @param mixed $stream
* @access public
* @return void
*/
public function setStream ( $stream )
{
$this -> stream = $stream ;
}
/**
* Connects the socket
*
* @access public
2020-10-10 10:45:30 +08:00
* @return Coroutine\Socket
2020-10-09 10:58:37 +08:00
*/
public function connect ()
{
2020-10-10 10:55:29 +08:00
if ( Context :: hasContext ( self :: SOCKET_NAME )) {
return Context :: getContext ( self :: SOCKET_NAME );
2020-10-09 10:58:37 +08:00
}
if ( empty ( $this -> host )) {
2020-10-10 10:55:29 +08:00
throw new Exception ( 'Cannot open null host.' );
2020-10-09 10:58:37 +08:00
}
if ( $this -> port <= 0 ) {
2020-10-10 10:55:29 +08:00
throw new Exception ( 'Cannot open without port.' );
2020-10-09 10:58:37 +08:00
}
2020-10-10 10:45:30 +08:00
$stream = new \Swoole\Coroutine\Socket ( AF_INET , SOCK_STREAM );
if ( ! $stream -> connect ( $this -> host , $this -> port )) {
2020-10-09 10:58:37 +08:00
$error = 'Could not connect to '
. $this -> host . ':' . $this -> port
2020-10-10 10:45:30 +08:00
. ' (' . $stream -> errMsg . ' [' . $stream -> errMsg . '])' ;
2020-10-10 10:55:29 +08:00
throw new Exception ( $error );
2020-10-09 10:58:37 +08:00
}
2020-10-10 10:55:29 +08:00
return Context :: setContext ( self :: SOCKET_NAME , $stream );
2020-10-09 10:58:37 +08:00
}
/**
* close the socket
*
* @access public
* @return void
*/
public function close ()
{
2020-10-10 10:45:30 +08:00
2020-10-09 10:58:37 +08:00
}
/**
* checks if the socket is a valid resource
*
* @access public
* @return boolean
*/
public function isResource ()
{
2020-10-10 10:55:29 +08:00
if ( ! Context :: hasContext ( self :: SOCKET_NAME )) {
2020-10-10 10:45:30 +08:00
return false ;
}
2020-10-10 10:55:29 +08:00
return Context :: getContext ( self :: SOCKET_NAME ) -> checkLiveness ();
2020-10-09 10:58:37 +08:00
}
/**
* Read from the socket at most $len bytes.
*
* This method will not wait for all the requested data, it will return as
* soon as any data is received.
*
* @param integer $len Maximum number of bytes to read.
* @param boolean $verifyExactLength Throw an exception if the number of read bytes is less than $len
*
* @return string Binary data
2020-10-10 10:55:29 +08:00
* @throws Exception
2020-10-09 10:58:37 +08:00
*/
2020-10-10 10:55:29 +08:00
public function read ( int $len , $verifyExactLength = false )
2020-10-09 10:58:37 +08:00
{
if ( $len > self :: READ_MAX_LEN ) {
2020-10-10 10:55:29 +08:00
throw new Exception ( 'Could not read ' . $len . ' bytes from stream, length too longer.' );
2020-10-09 10:58:37 +08:00
}
2020-10-10 10:56:42 +08:00
$stream = $this -> connect ();
2020-10-09 10:58:37 +08:00
$null = null ;
$remainingBytes = $len ;
$data = $chunk = '' ;
while ( $remainingBytes > 0 ) {
2020-10-10 10:45:30 +08:00
$chunk = $stream -> recv ( $remainingBytes );
2020-10-09 10:58:37 +08:00
if ( $chunk === false ) {
2020-10-10 10:55:29 +08:00
throw new Exception ( 'Could not read ' . $len . ' bytes from stream (no data)' );
2020-10-09 10:58:37 +08:00
}
if ( strlen ( $chunk ) === 0 ) {
2020-10-10 10:45:30 +08:00
continue ;
2020-10-09 10:58:37 +08:00
}
$data .= $chunk ;
$remainingBytes -= strlen ( $chunk );
}
if ( $len === $remainingBytes || ( $verifyExactLength && $len !== strlen ( $data ))) {
2020-10-10 10:55:29 +08:00
throw new Exception ( 'Read ' . strlen ( $data ) . ' bytes instead of the requested ' . $len . ' bytes' );
2020-10-09 10:58:37 +08:00
}
return $data ;
}
/**
* Write to the socket.
*
* @param string $buf The data to write
*
* @return integer
2020-10-10 10:55:29 +08:00
* @throws Exception
* @throws \Exception
2020-10-09 10:58:37 +08:00
*/
2020-10-10 10:55:29 +08:00
public function write ( string $buf )
2020-10-09 10:58:37 +08:00
{
$null = null ;
$failedWriteAttempts = 0 ;
$written = 0 ;
$buflen = strlen ( $buf );
2020-10-09 12:06:05 +08:00
2020-10-10 10:45:30 +08:00
$stream = $this -> connect ();
2020-10-09 10:58:37 +08:00
while ( $written < $buflen ) {
if ( $buflen - $written > self :: MAX_WRITE_BUFFER ) {
2020-10-10 10:45:30 +08:00
$wrote = $stream -> send ( substr ( $buf , $written , self :: MAX_WRITE_BUFFER ), 1 );
2020-10-09 10:58:37 +08:00
} else {
2020-10-10 10:45:30 +08:00
$wrote = $stream -> send ( substr ( $buf , $written ), 1 );
2020-10-09 10:58:37 +08:00
}
if ( $wrote === - 1 || $wrote === false ) {
throw new \Kafka\Exception\Socket ( 'Could not write ' . strlen ( $buf ) . ' bytes to stream, completed writing only ' . $written . ' bytes' );
} elseif ( $wrote === 0 ) {
$failedWriteAttempts ++ ;
if ( $failedWriteAttempts > $this -> maxWriteAttempts ) {
throw new \Kafka\Exception\Socket ( 'After ' . $failedWriteAttempts . ' attempts could not write ' . strlen ( $buf ) . ' bytes to stream, completed writing only ' . $written . ' bytes' );
}
} else {
$failedWriteAttempts = 0 ;
}
$written += $wrote ;
}
return $written ;
}
}