/lib/AsyncStream.php

https://github.com/Erika31/phpdaemon · PHP · 524 lines · 404 code · 113 blank · 7 comment · 84 complexity · 808513ca017fbad2baf7b6e97d30bac8 MD5 · raw file

  1. <?php
  2. /**
  3. * Asynchronous stream
  4. *
  5. * @package Core
  6. *
  7. * @author Zorin Vasily <kak.serpom.po.yaitsam@gmail.com>
  8. */
  9. class AsyncStream {
  10. public $readFD;
  11. public $writeFD;
  12. public $readBuf;
  13. public $writeBuf;
  14. public $onRead;
  15. public $onReadData;
  16. public $onWrite;
  17. public $onReadFailure;
  18. public $onWriteFailure;
  19. public $onEOF;
  20. public $EOF = FALSE;
  21. public $readPriority = 10;
  22. public $writePriority = 10;
  23. public $request;
  24. public $readPacketSize = 4096;
  25. public $done = FALSE;
  26. public $writeState = FALSE;
  27. public $finishWrite = FALSE;
  28. public $buf = '';
  29. public $noEvents = FALSE;
  30. public $fileMode = FALSE;
  31. public $filePath;
  32. public function __construct($readFD = NULL, $writeFD = NULL) {
  33. $this->initStream($readFD, $writeFD);
  34. }
  35. public function gets() {
  36. $p = strpos($this->buf, "\n");
  37. if ($p === FALSE) {
  38. return FALSE;
  39. }
  40. $r = binarySubstr($this->buf, 0, $p + 1);
  41. $this->buf = binarySubstr($this->buf, $p + 1);
  42. return $r;
  43. }
  44. public function initStream($readFD = NULL,$writeFD = NULL) {
  45. if (is_string($readFD)) {
  46. $u = parse_url($url = $readFD);
  47. if ($u['scheme'] === 'unix') {
  48. if (!Daemon::$useSockets) {
  49. $readFD = stream_socket_client($readFD, $errno, $errstr, 1);
  50. } else {
  51. $readFD = socket_create(AF_UNIX, SOCK_STREAM, 0);
  52. if (!socket_connect($readFD, substr($url, 7))) {
  53. socket_close($readFD);
  54. $readFD = FALSE;
  55. }
  56. }
  57. }
  58. elseif (
  59. ($u['scheme'] === 'tcp')
  60. || ($u['scheme'] === 'tcpstream')
  61. ) {
  62. if (
  63. !Daemon::$useSockets
  64. || ($u['scheme'] === 'tcpstream')
  65. ) {
  66. $readFD = stream_socket_client('tcp://' . substr($readFD, 12), $errno, $errstr, 1);
  67. } else {
  68. $readFD = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  69. if (!socket_connect($readFD, $u['host'], $u['port'])) {
  70. socket_close($readFD);
  71. $readFD = FALSE;
  72. }
  73. }
  74. }
  75. elseif ($u['scheme'] === 'udp') {
  76. if (!Daemon::$useSockets) {
  77. $readFD = stream_socket_client($readFD, $errno, $errstr, 1);
  78. } else {
  79. $readFD = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
  80. if (!socket_connect($readFD, $u['host'], $u['port'])) {
  81. socket_close($readFD);
  82. $readFD = FALSE;
  83. }
  84. }
  85. }
  86. elseif ($u['scheme'] === 'file') {
  87. $this->filePath = substr($url, 7);
  88. $readFD = @fopen($this->filePath, 'r');
  89. $this->fileMode = TRUE;
  90. }
  91. }
  92. if ($readFD !== NULL) {
  93. $this->setFD($readFD,$writeFD);
  94. }
  95. return $readFD !== FALSE;
  96. }
  97. public function setReadPacketSize($n) {
  98. $this->readPacketSize = $n;
  99. return $this;
  100. }
  101. public function finishWrite() {
  102. if (!$this->writeState) {
  103. $this->closeWrite();
  104. }
  105. $this->finishWrite = TRUE;
  106. return TRUE;
  107. }
  108. public function setFD($readFD, $writeFD = NULL) {
  109. $this->readFD = $readFD;
  110. $this->writeFD = $writeFD;
  111. if (!is_resource($this->readFD)) {
  112. throw new BadStreamDescriptorException('wrong readFD', 1);
  113. }
  114. if ($this->readBuf === NULL) {
  115. if (!stream_set_blocking($this->readFD, 0)) {
  116. throw new Exception('setting blocking for read stream failed');
  117. }
  118. $this->readBuf = event_buffer_new(
  119. $this->readFD,
  120. array($this, 'onReadEvent'),
  121. array($this, 'onWriteEvent'),
  122. array($this, 'onReadFailureEvent'),
  123. array()
  124. );
  125. if (!$this->readBuf) {
  126. throw new Exception('creating read buffer failed');
  127. }
  128. if (!event_buffer_base_set($this->readBuf, Daemon::$process->eventBase)) {
  129. throw new Exception('wrong base');
  130. }
  131. if (
  132. (event_buffer_priority_set($this->readBuf, $this->readPriority) === FALSE)
  133. && FALSE
  134. ) {
  135. throw new Exception('setting priority for read buffer failed');
  136. }
  137. } else {
  138. if (!stream_set_blocking($this->readFD, 0)) {
  139. throw new Exception('setting blocking for read stream failed');
  140. }
  141. if (!event_buffer_fd_set($this->readBuf, $this->readFD)) {
  142. throw new Exception('setting descriptor for write buffer failed');
  143. }
  144. }
  145. if ($this->writeFD === NULL) {
  146. return $this;
  147. }
  148. if (!is_resource($this->writeFD)) {
  149. throw new BadStreamDescriptorException('wrong writeFD',1);
  150. }
  151. if ($this->writeBuf === NULL) {
  152. if (!stream_set_blocking($this->writeFD, 0)) {
  153. throw new Exception('setting blocking for write stream failed');
  154. }
  155. $this->writeBuf = event_buffer_new(
  156. $this->writeFD,
  157. NULL,
  158. array($this, 'onWriteEvent'),
  159. array($this, 'onWriteFailureEvent'),
  160. array()
  161. );
  162. if (!$this->writeBuf) {
  163. throw new Exception('creating write buffer failed');
  164. }
  165. if (!event_buffer_base_set($this->writeBuf, Daemon::$process->eventBase)) {
  166. throw new Exception('wrong base');
  167. }
  168. if (
  169. (event_buffer_priority_set($this->writeBuf, $this->writePriority) === FALSE)
  170. && FALSE
  171. ) {
  172. throw new Exception('setting priority for write buffer failed');
  173. }
  174. } else {
  175. stream_set_blocking($this->writeFD, 0);
  176. event_buffer_fd_set($this->buf, $this->writeFD);
  177. }
  178. return $this;
  179. }
  180. public function closeRead() {
  181. if (is_resource($this->readBuf)) {
  182. if (event_buffer_free($this->readBuf) === FALSE) {
  183. $this->readBuf = FALSE;
  184. throw new Exception('freeing read buffer failed.');
  185. }
  186. $this->readBuf = FALSE;
  187. }
  188. if ($this->readFD) {
  189. fclose($this->readFD);
  190. $this->readFD = FALSE;
  191. }
  192. return $this;
  193. }
  194. public function closeWrite() {
  195. if (is_resource($this->writeBuf)) {
  196. if (event_buffer_free($this->writeBuf) === FALSE) {
  197. $this->writeBuf = FALSE;
  198. throw new Exception('freeing write buffer failed.');
  199. }
  200. $this->writeBuf = FALSE;
  201. }
  202. if ($this->writeFD) {
  203. fclose($this->writeFD);
  204. $this->writeFD = FALSE;
  205. }
  206. return $this;
  207. }
  208. public function close() {
  209. $this->closeRead();
  210. $this->closeWrite();
  211. }
  212. public function onRead($cb = NULL) {
  213. $this->onRead = $cb;
  214. return $this;
  215. }
  216. public function onReadData($cb = NULL) {
  217. $this->onReadData = $cb;
  218. return $this;
  219. }
  220. public function onWrite($cb = NULL) {
  221. $this->onWrite = $cb;
  222. return $this;
  223. }
  224. public function onFailure($read = NULL, $write = NULL) {
  225. if ($write === NULL) {
  226. $write = $read;
  227. }
  228. $this->onReadFailure = $read;
  229. $this->onWriteFailure = $write;
  230. return $this;
  231. }
  232. public function onEOF($cb = NULL) {
  233. $this->onEOF = $cb;
  234. return $this;
  235. }
  236. public function enable() {
  237. $mode = EV_READ | EV_PERSIST;
  238. if ($this->writeBuf === NULL) {
  239. $mode |= EV_WRITE;
  240. }
  241. if (!event_buffer_enable($this->readBuf, $mode)) {
  242. if ($this->fileMode) {
  243. $this->noEvents = TRUE;
  244. } else {
  245. throw new Exception('enabling read buffer failed');
  246. }
  247. }
  248. if ($this->writeBuf !== NULL) {
  249. if (!event_buffer_enable($this->writeBuf, EV_WRITE | EV_PERSIST)) {
  250. throw new Exception('enabling write buffer failed');
  251. }
  252. }
  253. return $this;
  254. }
  255. public function setPriority($read, $write) {
  256. $this->readPriority = $read;
  257. $this->writePriority = $write;
  258. if ($this->readBuf !== NULL) {
  259. if (event_buffer_priority_set($this->readBuf, $this->readPriority) === FALSE) {
  260. throw new Exception('setting priority for read buffer failed');
  261. }
  262. }
  263. if ($this->writeBuf !== NULL) {
  264. if (event_buffer_priority_set($this->writeBuf, $this->writePriority) === FALSE) {
  265. throw new Exception('setting priority for read buffer failed');
  266. }
  267. }
  268. return $this;
  269. }
  270. public function setRequest($request) {
  271. $this->request = $request;
  272. return $this;
  273. }
  274. public function readMask($low = NULL, $high = NULL) {
  275. if ($low === NULL) {
  276. $low = 1;
  277. }
  278. if ($high === NULL) {
  279. $high = 0xFFFFFF;
  280. }
  281. if (!event_buffer_watermark_set($this->readBuf, EV_READ, $low, $high)) {
  282. throw new Exception('readMask(' . $low . ',' . $high . ') failed.');
  283. }
  284. return $this;
  285. }
  286. public function writeMask($low = NULL, $high = NULL) {
  287. if ($low === NULL) {
  288. $low = 1;
  289. }
  290. if ($high === NULL) {
  291. $high = 0xFFFFFF;
  292. }
  293. if (!event_buffer_watermark_set($this->writeBuf, EV_WRITE, $low, $high)) {
  294. throw new Exception('writeMask(' . $low . ',' . $high . ') failed.');
  295. }
  296. return $this;
  297. }
  298. public function read($n = NULL) {
  299. if ($n === NULL) {
  300. $n = $this->readPacketSize;
  301. }
  302. if ($this->noEvents) {
  303. if (!$this->readFD) {
  304. return '';
  305. }
  306. $data = fread($this->readFD, $n);
  307. if ($data === FALSE) {
  308. return '';
  309. }
  310. return $data;
  311. }
  312. if ($this->readBuf === FALSE) {
  313. return '';
  314. }
  315. $r = event_buffer_read($this->readBuf, $n);
  316. if ($r === NULL) {
  317. $r = '';
  318. }
  319. if ($r === FALSE) {
  320. throw new Exception('read buffer failed.');
  321. }
  322. return $r;
  323. }
  324. public function write($s) {
  325. $b = ($this->writeBuf !== NULL) ? $this->writeBuf : $this->readBuf;
  326. if ($b === FALSE) {
  327. return $this;
  328. }
  329. if ($s !== '') {
  330. $this->writeState = TRUE;
  331. }
  332. if (!event_buffer_write($b, $s)) {
  333. throw new Exception('write() failed.');
  334. }
  335. return $this;
  336. }
  337. public function onEofEvent() {
  338. $this->EOF = TRUE;
  339. if ($this->onEOF !== NULL) {
  340. call_user_func($this->onEOF, $this);
  341. }
  342. $this->closeRead();
  343. }
  344. public function eof() {
  345. if (
  346. !$this->EOF && (
  347. ($this->readFD === FALSE)
  348. || feof($this->readFD)
  349. )
  350. ) {
  351. $this->onEofEvent();
  352. }
  353. elseif (
  354. !$this->EOF
  355. && $this->noEvents
  356. ) {
  357. $this->onReadEvent();
  358. }
  359. return $this->EOF;
  360. }
  361. public function onReadEvent($buf = NULL, $arg = NULL) {
  362. if (Daemon::$config->logevents->value) {
  363. Daemon::log(__METHOD__ . '()');
  364. }
  365. if ($this->onReadData !== NULL) {
  366. while (($data = $this->read()) !== '') {
  367. if ($data === FALSE) {
  368. throw new Exception('read() returned false');
  369. }
  370. call_user_func($this->onReadData, $this, $data);
  371. }
  372. $this->eof();
  373. }
  374. elseif ($this->onRead !== NULL) {
  375. call_user_func($this->onRead, $this);
  376. }
  377. }
  378. public function onWriteEvent($buf, $arg = NULL) {
  379. if (Daemon::$config->logevents->value) {
  380. Daemon::log(__METHOD__ . '()');
  381. }
  382. $this->writeState = FALSE;
  383. if ($this->onWrite !== NULL) {
  384. call_user_func($this->onWrite, $this);
  385. }
  386. if ($this->finishWrite) {
  387. $this->closeWrite();
  388. }
  389. }
  390. public function onReadFailureEvent($buf, $arg = NULL) {
  391. if (Daemon::$config->logevents->value) {
  392. Daemon::log(__METHOD__ . '()');
  393. }
  394. if ($this->onReadFailure !== NULL) {
  395. call_user_func($this->onReadFailure, $this);
  396. }
  397. event_base_loopexit(Daemon::$process->eventBase);
  398. $this->closeRead();
  399. }
  400. public function onWriteFailureEvent($buf, $arg = NULL) {
  401. if (Daemon::$config->logevents->value) {
  402. Daemon::log(__METHOD__ . '()');
  403. }
  404. if ($this->onWriteFailure !== NULL) {
  405. call_user_func($this->onWriteFailure, $this);
  406. }
  407. event_base_loopexit(Daemon::$process->eventBase);
  408. $this->closeWrite();
  409. }
  410. }
  411. class BadStreamDescriptorException extends Exception {}