├── .gitignore ├── demo └── laravel │ ├── app │ ├── config │ │ └── websocket.php │ ├── Console │ │ ├── Commands │ │ │ └── ChatWebSocketCommand.php │ │ └── Kernel.php │ └── Server │ │ └── Chat │ │ └── ChatHandle.php │ └── public │ └── js │ └── push.js ├── src ├── Facades │ └── Queue.php ├── RedisSubscribe.php ├── Redis │ ├── RedisManagerCli.php │ └── RedisServiceProvider.php ├── Contracts │ └── Queue.php ├── Commands │ └── WebSocketCommand.php ├── RedisSubscribeCommand.php ├── Service │ └── PushService.php ├── WebSocket.php └── ServerHandle.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | -------------------------------------------------------------------------------- /demo/laravel/app/config/websocket.php: -------------------------------------------------------------------------------- 1 | '0.0.0.0', 4 | 5 | 'port'=>9501, 6 | 7 | 'task_worker_num'=>8 8 | ]; -------------------------------------------------------------------------------- /src/Facades/Queue.php: -------------------------------------------------------------------------------- 1 | =5.5.9", 14 | "laravel/framework": ">=5.4.0" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "webSocket\\": "src/" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/RedisSubscribe.php: -------------------------------------------------------------------------------- 1 | channel = $channel; 23 | $this->fd = $data['fd']; 24 | $this->data = $data['data']; 25 | } 26 | 27 | function reset(){ 28 | $this->fd = null; 29 | $this->data = null; 30 | $this->channel = null; 31 | } 32 | 33 | abstract function handle(); 34 | } -------------------------------------------------------------------------------- /src/Redis/RedisManagerCli.php: -------------------------------------------------------------------------------- 1 | connections[$pid_name])) { 20 | return $this->connections[$pid_name]; 21 | } 22 | 23 | return $this->connections[$pid_name] = $this->resolve($name); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/Redis/RedisServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('redis', function ($app) { 23 | $config = $app->make('config')->get('database.redis'); 24 | 25 | return new RedisManagerCli($app,Arr::pull($config, 'client', 'predis'), $config); 26 | }); 27 | 28 | $this->app->bind('redis.connection', function ($app) { 29 | return $app['redis']->connection(); 30 | }); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /demo/laravel/app/Console/Commands/ChatWebSocketCommand.php: -------------------------------------------------------------------------------- 1 | command('inspire') 33 | // ->hourly(); 34 | } 35 | 36 | /** 37 | * Register the Closure based commands for the application. 38 | * 39 | * @return void 40 | */ 41 | protected function commands() 42 | { 43 | require base_path('routes/console.php'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Contracts/Queue.php: -------------------------------------------------------------------------------- 1 | get($this->getConfig()); 46 | if(!$webSocketConfig){ 47 | throw new \Exception("请设置webSocket配置!"); 48 | } 49 | 50 | $webSocket = new WebSocket($webSocketConfig,$this->laravel); 51 | if(!$bindClass = $this->getBindClass()){ 52 | throw new \Exception("请配置需要运行的webSocket类"); 53 | } 54 | $webSocket->bind(ServerHandle::class, $bindClass); 55 | 56 | $webSocket->handle(); 57 | } 58 | 59 | protected function getConfig(){ 60 | return 'websocket'; 61 | } 62 | 63 | protected function getBindClass(){ 64 | throw new \Exception("请设置 Handle class 名称!"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/RedisSubscribeCommand.php: -------------------------------------------------------------------------------- 1 | channel){ 39 | throw new \Exception("not channel!"); 40 | } 41 | 42 | if(!$this->handler){ 43 | throw new \Exception("not handler!"); 44 | } 45 | } 46 | 47 | /** 48 | * Execute the console command. 49 | * 50 | * @return mixed 51 | */ 52 | public function handle() 53 | { 54 | $this->laravel->bind(RedisSubscribe::class,$this->handler); 55 | $instance = $this->laravel->make(RedisSubscribe::class); 56 | 57 | while(true) { 58 | $value = Redis::brpop($this->channel, 0); 59 | 60 | $channel = $value[0]; 61 | $data = $value[1]; 62 | 63 | $instance->reset(); 64 | $instance->setData($channel,json_decode($data,true)); 65 | $instance->handle(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /demo/laravel/app/Server/Chat/ChatHandle.php: -------------------------------------------------------------------------------- 1 | results("login",1,[ 20 | 'message' => "{$fd} 用户登录成功!" 21 | ])); 22 | } 23 | 24 | /** 25 | * 异步消息请求处理 26 | * @param $fd 27 | * @param $data 28 | */ 29 | public function message_Async($fd,$data){ 30 | 31 | PushService::pushToAllOutMeAsync($fd,$this->results("async",1,[ 32 | 'message' => "Async 消息发送成功" 33 | ])); 34 | 35 | /*PushService::pushToAllAsync($this->results("async1",1,[ 36 | 'message' => "Async 任务发送成功1" 37 | ]));*/ 38 | 39 | } 40 | 41 | /** 42 | * 客户端发送订阅事件 43 | * @param $fd 44 | * @param $data 45 | */ 46 | public function message_publish($fd,$data){ 47 | $data = is_string($data)?$data:json_encode($data); 48 | 49 | $this->publish("packet.rob",[ 50 | 'fd'=>$fd, 51 | 'data'=>$data 52 | ]); 53 | } 54 | 55 | /** 56 | * 对客户端的其它消息发送信息 57 | * @param $fd 58 | * @param $data 59 | * @return array 60 | */ 61 | public function message_chat($fd,$data){ 62 | return $this->results("chat",1,"发送成功"); 63 | } 64 | 65 | 66 | /** 67 | * 用户关闭了连接 68 | * @param $fd 69 | */ 70 | public function close($fd){ 71 | log:info($fd." is close !"); 72 | 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/Service/PushService.php: -------------------------------------------------------------------------------- 1 | $action, 135 | 'status'=>$status, 136 | 'message'=>$message 137 | ]; 138 | } 139 | 140 | /** 141 | * 执行成功消息 142 | * @param $action 143 | * @param array $message 144 | * @return array 145 | */ 146 | static public function success($action,$message = array()){ 147 | return [ 148 | 'action'=>$action, 149 | 'status'=>1, 150 | 'message'=>$message 151 | ]; 152 | } 153 | 154 | /** 155 | * 执行失败消息 156 | * @param $action 157 | * @param array $message 158 | * @return array 159 | */ 160 | static public function error($action,$message = array()){ 161 | return [ 162 | 'action'=>$action, 163 | 'status'=>0, 164 | 'message'=>$message 165 | ]; 166 | } 167 | } -------------------------------------------------------------------------------- /src/WebSocket.php: -------------------------------------------------------------------------------- 1 | app = $app; 23 | 24 | $this->instance(WebSocket::class,$this); 25 | 26 | $this->server = new \swoole_websocket_server($config['host'],$config['port']); 27 | 28 | $this->server->set(array( 29 | 'task_worker_num' => $config['task_worker_num'] 30 | )); 31 | 32 | $this->server->on("open",array($this,"onOpen")); 33 | $this->server->on("message",array($this,"onMessage")); 34 | $this->server->on("Task",array($this,"onTask")); 35 | $this->server->on("Finish",array($this,"onFinish")); 36 | $this->server->on("close",array($this,"onClose")); 37 | } 38 | 39 | function handle(){ 40 | 41 | $this->handler = $this->make(ServerHandle::class); 42 | 43 | $this->server->start(); 44 | } 45 | 46 | /** 47 | * 添加任务 48 | * @param $fd 49 | * @param $key 50 | * @param array $params 51 | */ 52 | public function addTask($fd,$key,$params = []){ 53 | $this->server->task(json_encode([ 54 | 'task'=>$key, 55 | 'fd' =>$fd, 56 | 'data'=>$params 57 | ])); 58 | } 59 | 60 | /** 61 | * 当用户上线 62 | * @param $server 63 | * @param $request 64 | */ 65 | public function onOpen( $server , $request){ 66 | 67 | $this->handler->openBefore($request->fd); 68 | 69 | if($pushMsg = $this->handler->open($request->fd)){ 70 | $this->push( $request->fd ,null, $pushMsg ); 71 | } 72 | 73 | $this->handler->openAfter($request->fd); 74 | 75 | } 76 | 77 | /** 78 | * 但任务发送给服务器 79 | * @param $server 80 | * @param $task_id 81 | * @param $from_id 82 | * @param $data 83 | */ 84 | public function onTask($server , $task_id , $from_id , $data){ 85 | $data = json_decode($data,true); 86 | if($data['task'] == '__message__'){ 87 | $pushMsg = $this->handler->message($data['fd'],$data['data']); 88 | }else{ 89 | $pushMsg = $this->handler->task($data['task'],$data['fd'],$data['data']); 90 | } 91 | 92 | if($pushMsg){ 93 | $this->push( $data['fd'] ,$task_id, $pushMsg ); 94 | } 95 | } 96 | 97 | /** 98 | * 当信息发送过来 99 | * @param $server 100 | * @param $frame 101 | */ 102 | public function onMessage($server , $frame ){ 103 | $data = json_decode( $frame->data , true ); 104 | $this->addTask($frame->fd,'__message__',$data); 105 | } 106 | 107 | /** 108 | * 操作完成 109 | * @param $server 110 | * @param $task_id 111 | * @param $data 112 | * @return mixed 113 | */ 114 | public function onFinish($server , $task_id , $data){ 115 | return $this->handler->finish($task_id,$data); 116 | } 117 | 118 | /** 119 | * 当系统结束 120 | * @param $server 121 | * @param $fd 122 | * @return mixed 123 | */ 124 | public function onClose($server , $fd){ 125 | 126 | $this->handler->closeBefore($fd); 127 | 128 | $ret = $this->handler->close($fd); 129 | 130 | $this->handler->closeAfter($fd); 131 | 132 | return $ret; 133 | } 134 | 135 | /** 136 | * 添加反馈的信息 137 | * @param $fd 138 | * @param null $task_id 139 | * @param $data 140 | */ 141 | public function push($fd,$task_id = null,$data){ 142 | $isLiveClient = false; 143 | foreach($this->server->connections as $i){ 144 | if($fd == $i){ 145 | $isLiveClient = true; 146 | break; 147 | } 148 | } 149 | 150 | if($isLiveClient){ 151 | try{ 152 | $this->server->push( $fd , is_array($data)?json_encode($data):$data ); 153 | }catch (\ErrorException $e){ 154 | Log::warning($fd." push data error!"); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * 为所有用户发布发送消息 161 | * @param $data 162 | */ 163 | public function pushAll($data){ 164 | foreach($this->server->connections as $i){ 165 | $this->server->push( $i , is_array($data)?json_encode($data):$data ); 166 | } 167 | } 168 | 169 | /** 170 | * 为所有用户push信息,除开fd 171 | * @param $fd 172 | * @param $data 173 | */ 174 | public function pushToAllOutMe($fd,$data){ 175 | foreach($this->server->connections as $i){ 176 | if($i != $fd){ 177 | $this->server->push( $i , is_array($data)?json_encode($data):$data ); 178 | } 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /src/ServerHandle.php: -------------------------------------------------------------------------------- 1 | server = $webSocket; 40 | } 41 | 42 | 43 | 44 | /** 45 | * 反馈给client的结果 46 | * @param $action 47 | * @param $status 48 | * @param $message 49 | * @return array 50 | */ 51 | public function results($action,$status,$message = array()){ 52 | return PushService::results($action,$status,$message); 53 | } 54 | 55 | /** 56 | * 添加任务到Task进程 57 | * @param $fd 58 | * @param $key 59 | * @param array $params 60 | */ 61 | public function pushTask($fd, $key, $params = []){ 62 | return $this->server->addTask($fd,$key,$params); 63 | } 64 | 65 | 66 | /** 67 | * 发送消息给某fd 68 | * @param $fd 69 | * @param $data 70 | */ 71 | public function pushToFd($fd,$data){ 72 | $this->server->push($fd,null,$data); 73 | } 74 | 75 | /** 76 | * 发送消息给所有的fd 77 | * @param $data 78 | */ 79 | public function pushToAll($data){ 80 | $this->server->pushAll($data); 81 | } 82 | 83 | /** 84 | * 发送消息给所有的fd,不包括$fd 85 | * @param $fd 86 | * @param $data 87 | */ 88 | public function pushToAllOutMe($fd,$data){ 89 | $this->server->pushToAllOutMe($fd,$data); 90 | } 91 | 92 | /** 93 | * 调试模式下调试发布与订阅机制 94 | * @param $channel 95 | * @param $data 96 | */ 97 | protected function call($channel,$data){ 98 | foreach($this->channels as $c => $class){ 99 | if($c == $channel){ 100 | Log::info("{$c} channel call!"); 101 | 102 | /** 103 | * @var $Object RedisSubscribe 104 | */ 105 | $Object = new $class($data); 106 | $Object->setData($channel,$data); 107 | $Object->handle(); 108 | 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * 发布消息 115 | * @param $channel 116 | * @param string $data 117 | */ 118 | protected function publish($channel,$data = ''){ 119 | if(env('APP_ENV') =='local'){ 120 | $this->call($channel,$data); 121 | }else{ 122 | Queue::push($channel,$data); 123 | } 124 | } 125 | 126 | /** 127 | * message分发 128 | * @param $fd 129 | * @param $data 130 | * @return mixed 131 | */ 132 | public function message($fd,$data){ 133 | if(isset($data['type'])){ 134 | $key = "message_".$data['type']; 135 | if(method_exists($this,$key)){ 136 | return $this->$key($fd,$data); 137 | } 138 | } 139 | 140 | Log::error("message no run ",$data); 141 | } 142 | 143 | /** 144 | * 任务分发 145 | * @param $task 146 | * @param $fd 147 | * @param $data 148 | * @return array 149 | */ 150 | public function task($task,$fd,$data){ 151 | $key = "task_".$task; 152 | if(method_exists($this,$key)){ 153 | return $this->$task($fd,$data); 154 | }else{ 155 | return $this->results(self::SYS_ERROR,0,'无处理程序'); 156 | } 157 | } 158 | 159 | /** 160 | * 数据监听 161 | * @param $fd 162 | */ 163 | public function timer($fd){ 164 | $the = $this; 165 | 166 | \swoole_timer_after($this->interval,function () use ($fd,$the){ 167 | try{ 168 | // Log::info("{$fd}开始消费!"); 169 | 170 | while($value = Queue::lpop(PushService::getFdChannel($fd))){ 171 | $this->pushToFd( $fd, $value ); 172 | } 173 | 174 | if(PushService::isLive($fd)){ 175 | $the->timer($fd); 176 | } 177 | 178 | }catch (\Exception $e){ 179 | 180 | Log::info("timer error:".$e->getMessage()); 181 | 182 | $this->triggerClose($fd); 183 | } 184 | }); 185 | 186 | } 187 | 188 | /** 189 | * 链接打开之前执行的操作 190 | * @param $fd 191 | */ 192 | public function openBefore($fd){ 193 | PushService::cleanFdChannel($fd); 194 | } 195 | 196 | /** 197 | * 打开链接 198 | * @param $fd 199 | */ 200 | public function open($fd){ 201 | 202 | $this->timer($fd); 203 | } 204 | 205 | /** 206 | * 打开链接之后 207 | * @param $fd 208 | */ 209 | public function openAfter($fd){ 210 | Log::info(" {$fd} is open! "); 211 | 212 | PushService::login($fd); 213 | 214 | $length = Redis::get(PushService::Store_fds_length); 215 | Log::info("当前 fds length {$length}"); 216 | } 217 | 218 | /** 219 | * 任务完成 220 | * @param $task_id 221 | * @param $data 222 | */ 223 | public function finish($task_id,$data){ 224 | 225 | } 226 | 227 | /** 228 | * 链接关闭之前 229 | * @param $fd 230 | */ 231 | public function closeBefore($fd){ 232 | 233 | } 234 | 235 | /** 236 | * 链接关闭 237 | * @param $fd 238 | */ 239 | public function close($fd){ 240 | 241 | } 242 | 243 | /** 244 | * 关闭链接之后 245 | * @param $fd 246 | */ 247 | public function closeAfter($fd){ 248 | log:info($fd." is close !"); 249 | 250 | PushService::out($fd); 251 | } 252 | 253 | /** 254 | * 触发关闭事件 255 | * @param $fd 256 | */ 257 | public function triggerClose($fd){ 258 | $this->openBefore($fd); 259 | 260 | $this->close($fd); 261 | 262 | $this->closeAfter($fd); 263 | } 264 | } --------------------------------------------------------------------------------