socket.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <?php
  2. require_once(realpath(dirname(__FILE__)).'/common.php');
  3. class ClientSocket extends SocketServer {
  4. public $connected = array();
  5. private $received = array();
  6. function onDataReceived($socket,$data) {
  7. $socketId = (int)$socket;
  8. if(!isset($received[$socketId] )) $received[$socketId] = '';
  9. $received[$socketId].= $data;
  10. if(substr($received[$socketId],-5)=='<EOF>'){
  11. $received[$socketId] = substr($received[$socketId],0,-5);
  12. $this->handleData($this->connected[(int)$socket],$received[$socketId]);
  13. $received[$socketId] = '';
  14. }
  15. }
  16. function onClientConnected($socket) {
  17. $this->log('New client connected: ' . $socket);
  18. $client = new ClientDevice();
  19. $client->socket= $socket;
  20. $client->id= (int)$socket;
  21. $client->name = 'Client '.count($this->clients);
  22. $this->connected[(int)$socket] = $client;
  23. }
  24. function onClientDisconnected($socket) {
  25. $client = $this->connected[(int)$socket];
  26. $this->log($client->type.' - '.$client->location . ' disconnected');
  27. unset($this->connected[(int)$socket]);
  28. $this->clientdisconnected($client);
  29. //$this->sendBroadcast($socket . ' left the room');
  30. }
  31. function handleData($client,$data){
  32. $this->log("Try to parse received data : ".$data);
  33. try{
  34. $datas = explode('<EOF>',$data);
  35. foreach($datas as $data){
  36. $_ = json_decode($data,true);
  37. if(!$_) throw new Exception("Unable to parse data : ".$data);
  38. if(!isset($_['action'])) $_['action'] = '';
  39. $this->log("Parsed action : ".$_['action']);
  40. switch($_['action']){
  41. case 'TALK':
  42. $this->talkAnimate();
  43. $this->talk($_['parameter']);
  44. break;
  45. case 'TALK_FINISHED':
  46. $this->muteAnimate();
  47. break;
  48. case 'EMOTION':
  49. $this->emotion($_['parameter']);
  50. break;
  51. case 'IMAGE':
  52. $this->image($_['parameter']);
  53. break;
  54. case 'SOUND':
  55. $this->sound($_['parameter']);
  56. break;
  57. case 'EXECUTE':
  58. $this->execute($_['parameter']);
  59. break;
  60. case 'CLIENT_INFOS':
  61. $client->type = $_['type'];
  62. $client->location = $_['location'];
  63. $userManager = new User();
  64. $myUser = $userManager->load(array('token'=>$_['token']));
  65. if(isset($myUser) && $myUser!=false)
  66. $myUser->loadRight();
  67. $client->user = (!$myUser?new User():$myUser);
  68. $this->log('setting infos '.$client->type.' - '.$client->location.' for '.$client->name.' with user:'.$client->user->getLogin());
  69. $this->clientConnected($client);
  70. break;
  71. case 'GET_SPEECH_COMMANDS':
  72. $response = array();
  73. Plugin::callHook("vocal_command", array(&$response,YANA_URL));
  74. $commands = array();
  75. foreach($response['commands'] as $command){
  76. unset($command['url']);
  77. $this->send($this->connected[$client->id]->socket,'{"action":"ADD_COMMAND","command":'.json_encode($command).'}');
  78. }
  79. $this->send($this->connected[$client->id]->socket,'{"action":"UPDATE_COMMANDS"}');
  80. break;
  81. case 'GET_CONNECTED_CLIENTS':
  82. $response = array();
  83. foreach($this->connected as $id=>$cli){
  84. $this->send($this->connected[$client->id]->socket,'{"action":"clientConnected","client":{"type":"'.$cli->type.'","location":"'.$cli->location.'","user":"'.($cli->user!=null && is_object($cli->user)?$cli->user->getLogin():'Anonyme').'"}}');
  85. }
  86. break;
  87. case 'CATCH_COMMAND':
  88. $response = "";
  89. $this->log("Call listen hook (v2.0 plugins) with params ".$_['command']." > ".$_['text']." > ".$_['confidence']);
  90. Plugin::callHook('listen',array($_['command'],trim(str_replace($_['command'],'',$_['text'])),$_['confidence'],$client->user));
  91. break;
  92. case '':
  93. default:
  94. //$this->talk("Coucou");
  95. //$this->sound("C:/poule.wav");
  96. //$this->execute("C:\Program Files (x86)\PuTTY\putty.exe");
  97. $this->log($client->name.'('.$client->type.') send '.$data);
  98. break;
  99. }
  100. $this->updateClient($client);
  101. }
  102. }catch(Exception $e){
  103. $this->log("ERROR : ".$e->getMessage());
  104. }
  105. //system('php '.realpath(dirname(__FILE__)).'\action.php '.$json['action'],$out);
  106. //$this->send($socket,$out);
  107. }
  108. function updateClient($client){
  109. $this->connected[$client->id] = $client;
  110. }
  111. public function sound($message,$clients=array()){
  112. if(count($clients)==0)
  113. $clients = $this->getByType('speak');
  114. foreach($clients as $client){
  115. $socket = $this->connected[$client->id]->socket;
  116. $this->send($socket,'{"action":"sound","file":"'.str_replace('\\','/',$message).'"}');
  117. }
  118. }
  119. public function talk($message,$clients=array()){
  120. if(count($clients)==0)
  121. $clients = $this->getByType('speak');
  122. $this->log("TALK : Try to send ".$message." to ".count($clients)." clients");
  123. foreach($clients as $client){
  124. $socket = $this->connected[$client->id]->socket;
  125. $this->log("send ".'{"action":"talk","message":"'.$message.'"} to '.$client->name);
  126. $this->send($socket,'{"action":"talk","message":"'.$message.'"}');
  127. }
  128. }
  129. public function clientConnected($new_client,$clients=array()){
  130. if(count($clients)==0)
  131. $clients = $this->getByType('face');
  132. //$this->log("CONNECTED : Try to send ".$emotion." to ".count($clients)." clients");
  133. foreach($clients as $client){
  134. if($client->id == $new_client->id) continue;
  135. $socket = $this->connected[$client->id]->socket;
  136. $packet = '{"action":"clientConnected","client":{"type":"'.$new_client->type.'","location":"'.$new_client->location.'","user":"'.($new_client->user!=null && is_object($new_client->user)?$new_client->user->getLogin():'Anonyme').'"}}';
  137. $this->log("send ".$packet." to ".$client->name);
  138. $this->send($socket,$packet);
  139. }
  140. }
  141. public function clientDisconnected($new_client,$clients=array()){
  142. if(count($clients)==0)
  143. $clients = $this->getByType('face');
  144. //$this->log("CONNECTED : Try to send ".$emotion." to ".count($clients)." clients");
  145. foreach($clients as $client){
  146. $socket = $this->connected[$client->id]->socket;
  147. $packet = '{"action":"clientDisconnected","client":{"type":"'.$new_client->type.'","location":"'.$new_client->location.'"}}';
  148. $this->log("send ".$packet." to ".$client->name);
  149. $this->send($socket,$packet);
  150. }
  151. }
  152. public function emotion($emotion,$clients=array()){
  153. if(count($clients)==0)
  154. $clients = $this->getByType('face');
  155. $this->log("EMOTION : Try to send ".$emotion." to ".count($clients)." clients");
  156. foreach($clients as $client){
  157. $socket = $this->connected[$client->id]->socket;
  158. $packet = '{"action":"emotion","type":"'.$emotion.'"}';
  159. $this->log("send ".$packet." to ".$client->name);
  160. $this->send($socket,$packet);
  161. }
  162. }
  163. public function image($image,$clients=array()){
  164. if(count($clients)==0)
  165. $clients = $this->getByType('face');
  166. $this->log("IMAGE : Try to send ".$image." to ".count($clients)." clients");
  167. foreach($clients as $client){
  168. $socket = $this->connected[$client->id]->socket;
  169. $packet = '{"action":"image","url":"'.$image.'"}';
  170. $this->log("send ".$packet." to ".$client->name);
  171. $this->send($socket,$packet);
  172. }
  173. }
  174. public function talkAnimate($clients=array()){
  175. if(count($clients)==0)
  176. $clients = $this->getByType('face');
  177. $this->log("TALK ANIMATION : Try to send ".$emotion." to ".count($clients)." clients");
  178. foreach($clients as $client){
  179. $socket = $this->connected[$client->id]->socket;
  180. $packet = '{"action":"talk"}';
  181. $this->log("send ".$packet." to ".$client->name);
  182. $this->send($socket,$packet);
  183. }
  184. }
  185. public function muteAnimate($clients=array()){
  186. if(count($clients)==0)
  187. $clients = $this->getByType('face');
  188. $this->log("MUTE ANIMATION : Try to send ".$emotion." to ".count($clients)." clients");
  189. foreach($clients as $client){
  190. $socket = $this->connected[$client->id]->socket;
  191. $packet = '{"action":"mute"}';
  192. $this->log("send ".$packet." to ".$client->name);
  193. $this->send($socket,$packet);
  194. }
  195. }
  196. public function url($message,$clients=array()){
  197. echo "Envois de l\'url".$message;
  198. if(count($clients)==0)
  199. $clients = $this->getByType('speak');
  200. if(count($clients)==0) return;
  201. foreach($clients as $client){
  202. //$client = $clients[0];
  203. $socket = $this->connected[$client->id]->socket;
  204. $this->log("url ".'{"action":"url","url":"'.$message.'"} to '.$client->name);
  205. $this->send($socket,'{"action":"url","url":"'.$message.'"}');
  206. }
  207. }
  208. public function execute($message,$clients=array()){
  209. if(count($clients)==0)
  210. $clients = $this->getByType('speak');
  211. foreach($clients as $client){
  212. $socket = $this->connected[$client->id]->socket;
  213. $this->log("send ".'{"action":"execute","command":"'.$message.'"} to '.$client->name);
  214. $this->send($socket,'{"action":"execute","command":"'.str_replace('\\','/',$message).'"}');
  215. }
  216. }
  217. public function getByType($type){
  218. $clients =array();
  219. foreach ($this->connected as $client)
  220. if($client->type == $type) $clients[] = $client;
  221. return $clients;
  222. }
  223. private $lastMessage;
  224. }
  225. require_once('constant.php');
  226. logs("Launch Program");
  227. $client = new ClientSocket('0.0.0.0',SOCKET_PORT,SOCKET_MAX_CLIENTS);
  228. $client->start();
  229. class ClientDevice {
  230. public $id,$type,$socket,$location,$user;
  231. }
  232. /**
  233. * Class to handle a sockets server
  234. * It's abstract class so you need to create another class that will extends SocketServer to run your server
  235. *
  236. * @author Cyril Mazur www.cyrilmazur.com twitter.com/CyrilMazur facebook.com/CyrilMazur
  237. * @abstract
  238. */
  239. abstract class SocketServer {
  240. /**
  241. * The address the socket will be bound to
  242. * @var string
  243. */
  244. protected $address;
  245. /**
  246. * The port the socket will be bound to
  247. * @var int
  248. */
  249. protected $port;
  250. /**
  251. * The max number of clients authorized
  252. * @var int
  253. */
  254. protected $maxClients;
  255. /**
  256. * Array containing all the connected clients
  257. * @var array
  258. */
  259. protected $clients;
  260. /**
  261. * The master socket
  262. * @var resource
  263. */
  264. protected $master;
  265. /**
  266. * Constructor
  267. * @param string $address
  268. * @param int $port
  269. * @param int $maxClients
  270. * @return SocketServer
  271. */
  272. public function __construct($address,$port,$maxClients) {
  273. $this->address = $address;
  274. $this->port = $port;
  275. $this->maxClients = $maxClients;
  276. $this->clients = array();
  277. }
  278. /**
  279. * Start the server
  280. */
  281. public function start() {
  282. // flush all the output directly
  283. ob_implicit_flush();
  284. // create master socket
  285. $this->master = @socket_create(AF_INET, SOCK_STREAM, 0) or die($this->log('Could not create socket'));
  286. // to prevent: address already in use
  287. //socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die($this->log('Could not set up SO_REUSEADDR',true));
  288. // bind socket to port
  289. @socket_bind($this->master, $this->address, $this->port) or die($this->log('Could not bind to socket',true));
  290. // start listening for connections
  291. socket_listen($this->master) or die($this->log('Could not set up socket listener'));
  292. $this->log('Server started on ' . $this->address . ':' . $this->port);
  293. // infinite loop
  294. while(true) {
  295. // build the array of sockets to select
  296. $read = array_merge(array($this->master),$this->clients);
  297. $write = NULL;
  298. $except = NULL;
  299. $tv_sec = NULL;
  300. // if no socket has changed its status, continue the loop
  301. socket_select($read,$write,$except,$tv_sec);
  302. // if the master's status changed, it means a new client would like to connect
  303. if (in_array($this->master,$read)) {
  304. // if we didn't reach the maximum amount of connected clients
  305. if (sizeof($this->clients) < $this->maxClients) {
  306. // attempt to create a new socket
  307. $socket = socket_accept($this->master);
  308. // if socket created successfuly, add it to the clients array and write message log
  309. if ($socket !== false) {
  310. $this->clients[] = $socket;
  311. if (socket_getpeername($socket,$ip)) {
  312. $this->log('New client connected: ' . $socket . ' (' . $ip . ')');
  313. } else {
  314. $this->log('New client connected: ' . $socket);
  315. }
  316. $this->onClientConnected($socket);
  317. // else display error message to the log console
  318. } else {
  319. $this->log('Impossible to connect new client',true);
  320. }
  321. // else tell the client that there is not place available and display error message to the log console
  322. } else {
  323. $socket = socket_accept($this->master);
  324. socket_write($socket,'Max clients reached. Retry later.' . chr(0));
  325. socket_close($socket);
  326. $this->log('Impossible to connect new client: maxClients reached');
  327. }
  328. if (sizeof($read) == 1)
  329. continue;
  330. }
  331. // foreach client that is ready to be read
  332. foreach($read as $client) {
  333. // we don't read data from the master socket
  334. if ($client != $this->master) {
  335. // read input
  336. $input = @socket_read($client, 1024, PHP_BINARY_READ);
  337. // if socket_read() returned false, the client has been disconnected
  338. if (strlen($input) == 0) {
  339. // disconnect client
  340. $this->disconnect($client);
  341. // custom method called
  342. $this->onClientDisconnected($client);
  343. // else, we received a normal message
  344. } else {
  345. $input = trim($input);
  346. // special case of a domain policy file request
  347. if ($input == '<policy-file-request/>') {
  348. $cmd = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cross-domain-policy xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd\"><allow-access-from domain=\"*\" to-ports=\"*\" secure=\"false\" /><site-control permitted-cross-domain-policies=\"master-only\" /></cross-domain-policy>";
  349. $this->log('Policy file requested by ' . $client);
  350. socket_write($client,$cmd . chr(0));
  351. // normal case, standard message
  352. } else {
  353. // custom method called
  354. $this->onDataReceived($client,$input);
  355. }
  356. }
  357. }
  358. }
  359. }
  360. }
  361. /**
  362. * Stop the server: disconnect all the coonected clients, close the master socket
  363. */
  364. public function stop() {
  365. foreach($this->clients as $client) {
  366. socket_close($client);
  367. }
  368. $this->clients = array();
  369. socket_close($this->master);
  370. }
  371. /**
  372. * Disconnect a client
  373. * @param resource $client
  374. * @return bool
  375. */
  376. protected function disconnect($client) {
  377. // close socket
  378. socket_close($client);
  379. // unset variable in the clients array
  380. $key = array_keys($this->clients,$client);
  381. unset($this->clients[$key[0]]);
  382. $this->log('Client disconnected: ' . $client);
  383. return true;
  384. }
  385. /**
  386. * Send data to a client
  387. * @param resource $client
  388. * @param string $data
  389. * @return bool
  390. */
  391. protected function send($client,$data) {
  392. @socket_write($client, $data);
  393. usleep(100);
  394. @socket_write($client, "<EOF>");
  395. }
  396. /**
  397. * Send data to everybody
  398. * @param string $data
  399. * @return bool
  400. */
  401. protected function sendBroadcast($data) {
  402. $return = true;
  403. foreach($this->clients as $client) {
  404. $return = $return && socket_write($client, $data . chr(0));
  405. }
  406. return $return;
  407. }
  408. /**
  409. * Method called after a value had been read
  410. * @abstract
  411. * @param resource $socket
  412. * @param string $data
  413. */
  414. abstract protected function onDataReceived($socket,$data);
  415. /**
  416. * Method called after a new client is connected
  417. * @param resource $socket
  418. */
  419. abstract protected function onClientConnected($socket);
  420. /**
  421. * Method called after a new client is disconnected
  422. * @param resource $socket
  423. */
  424. abstract protected function onClientDisconnected($socket);
  425. /**
  426. * Write log messages to the console
  427. * @param string $message
  428. * @param bool $socketError
  429. */
  430. public function log($message,$socketError = false) {
  431. echo '[' . date('d/m/Y H:i:s') . '] ' . $message;
  432. if ($socketError) {
  433. $errNo = socket_last_error();
  434. $errMsg = socket_strerror($errNo);
  435. echo ' : #' . $errNo . ' ' . $errMsg;
  436. }
  437. echo "\n";
  438. }
  439. }
  440. function logs($message) {
  441. echo '[' . date('d/m/Y H:i:s') . '] ' . $message.PHP_EOL;
  442. }
  443. ?>