设为首页 收藏本站
查看: 973|回复: 0

[经验分享] websocket+php socket实现聊天室

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2017-12-29 13:35:47 | 显示全部楼层 |阅读模式
1 <?php  

2 /**  

3  * author: NickBai  

4  * createTime: 2016/12/9 0009 下午 4:17  

5  */  
  6 namespace NickBai;
  
  7

  
  8>  
  9 {
  
10     private $timeout = 60;  //超时时间
  
11     private $handShake = False; //默认未牵手
  
12     private $master = 1;  //主进程
  
13     private $port = 2000;  //监听端口
  
14     private static $connectPool = [];  //连接池
  
15     private static $maxConnectNum = 1024; //最大连接数
  
16     private static $chatUser = [];  //参与聊天的用户
  
17
  
18
  
19     public function __construct( $port = 0 )
  
20     {
  
21         !empty( $port ) && $this->port = $port;
  
22         $this->startServer();
  
23     }
  
24
  
25     //开始服务器
  
26     public function startServer()
  
27     {
  
28         $this->master = socket_create_listen( $this->port );
  
29         if( !$this->master ) throw new \Exception('listen $this->port fail !');
  
30
  
31         $this->runLog("Server Started : ".date('Y-m-d H:i:s'));
  
32         $this->runLog("Listening on   : 127.0.0.1 port " . $this->port);
  
33         $this->runLog("Master socket  : ".$this->master."\n");
  
34
  
35         self::$connectPool[] = $this->master;
  
36
  
37         while( true ){
  
38             $readFds = self::$connectPool;
  
39             //阻塞接收客户端链接
  
40             @socket_select( $readFds, $writeFds, $e = null, $this->timeout );
  
41
  
42             foreach( $readFds as $socket ){
  
43                 //当前链接 是主进程
  
44                 if( $this->master == $socket ){
  
45
  
46                     $client = socket_accept( $this->master );  //接收新的链接
  
47                     $this->handShake = False;
  
48
  
49                     if ($client < 0){
  
50                         $this->log('clinet connect false!');
  
51                         continue;
  
52                     } else{
  
53                         //超过最大连接数
  
54                         if( count( self::$connectPool ) > self::$maxConnectNum )
  
55                             continue;
  
56
  
57                         //加入连接池
  
58                         $this->connect( $client );
  
59                     }
  
60
  
61                 }else{
  
62                     //不是主进程,开始接收数据
  
63                     $bytes = @socket_recv($socket, $buffer, 2048, 0);
  
64                     //未读取到数据
  
65                     if( $bytes == 0 ){
  
66                         $this->disConnect( $socket );
  
67                     }else{
  
68                         //未握手 先握手
  
69                         if( !$this->handShake ){
  
70
  
71                             $this->doHandShake( $socket, $buffer );
  
72                         }else{
  
73
  
74                             //如果是已经握完手的数据,广播其发送的消息
  
75                             $buffer = $this->decode( $buffer );
  
76                             $this->parseMessage( $buffer, $socket );
  
77                         }
  
78                     }
  
79
  
80                 }
  
81             }
  
82
  
83         }
  
84     }
  
85
  
86     //解析发送的数据
  
87     public function parseMessage( $message, $socket )
  
88     {
  
89         //msg type  1 初始化  2 通知  3 一般聊天  4 断开链接  5 获取在线用户 6 通知下线
  
90         $message = json_decode( $message, true );
  
91         switch( $message['type'] ){
  
92
  
93             case 1:
  
94                 $this->bind( $socket, $message );
  
95                 //通知其他客户端,当前用户上线
  
96                 $msg = [
  
97                     'type' => "2",
  
98                     'msg' => 'online',
  
99                     'avar' => $message['avar']
  
100                 ];
  
101                 $this->sendToAll( $socket,  $msg );
  
102                 //更新在线用户
  
103                 $this->freshOnlineUser();
  
104
  
105                 break;
  
106             case 3:
  
107                 $this->sendToAll( $socket, $message );
  
108                 break;
  
109             case 4:
  
110                 //通知用户离线
  
111                 $msgOutline = [
  
112                     'type' => '6',
  
113                     'user' => self::$chatUser[(int)$socket]['user']
  
114                 ];
  
115                 $this->tellOnlineInfo( $msgOutline );
  
116                 //断开 要离线的用户
  
117                 $this->disConnect( $socket );
  
118                 //更新在线用户
  
119                 $this->freshOnlineUser();
  
120
  
121                 break;
  
122             default:
  
123                 break;
  
124         }
  
125     }
  
126
  
127     //用户--链接 绑定
  
128     public function bind( $socket, $user )
  
129     {
  
130         self::$chatUser[(int) $socket] = [
  
131             'user' => $user['user'],
  
132             'avar' => $user['avar']
  
133         ];
  
134     }
  
135
  
136     //用户--链接 解绑
  
137     public function unBind( $socket )
  
138     {
  
139         unset( self::$chatUser[(int) $socket] );
  
140     }
  
141
  
142     //获取在线用户
  
143     public function getOnlineUser()
  
144     {
  
145         return self::$chatUser;
  
146     }
  
147
  
148     //更新在线用户
  
149     public function freshOnlineUser()
  
150     {
  
151         $msgOnlie = [
  
152             'type' => "5",
  
153             'msg' => 'online user',
  
154             'info' => self::$chatUser
  
155         ];
  
156         $this->tellOnlineInfo( $msgOnlie );
  
157     }
  
158
  
159     //广播所有的客户端(排除自己和master)
  
160     public function sendToAll( $client, $mess )
  
161     {
  
162         //拼装发送者的名称
  
163         $mess['user'] = self::$chatUser[(int) $client]['user'];
  
164         $mess['stime'] = date('Y-m-d H:i:s');
  
165
  
166         foreach( self::$connectPool as $socket ){
  
167             if( $socket != $this->master && $socket != $client  ){
  
168                 $this->send( $socket, $mess );
  
169             }
  
170         }
  
171     }
  
172
  
173     //广播客户端在线用户信息
  
174     public function tellOnlineInfo( $mess )
  
175     {
  
176         foreach( self::$connectPool as $socket ){
  
177             if( $socket != $this->master ){
  
178                 $this->send( $socket, $mess );
  
179             }
  
180         }
  
181     }
  
182
  
183     //处理发送信息
  
184    public function send( $client, $msg )
  
185     {
  
186         $msg = $this->frame( json_encode( $msg ) );
  
187         socket_write( $client, $msg, strlen($msg) );
  
188     }
  
189
  
190     //握手协议
  
191     function doHandShake($socket, $buffer)
  
192     {
  
193         list($resource, $host, $origin, $key) = $this->getHeaders($buffer);
  
194         $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
  
195             "Upgrade: websocket\r\n" .
  
196             "Connection: Upgrade\r\n" .
  
197             "Sec-WebSocket-Accept: " . $this->calcKey($key) . "\r\n\r\n";  //必须以两个回车结尾
  
198
  
199         socket_write($socket, $upgrade, strlen($upgrade));
  
200         $this->handShake = true;
  
201         return true;
  
202     }
  
203
  
204     //获取请求头
  
205     function getHeaders( $req )
  
206     {
  
207         $r = $h = $o = $key = null;
  
208         if (preg_match("/GET (.*) HTTP/"              , $req, $match)) { $r = $match[1]; }
  
209         if (preg_match("/Host: (.*)\r\n/"             , $req, $match)) { $h = $match[1]; }
  
210         if (preg_match("/Origin: (.*)\r\n/"           , $req, $match)) { $o = $match[1]; }
  
211         if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) { $key = $match[1]; }
  
212         return [$r, $h, $o, $key];
  
213     }
  
