golang websocket绑定用户_websocket+php socket实现聊天室

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

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

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

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

项目的依赖

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

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

1 <?php
2 /**
3 * author: NickBai
4 * createTime: 2016/12/9 0009 下午 4:17
5 */
6 namespace NickBai;
7
8 class SocketChat
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 Protocolrn" .
195 "Upgrade: websocketrn" .
196 "Connection: Upgradern" .
197 "Sec-WebSocket-Accept: " . $this->calcKey($key) . "rnrn"; //必须以两个回车结尾
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: (.*)rn/" , $req, $match)) { $h = $match[1]; }
210 if (preg_match("/Origin: (.*)rn/" , $req, $match)) { $o = $match[1]; }
211 if (preg_match("/Sec-WebSocket-Key: (.*)rn/", $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

   看一下页面效果吧:

62986f8b206e60f64ca08f1a36503925.png

309f3fab6d8ee7ab669bbdc44a863782.png

d9262cac3a2ae07973ff6e4548d20f04.png

8b7730ee27780196082b8ff3ab295cad.png

ab并发测试:

3ea70f0394e6f8fe7b502540fcd0c2d1.png

作者: NickBai

出处:https://www.cnblogs.com/nickbai/articles/6169745.html

版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值