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

 这两天用了点时间,研究了一下,用php socket+ websocket实现了一个小型的聊天室。我采用的是 select/poll 的同步模型,虽然扛不住很大的并发,但是理论上 维持 几百人在线还是可以的。
目前完成了第一版。这一版的由于采用的是 select/poll 和单进程,所以在win下面就可以运行。不需要额外的其他扩展支持。
我最近在 看云 发表了 ThinkPHP5+workerman+layIM打造聊天系统 教程,感兴趣的可以去看看。传送门:ThinkPHP5+workerman+layIM打造聊天系统
ichat v3.0 版本正在和 layim 合作中,详情可以参看 http://layim.layui.com。现开通了 ichat 线上预览功能。地址 :ichat v3预览 项目的依赖
  php版本大于 5.4,浏览器支持 websocket和localstorage。
  看一下核心的服务端代码吧:


1

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 }

e749639e677a5cf70aac9e6091569b6e.png


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

golang websocket绑定用户_websocket+php socket实现聊天室_第1张图片

golang websocket绑定用户_websocket+php socket实现聊天室_第2张图片

golang websocket绑定用户_websocket+php socket实现聊天室_第3张图片

golang websocket绑定用户_websocket+php socket实现聊天室_第4张图片


ab并发测试:

golang websocket绑定用户_websocket+php socket实现聊天室_第5张图片


参考文章:http://blog.csdn.net/shagoo/article/details/6396089http://www.cnblogs.com/hustskyking/p/websocket-with-php.htmlhttps://www.web-tinker.com/article/20306.html

作者: NickBai
出处:https://www.cnblogs.com/nickbai/articles/6169745.html
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

你可能感兴趣的:(golang,websocket绑定用户)