214
  
215     //验证socket
  
216     function calcKey( $key )
  
217     {
  
218         //基于websocket version 13
  
219         $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
  
220         return $accept;
  
221     }
  
222
  
223
  
224     //打包函数 返回帧处理
  
225     public function frame( $buffer )
  
226     {
  
227         $len = strlen($buffer);
  
228         if ($len <= 125) {
  
229
  
230             return "\x81" . chr($len) . $buffer;
  
231         } else if ($len <= 65535) {
  
232
  
233             return "\x81" . chr(126) . pack("n", $len) . $buffer;
  
234         } else {
  
235
  
236             return "\x81" . char(127) . pack("xxxxN", $len) . $buffer;
  
237         }
  
238     }
  
239
  
240     //解码 解析数据帧
  
241     function decode( $buffer )
  
242     {
  
243         $len = $masks = $data = $decoded = null;
  
244         $len = ord($buffer[1]) & 127;
  
245
  
246         if ($len === 126) {
  
247             $masks = substr($buffer, 4, 4);
  
248             $data = substr($buffer, 8);
  
249         }
  
250         else if ($len === 127) {
  
251             $masks = substr($buffer, 10, 4);
  
252             $data = substr($buffer, 14);
  
253         }
  
254         else {
  
255             $masks = substr($buffer, 2, 4);
  
256             $data = substr($buffer, 6);
  
257         }
  
258         for ($index = 0; $index < strlen($data); $index++) {
  
259             $decoded .= $data[$index] ^ $masks[$index % 4];
  
260         }
  
261         return $decoded;
  
262     }
  
263
  
264     //客户端链接处理函数
  
265     function connect( $socket )
  
266     {
  
267         array_push( self::$connectPool, $socket );
  
268         $this->runLog("\n" . $socket . " CONNECTED!");
  
269         $this->runLog(date("Y-n-d H:i:s"));
  
270     }
  
271
  
272     //客户端断开链接函数
  
273     function disConnect( $socket )
  
274     {
  
275         $index = array_search( $socket, self::$connectPool );
  
276         socket_close( $socket );
  
277
  
278         $this->unBind( $socket );
  
279         $this->runLog( $socket . " DISCONNECTED!" );
  
280         if ($index >= 0){
  
281             array_splice( self::$connectPool, $index, 1 );
  
282         }
  
283     }
  
284
  
285     //打印运行信息
  
286     public function runLog( $mess = '' )
  
287     {
  
288         echo $mess . PHP_EOL;
  
289     }
  
290
  
291     //系统日志
  
292     public function log( $mess = '' )
  
293     {
  
294         @file_put_contents( './' . date("Y-m-d") . ".log", date('Y-m-d H:i:s') . "  " . $mess . PHP_EOL, FILE_APPEND );
  
295     }
  
296 }

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-429342-1-1.html 上篇帖子: PHP标准注释 下篇帖子: 2017php经典面试题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表