2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > websocket+php socket实现聊天室

websocket+php socket实现聊天室

时间:2019-02-26 19:06:53

相关推荐

websocket+php socket实现聊天室

原文地址:/nickbai/articles/6169745.html

这两天用了点时间,研究了一下,用php socket+ websocket实现了一个小型的聊天室。我采用的是select/poll 的同步模型,虽然扛不住很大的并发,但是理论上 维持 几百人在线还是可以的。

目前完成了第一版。这一版的由于采用的是select/poll 和单进程,所以在win下面就可以运行。不需要额外的其他扩展支持。

我最近在 看云 发表了ThinkPHP5+workerman+layIM打造聊天系统 教程,感兴趣的可以去看看。传送门:ThinkPHP5+workerman+layIM打造聊天系统

ichat v3.0 版本正在和 layim 合作中,详情可以参看 。现开通了 ichat 线上预览功能。地址 :ichat v3预览

项目的依赖

php版本大于 5.4,浏览器支持 websocket和localstorage。

看一下核心的服务端代码吧:

1 <?php2 /**3 * author: NickBai4 * createTime: /12/9 0009 下午 4:175 */6 namespace NickBai;7 8 class SocketChat9 {10private $timeout = 60; //超时时间11private $handShake = False; //默认未牵手12private $master = 1; //主进程13private $port = 2000; //监听端口14private static $connectPool = []; //连接池15private static $maxConnectNum = 1024; //最大连接数16private static $chatUser = []; //参与聊天的用户17 18 19public function __construct( $port = 0 )20{21 !empty( $port ) && $this->port = $port;22 $this->startServer();23}24 25//开始服务器26public 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!');51continue;52 } else{53//超过最大连接数54if( 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//未握手 先握手69if( !$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//解析发送的数据87public 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//用户--链接 绑定128public function bind( $socket, $user )129{130 self::$chatUser[(int) $socket] = [131 'user' => $user['user'],132 'avar' => $user['avar']133 ];134}135 136//用户--链接 解绑137public function unBind( $socket )138{139 unset( self::$chatUser[(int) $socket] );140}141 142//获取在线用户143public function getOnlineUser()144{145 return self::$chatUser;146}147 148//更新在线用户149public function freshOnlineUser()150{151 $msgOnlie = [152 'type' => "5",153 'msg' => 'online user',154 'info' => self::$chatUser155 ];156 $this->tellOnlineInfo( $msgOnlie );157}158 159//广播所有的客户端(排除自己和master)160public 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//广播客户端在线用户信息174public 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//握手协议191function 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//获取请求头205function 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//验证socket216function calcKey( $key )217{218 //基于websocket version 13219 $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));220 return $accept;221}222 223 224//打包函数 返回帧处理225public 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//解码 解析数据帧241function 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//客户端链接处理函数265function 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//客户端断开链接函数273function 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//打印运行信息286public function runLog( $mess = '' )287{288 echo $mess . PHP_EOL;289}290 291//系统日志292public 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 }

客户端的代码,篇幅有限,我就不放出了。项目已经放入 本人github,需要了解的请 关注 :/nick-bai/HappyChat

看一下页面效果吧:

ab并发测试:

参考文章:

/shagoo/article/details/6396089

/hustskyking/p/websocket-with-php.html

https://www.web-/article/20306.html

声明:本文内容仅是本人学习的记录,不保证在项目中可用,若引用此代码导致了严重后果,本人不承担任何法律责任。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。