PageRenderTime 52ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/experimental/php/external/patserver.class.php

https://github.com/matschaffer/fakemail
PHP | 485 lines | 236 code | 70 blank | 179 comment | 57 complexity | 5d181988125244edda02db00e58cdf17 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * patServer
  4. * PHP socket server base class
  5. * Events that can be handled:
  6. * * onStart
  7. * * onConnect
  8. * * onConnectionRefused
  9. * * onClose
  10. * * onShutdown
  11. * * afterShutdown
  12. * * onReceiveData
  13. *
  14. * @version 1.0.1
  15. * @author Stephan Schmidt <schst@php-tools.de>
  16. * @package patServer
  17. */
  18. class patServer
  19. {
  20. /**
  21. * port to listen
  22. * @var integer $port
  23. */
  24. var $port = 10000;
  25. /**
  26. * domain to bind to
  27. * @var string $domain
  28. */
  29. var $domain = "localhost";
  30. /**
  31. * maximum amount of clients
  32. * @var integer $maxClients
  33. */
  34. var $maxClients = -1;
  35. /**
  36. * buffer size for socket_read
  37. * @var integer $readBufferSize
  38. */
  39. var $readBufferSize = 512;
  40. /**
  41. * end character for socket_read
  42. * @var integer $readEndCharacter
  43. */
  44. var $readEndCharacter = "\n";
  45. /**
  46. * maximum of backlog in queue
  47. * @var integer $maxQueue
  48. */
  49. var $maxQueue = 500;
  50. /**
  51. * debug mode
  52. * @var boolean $debug
  53. */
  54. var $debug = false;
  55. /**
  56. * debug mode
  57. * @var string $debugMode
  58. */
  59. var $debugMode = "text";
  60. /**
  61. * debug destination (filename or stdout)
  62. * @var string $debugDest
  63. */
  64. var $debugDest = "stdout";
  65. /**
  66. * empty array, used for socket_select
  67. * @var array $null
  68. */
  69. var $null = array ();
  70. /**
  71. * all file descriptors are stored here
  72. * @var array $clientFD
  73. */
  74. var $clientFD = array ();
  75. /**
  76. * needed to store client information
  77. * @var array $clientInfo
  78. */
  79. var $clientInfo = array ();
  80. /**
  81. * amount of clients
  82. * @var integer $clients
  83. */
  84. var $clients = 0;
  85. /**
  86. * create a new socket server
  87. *
  88. * @access public
  89. * @param string $domain domain to bind to
  90. * @param integer $port port to listen to
  91. */
  92. function patServer($domain = "localhost", $port = 10000)
  93. {
  94. $this->domain = $domain;
  95. $this->port = $port;
  96. set_time_limit(0);
  97. }
  98. /**
  99. * set maximum amount of simultaneous connections
  100. *
  101. * @access public
  102. * @param int $maxClients
  103. */
  104. function setMaxClients($maxClients)
  105. {
  106. $this->maxClients = $maxClients;
  107. }
  108. /**
  109. * set debug mode
  110. *
  111. * @access public
  112. * @param mixed $debug [text|htmlfalse]
  113. * @param string $dest destination of debug message (stdout to output or filename if log should be written)
  114. */
  115. function setDebugMode($debug, $dest = "stdout")
  116. {
  117. if ($debug === false)
  118. {
  119. $this->debug = false;
  120. return true;
  121. }
  122. $this->debug = true;
  123. $this->debugMode = $debug;
  124. $this->debugDest = $dest;
  125. }
  126. /**
  127. * start the server
  128. *
  129. * @access public
  130. * @param int $maxClients
  131. */
  132. function start()
  133. {
  134. $this->initFD = @ socket_create(AF_INET, SOCK_STREAM, 0);
  135. if (!$this->initFD)
  136. die("patServer: Could not create socket.");
  137. // adress may be reused
  138. socket_setopt($this->initFD, SOL_SOCKET, SO_REUSEADDR, 1);
  139. // bind the socket
  140. if (!@ socket_bind($this->initFD, $this->domain, $this->port))
  141. {
  142. @ socket_close($this->initFD);
  143. die("patServer: Could not bind socket to ".$this->domain." on port ".$this->port." ( ".$this->getLastSocketError($this->initFd)." ).");
  144. }
  145. // listen on selected port
  146. if (!@ socket_listen($this->initFD, $this->maxQueue))
  147. die("patServer: Could not listen ( ".$this->getLastSocketError($this->initFd)." ).");
  148. $this->sendDebugMessage("Listening on port ".$this->port.". Server started at ".date("H:i:s", time()));
  149. // this allows the shutdown function to check whether the server is already shut down
  150. $GLOBALS["_patServerStatus"] = "running";
  151. // this ensures that the server will be sutdown correctly
  152. register_shutdown_function(array ($this, "shutdown"));
  153. if (method_exists($this, "onStart"))
  154. $this->onStart();
  155. while (true)
  156. {
  157. $readFDs = array ();
  158. array_push($readFDs, $this->initFD);
  159. // fetch all clients that are awaiting connections
  160. for ($i = 0; $i < count($this->clientFD); $i ++)
  161. if (isset ($this->clientFD[$i]))
  162. array_push($readFDs, $this->clientFD[$i]);
  163. // block and wait for data or new connection
  164. $ready = @ socket_select($readFDs, $this->null, $this->null, NULL);
  165. if ($ready === false)
  166. {
  167. $this->sendDebugMessage("socket_select failed.");
  168. $this->shutdown();
  169. }
  170. // check for new connection
  171. if (in_array($this->initFD, $readFDs))
  172. {
  173. $newClient = $this->acceptConnection($this->initFD);
  174. // check for maximum amount of connections
  175. if ($this->maxClients > 0)
  176. {
  177. if ($this->clients > $this->maxClients)
  178. {
  179. $this->sendDebugMessage("Too many connections.");
  180. if (method_exists($this, "onConnectionRefused"))
  181. $this->onConnectionRefused($newClient);
  182. $this->closeConnection($newClient);
  183. }
  184. }
  185. if (-- $ready <= 0)
  186. continue;
  187. }
  188. // check all clients for incoming data
  189. for ($i = 0; $i < count($this->clientFD); $i ++)
  190. {
  191. if (!isset ($this->clientFD[$i]))
  192. continue;
  193. if (in_array($this->clientFD[$i], $readFDs))
  194. {
  195. $data = $this->readFromSocket($i);
  196. // empty data => connection was closed
  197. if (!$data)
  198. {
  199. $this->sendDebugMessage("Connection closed by peer");
  200. $this->closeConnection($i);
  201. }
  202. else
  203. {
  204. $this->sendDebugMessage("Received ".trim($data)." from ".$i);
  205. if (method_exists($this, "onReceiveData"))
  206. $this->onReceiveData($i, $data);
  207. }
  208. }
  209. }
  210. }
  211. }
  212. /**
  213. * read from a socket
  214. *
  215. * @access private
  216. * @param integer $clientId internal id of the client to read from
  217. * @return string $data data that was read
  218. */
  219. function readFromSocket($clientId)
  220. {
  221. // start with empty string
  222. $data = "";
  223. // read data from socket
  224. while ($buf = @ socket_read($this->clientFD[$clientId], $this->readBufferSize))
  225. {
  226. $data .= $buf;
  227. $endString = substr($buf, -strlen($this->readEndCharacter));
  228. if ($endString == $this->readEndCharacter)
  229. break;
  230. }
  231. if ($buf === false)
  232. $this->sendDebugMessage("Could not read from client ".$clientId." ( ".$this->getLastSocketError($this->clientFD[$clientId])." ).");
  233. return $data;
  234. }
  235. /**
  236. * accept a new connection
  237. *
  238. * @access public
  239. * @param resource &$socket socket that received the new connection
  240. * @return int $clientID internal ID of the client
  241. */
  242. function acceptConnection(& $socket)
  243. {
  244. for ($i = 0; $i <= count($this->clientFD); $i ++)
  245. {
  246. if (!isset ($this->clientFD[$i]) || $this->clientFD[$i] == NULL)
  247. {
  248. $this->clientFD[$i] = socket_accept($socket);
  249. socket_setopt($this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1);
  250. $peer_host = "";
  251. $peer_port = "";
  252. socket_getpeername($this->clientFD[$i], $peer_host, $peer_port);
  253. $this->clientInfo[$i] = array ("host" => $peer_host, "port" => $peer_port, "connectOn" => time());
  254. $this->clients++;
  255. $this->sendDebugMessage("New connection ( ".$i." ) from ".$peer_host." on port ".$peer_port);
  256. if (method_exists($this, "onConnect"))
  257. $this->onConnect($i);
  258. return $i;
  259. }
  260. }
  261. }
  262. /**
  263. * check, whether a client is still connected
  264. *
  265. * @access public
  266. * @param integer $id client id
  267. * @return boolean $connected true if client is connected, false otherwise
  268. */
  269. function isConnected($id)
  270. {
  271. if (!isset ($this->clientFD[$id]))
  272. return false;
  273. return true;
  274. }
  275. /**
  276. * close connection to a client
  277. *
  278. * @access public
  279. * @param int $clientID internal ID of the client
  280. */
  281. function closeConnection($id)
  282. {
  283. if (!isset ($this->clientFD[$id]))
  284. return false;
  285. if (method_exists($this, "onClose"))
  286. $this->onClose($id);
  287. $this->sendDebugMessage("Closed connection ( ".$id." ) from ".$this->clientInfo[$id]["host"]." on port ".$this->clientInfo[$id]["port"]);
  288. @ socket_close($this->clientFD[$id]);
  289. $this->clientFD[$id] = NULL;
  290. unset ($this->clientInfo[$id]);
  291. $this->clients--;
  292. }
  293. /**
  294. * shutdown server
  295. *
  296. * @access public
  297. */
  298. function shutDown()
  299. {
  300. if ($GLOBALS["_patServerStatus"] != "running")
  301. exit;
  302. $GLOBALS["_patServerStatus"] = "stopped";
  303. if (method_exists($this, "onShutdown"))
  304. $this->onShutdown();
  305. $maxFD = count($this->clientFD);
  306. for ($i = 0; $i < $maxFD; $i ++)
  307. $this->closeConnection($i);
  308. @ socket_close($this->initFD);
  309. $this->sendDebugMessage("Shutdown server.");
  310. if (method_exists($this, "afterShutdown"))
  311. $this->afterShutdown();
  312. exit;
  313. }
  314. /**
  315. * get current amount of clients
  316. *
  317. * @access public
  318. * @return int $clients amount of clients
  319. */
  320. function getClients()
  321. {
  322. return $this->clients;
  323. }
  324. /**
  325. * send data to a client
  326. *
  327. * @access public
  328. * @param int $clientId ID of the client
  329. * @param string $data data to send
  330. * @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message
  331. */
  332. function sendData($clientId, $data, $debugData = true)
  333. {
  334. if (!isset ($this->clientFD[$clientId]) || $this->clientFD[$clientId] == NULL)
  335. return false;
  336. if ($debugData)
  337. $this->sendDebugMessage("sending: \"".$data."\" to: $clientId");
  338. if (!@ socket_write($this->clientFD[$clientId], $data))
  339. $this->sendDebugMessage("Could not write '".$data."' client ".$clientId." ( ".$this->getLastSocketError($this->clientFD[$clientId])." ).");
  340. }
  341. /**
  342. * send data to all clients
  343. *
  344. * @access public
  345. * @param string $data data to send
  346. * @param array $exclude client ids to exclude
  347. */
  348. function broadcastData($data, $exclude = array ())
  349. {
  350. if (!empty ($exclude) && !is_array($exclude))
  351. $exclude = array ($exclude);
  352. for ($i = 0; $i < count($this->clientFD); $i ++)
  353. {
  354. if (isset ($this->clientFD[$i]) && $this->clientFD[$i] != NULL && !in_array($i, $exclude))
  355. {
  356. if (!@ socket_write($this->clientFD[$i], $data))
  357. $this->sendDebugMessage("Could not write '".$data."' client ".$i." ( ".$this->getLastSocketError($this->clientFD[$i])." ).");
  358. }
  359. }
  360. }
  361. /**
  362. * get current information about a client
  363. *
  364. * @access public
  365. * @param int $clientId ID of the client
  366. * @return array $info information about the client
  367. */
  368. function getClientInfo($clientId)
  369. {
  370. if (!isset ($this->clientFD[$clientId]) || $this->clientFD[$clientId] == NULL)
  371. return false;
  372. return $this->clientInfo[$clientId];
  373. }
  374. /**
  375. * send a debug message
  376. *
  377. * @access private
  378. * @param string $msg message to debug
  379. */
  380. function sendDebugMessage($msg)
  381. {
  382. if (!$this->debug)
  383. return false;
  384. $msg = date("Y-m-d H:i:s", time())." ".$msg;
  385. switch ($this->debugMode)
  386. {
  387. case "text" :
  388. $msg = $msg."\n";
  389. break;
  390. case "html" :
  391. $msg = htmlspecialchars($msg)."<br />\n";
  392. break;
  393. }
  394. if ($this->debugDest == "stdout" || empty ($this->debugDest))
  395. {
  396. echo $msg;
  397. flush();
  398. return true;
  399. }
  400. error_log($msg, 3, $this->debugDest);
  401. return true;
  402. }
  403. /**
  404. * return string for last socket error
  405. *
  406. * @access public
  407. * @return string $error last error
  408. */
  409. function getLastSocketError(& $fd)
  410. {
  411. $lastError = socket_last_error($fd);
  412. return "msg: ".socket_strerror($lastError)." / Code: ".$lastError;
  413. }
  414. }
  415. ?>