Last active
April 9, 2019 07:05
-
-
Save showsky/b15c92ac099e79217b526b60c98aa335 to your computer and use it in GitHub Desktop.
PHP Swoole + Redis = live chats room
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| define('SWOOLE_SERVER', '0.0.0.0'); | |
| define('SWOOLE_SERVER_PORT', '9502'); | |
| define('REDIS_SERVER', '127.0.0.1'); | |
| define('REDIS_SERVER_PORT', '6379'); | |
| define('REDIS_CHATS_KEY', 'chats'); | |
| define('TEMPLATE_REDIS_ROOM_KEY', 'chats_room:%d'); | |
| define('TEMPLATE_SEND', '[%s][%s] %s'); | |
| define('TEMPLATE_WELCOME', '目前在線人數 %d'); | |
| define('LOG_PATH', './log'); | |
| define('DEFAULT_ROOM', '0'); | |
| define('DEFAULT_NAME', '承德路上的承德'); | |
| define('DEFAULT_AVATAR_URL', 'https://avatars3.githubusercontent.com/u/2949145?s=400&v=4'); | |
| define('SSL_CERT_FILE', '/etc/letsencrypt/live/gameprice.tw/fullchain.pem'); | |
| define('SSL_KEY_FILE', '/etc/letsencrypt/live/gameprice.tw/privkey.pem'); | |
| class Server { | |
| private $_redis = NULL; | |
| private $_swoole = NULL; | |
| private $_is_ssl = FALSE; | |
| private $_worker_num = 2; | |
| public function __construct($is_ssl = FALSE, $worker_num = 2) { | |
| $this->_is_ssl = $is_ssl; | |
| $this->_worker_num = $worker_num; | |
| $this->init_redis(); | |
| $this->init_server(); | |
| } | |
| public function start() { | |
| $this->_swoole->start(); | |
| } | |
| private function init_redis() { | |
| $this->_redis = new Redis(); | |
| $this->_redis->pconnect(REDIS_SERVER, REDIS_SERVER_PORT); | |
| } | |
| private function init_server() { | |
| // reset hash | |
| $this->_redis->del(REDIS_CHATS_KEY); | |
| $this->_redis->del(sprintf(TEMPLATE_REDIS_ROOM_KEY, '*')); | |
| // swoole config | |
| $swoole_config['worker_num'] = $this->_worker_num; | |
| if ($this->_is_ssl) { | |
| $swoole_config['ssl_key_file'] = SSL_KEY_FILE; | |
| $swoole_config['ssl_cert_file'] = SSL_CERT_FILE; | |
| } | |
| $this->_swoole = new swoole_websocket_server(SWOOLE_SERVER, SWOOLE_SERVER_PORT, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); | |
| $this->_swoole->set($swoole_config); | |
| $this->_swoole->on('workerstart', array($this, 'onSwooleWorkerstart')); | |
| $this->_swoole->on('open', array($this, 'onSwooleOpen')); | |
| $this->_swoole->on('close', array($this, 'onSwooleClose')); | |
| $this->_swoole->on('message', array($this, 'onSwooleMessage')); | |
| } | |
| public function onSwooleWorkerstart($server, $id) { | |
| $this->init_redis(); | |
| } | |
| public function onSwooleClose($server, $fd) { | |
| echo 'close: ' . $fd; | |
| echo PHP_EOL; | |
| // remove redis key | |
| $room_key = $this->_redis->hGet(REDIS_CHATS_KEY, $fd); | |
| $this->_redis->hDel(REDIS_CHATS_KEY, $fd); | |
| $this->_redis->hDel($room_key, $fd); | |
| if ($this->_redis->hLen($room_key) == 0) { | |
| $this->_redis->hDel($room_key); | |
| } | |
| } | |
| public function onSwooleOpen($server, $req) { | |
| $name = (empty($req->get['name'])) ? DEFAULT_NAME : $req->get['name']; | |
| $room = (empty($req->get['room'])) ? DEFAULT_ROOM : $req->get['room']; | |
| $avatar_url = (empty($req->get['avatar'])) ? DEFAULT_AVATAR_URL : $req->get['avatar']; | |
| // load old message | |
| $array_last_line = $this->read_last_line( | |
| $this->get_log_filename(), | |
| 10 | |
| ); | |
| if (count($array_last_line) > 0) { | |
| $message = implode(PHP_EOL, $array_last_line); | |
| $server->push( | |
| $req->fd, | |
| $message | |
| ); | |
| } | |
| $room_key = sprintf( | |
| TEMPLATE_REDIS_ROOM_KEY, | |
| $room | |
| ); | |
| // welcome | |
| $conn_size = $this->_redis->hLen($room_key); | |
| $server->push( | |
| $req->fd, | |
| sprintf(TEMPLATE_WELCOME, $conn_size) | |
| ); | |
| echo 'open fd: ' . $req->fd . PHP_EOL; | |
| echo $name . PHP_EOL; | |
| $this->_redis->hSet(REDIS_CHATS_KEY, $req->fd, $room_key); | |
| $this->_redis->hSet( | |
| $room_key, $req->fd, | |
| $this->build_user_json_data($name, $avatar_url) | |
| ); | |
| } | |
| public function onSwooleMessage($server, $frame) { | |
| echo 'message fd: ' . $frame->fd; | |
| echo PHP_EOL; | |
| echo $frame->data; | |
| echo PHP_EOL; | |
| $it = NULL; | |
| $this->_redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); | |
| $room_key = $this->_redis->hGet(REDIS_CHATS_KEY, $frame->fd); | |
| $user_data = json_decode($this->_redis->hGet($room_key, $frame->fd), TRUE); | |
| $name = $user_data['name']; | |
| while ($array_keys = $this->_redis->hScan($room_key, $it)) { | |
| foreach ($array_keys as $key => $value) { | |
| $message = sprintf( | |
| TEMPLATE_SEND, | |
| date("Y-m-d H:i:s"), | |
| $name, | |
| $frame->data | |
| ); | |
| // avoid send own message | |
| if ($key == $frame->fd) { | |
| // log | |
| $this->write_log( | |
| $this->get_log_filename(), | |
| $message | |
| ); | |
| continue; | |
| } | |
| echo 'k ' . $key . ' v ' . $value . PHP_EOL; | |
| $server->push( | |
| $key, | |
| $message | |
| ); | |
| } | |
| } | |
| } | |
| private function build_user_json_data($name, $avatar_url = null) { | |
| $data = array( | |
| 'name' => $name, | |
| 'avatar_url' => $avatar_url, | |
| 'create_time' => date("Y-m-d H:i:s"), | |
| ); | |
| return json_encode($data); | |
| } | |
| private function check_log_path($filename) { | |
| if ( ! file_exists(LOG_PATH)) { | |
| mkdir(LOG_PATH, 0777, TRUE); | |
| } | |
| $path_filename = sprintf( | |
| '%s/%s', | |
| LOG_PATH, | |
| $filename | |
| ); | |
| return $path_filename; | |
| } | |
| private function write_log($filename, $message) { | |
| $path_filename = $this->check_log_path($filename); | |
| file_put_contents( | |
| $path_filename, | |
| $message . PHP_EOL, | |
| FILE_APPEND | |
| ); | |
| } | |
| private function read_last_line($filename, int $line = 5) { | |
| $result = array(); | |
| $path_filename = $this->check_log_path($filename); | |
| if ( ! file_exists($path_filename)) { | |
| return $result; | |
| } | |
| $file = new SplFileObject($path_filename); | |
| $result = array(); | |
| $total_lines = $file->key(); | |
| $start_seek = $total_lines - $line; | |
| if ($start_seek < 0) { | |
| $start_seek = 0; | |
| } | |
| $file->seek($start_seek); | |
| while ( ! $file->eof()) { | |
| $line = trim($file->current()); | |
| $file->next(); | |
| $result[] = $line; | |
| } | |
| return $result; | |
| } | |
| private function get_log_filename($room = 0) { | |
| return $filename = sprintf( | |
| 'chats_%s_%s.log', | |
| $room, | |
| date("Y-m-d") | |
| ); | |
| } | |
| } | |
| /* start */ | |
| $server = new Server(TRUE, 2); | |
| $server->start(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
