原文地址:http://www.cnblogs.com/nickbai/articles/6169745.html

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

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

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

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

  项目的依赖

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

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

  1 <?php2 /**3  * author: NickBai4  * createTime: 2016/12/9 0009 下午 4:175  */6 namespace NickBai;7 8 class SocketChat9 {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 }

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

  看一下页面效果吧:

ab并发测试:

参考文章:

http://blog.csdn.net/shagoo/article/details/6396089

http://www.cnblogs.com/hustskyking/p/websocket-with-php.html

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

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

websocket+php socket实现聊天室相关推荐

  1. rudesocket如何使用_[WebSocket入门]手把手搭建WebSocket多人在线聊天室(SpringBoot+WebS...

    前言 本文中搭建了一个简易的多人聊天室,使用了WebSocket的基础特性. 源代码来自老外的一篇好文: 本文内容摘要: 初步理解WebSocket的前后端交互逻辑 手把手使用 SpringBoot ...

  2. 教你从零开始用WebSocket打造一个IM聊天室

    之前我们在 IM即时聊天室(一):WebSocket 和 IM即时聊天室(二): Socket.io + Node.js 两篇文章中介绍了搭建一个IM的所需的技术栈和通信原理.那在这篇文章里我们就来详 ...

  3. SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室

    SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室 一.概述 WebSocket 是一种网络通信协议.RFC6455 定义了它的通信标准. WebSocket 是 HTML5 ...

  4. Nodejs+webSocket搭建多人聊天室

    NodeJs+webSocket搭建多人聊天室 准备的东西: 第一步:安装插件并且完善服务端 第二步 :搭建客户端并与服务端的通信 第三步 :添加CSS样式 第四步:总结 今天花了一个上午的时间去学习 ...

  5. 使用Socket模拟聊天室

    使用Socket模拟聊天室 文章目录 使用Socket模拟聊天室 客户端 客户端读线程 客户端写线程 客户主函数 服务端 服务端线程 服务端主函数 客户端 客户端读线程 public class Ec ...

  6. Socket.IO聊天室~简单实用

    小编心语:大家过完圣诞准备迎元旦吧~小编在这里预祝大家元旦快乐!!这一次要分享的东西小编也不是很懂啊,总之小编把它拿出来是觉地比较稀奇,而且程序也没有那么难,是一个比较简单的程序,大家可以多多试试~ ...

  7. 使用Node+websocket实现简易1v1聊天室(前端+服务器)

    使用Node+websocket实现简易1v1聊天室(前端+服务器) 前提: 安装好node环境~~~ 可使用 node -v 和 npm -v 查看是否装好 实现逻辑: 用户A 用户B 服务器 用户 ...

  8. Android 基于Socket的聊天室

    原文地址为: Android 基于Socket的聊天室 Socket是TCP/IP协议上的一种通信,在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路.一旦建立了虚拟的网络链路, ...

  9. 【java毕业设计】基于java+原生Sevlet+socket的聊天室系统设计与实现(毕业论文+程序源码)——聊天室系统

    基于java+原生Sevlet+socket的聊天室系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+原生Sevlet+socket的聊天室系统设计与实现,文章末尾附有本毕业设 ...

最新文章

  1. linux gcc 宏定义 __GNUC__ __GNUC_MINOR__ 版本区分
  2. KinectFusion——微软基于KINECT的实时三维重建项目(KINECT)
  3. golang字符串处理
  4. python五十一:动态导入模块,通过字符串导入模块
  5. UA OPTI501 电磁波 Lorentz Oscillator Model 1 Drude-Lorentz模型
  6. Leetcode 剑指 Offer 03. 数组中重复的数字 (每日一题 20210614)
  7. 【直播回放】150分钟详解模型压缩理论和实践
  8. (非)对称加密算法在https中的应用(加密过程以及CA颁发、验证)
  9. Mysql 电商常用的时间操作(当天,昨天,7天,30天,半年,全年,季度等等)...
  10. Python argv小结
  11. 风口来了第二期——电子科学与技术专业现状和前景介绍分享
  12. 大疆DJI2019届秋季招聘笔试:机器学习算法工程师
  13. AIDL 方法参数的in out inout前缀作用
  14. 吉林大学计算机学院杜天宇,2016年全国研究生数学建模竞赛获奖名单
  15. 【CCF】小中大(C++)
  16. 传世之文《Teach Yourself Programming in Ten Years》十年学会编程
  17. Oracle 18c新特性-Memoptimized Rowstore(内存优化的行存储)
  18. java 函数(方法)
  19. Java实验报告之java面向对象编程
  20. duck java_Duck类型和(java)接口概念

热门文章

  1. Oracle 数据库的备份与恢复
  2. laysns模板系统仿善恶模版 完美自适应
  3. Jupyter 快捷键 (2)
  4. 微信小程序长按复制文本内容
  5. oracle 的ADG,oracle ADG启动顺序
  6. 蓝桥杯-阶乘运算和高精度加法(Java)
  7. 基于STM32单片机和Android的便携式数字示波器设计
  8. python实验指导书燕山大学答案_Python实验指导书课件.doc
  9. Fedora 阿里云源
  10. linux下载安装一个软件,怎么创建快捷方式