五分钟教你写超简单的swoole聊天室

26

原本我是准备接着写我那个多进程教程的,今天心血来潮想看看swoole的websocket,
我就点开了这个
WebSocket
我看了看官网的demo,觉得看起来很简单嘛,

<?php
//官网demo
$server = new swoole_websocket_server("0.0.0.0", 9501);

$server->on('open', function (swoole_websocket_server $server, $request) {
    echo "server: handshake success with fd{$request->fd}\n";//$request->fd 是客户端id
});

$server->on('message', function (swoole_websocket_server $server, $frame) {
    echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($frame->fd, "this is server");//$frame->fd 是客户端id,$frame->data是客户端发送的数据
    //服务端向客户端发送数据是用 $server->push( '客户端id' ,  '内容')
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

我就是喜欢这种简单易懂的demo ,每行代码意思一看就明白

服务端有了,我找点客户端的js代码
火狐的MDN

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <meta charset="UTF-8">
  <script type="text/javascript">
  var exampleSocket = new WebSocket("ws://0.0.0.0:9501");
  exampleSocket.onopen = function (event) {
    exampleSocket.send("亲爱的服务器!我连上你啦!"); 
  };
  exampleSocket.onmessage = function (event) {
    console.log(event.data);
  }
  </script>
</head>
<body>
<input  type="text" id="content">
<button  onclick="exampleSocket.send( document.getElementById('content').value )">发送</button>
</body>
</html>

最后命令行运行php文件,之后浏览器打开html文件,
F12打开调试界面看console,ok , 没有问题

这个时候我突然想到一个事情,因为我做多进程的那个教程里,在主进程中会将所有的子进程的句柄存起来,以后进行进程间通讯用。
那么 我将所有的客户端的链接存起来存成数组,每当一个客户端发送消息时,我就遍历这个客户端数组,将消息群发一遍,不久实现了聊天室了吗?
然后就,服务端代码成了这个样子

<?php
$map = array();//客户端集合
$server = new swoole_websocket_server("0.0.0.0", 9501);

$server->on('open', function (swoole_websocket_server $server, $request) {
    global $map;//客户端集合
    $map[$request->fd] = $request->fd;//首次连上时存起来
});

$server->on('message', function (swoole_websocket_server $server, $frame) {
    global $map;//客户端集合
    $data = $frame->data;
    foreach($map as $fd){
        $server->push($fd , $data);//循环广播
    }
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

哈哈 , 我觉得这样就大功告成了,结果发现自己是 图样图森破
大家可以自己试试,运行php后 , 浏览器打开两个页面,看看console.log的内容是什么

运行良好,可是并没有实现我们说的那种聊天效果。
找找原因吧。
我第一反映看看$map里面是什么,就输出看看,结果发现这个map里面只有一个元素。
唉,不对啊,我这是全局变量,难道不应该是有几个客户端链接,就有几个元素吗?
这是怎么回事啊,竟然没有保存到所有客户端id?

到了这一步,我解决不了map变量的这个问题了,然后我就想看看那个fd是什么东西,
老规矩 var_dump输出 , 发现fd就是 int类型的数字,并且是自增的
这好办了,不就是数字嘛

于是呼,我就这样做
变量存不了,我搞不定,我存文本里嘛。
最终版 websocket.php

<?php

$server = new swoole_websocket_server("0.0.0.0", 9501);

$server->on('open', function (swoole_websocket_server $server, $request) {
    file_put_contents( __DIR__ .'/log.txt' , $request->fd);
});

$server->on('message', function (swoole_websocket_server $server, $frame) {
    global $client;
    $data = $frame->data;
    $m = file_get_contents( __DIR__ .'/log.txt');
    for ($i=1 ; $i<= $m ; $i++) {
        echo PHP_EOL . '  i is  ' . $i .  '  data  is '.$data  . '  m = ' . $m;
        $server->push($i, $data );
    }

});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

再次打开html文件,多个页面进行输入观察,ok,可以了。

当然,作为聊天室,我这写的也过于简陋了,界面大家自己可以写的好看一些(因为我懒的写界面)
还有,每次的发送聊天的记录,应该存起来,这样,如果有新的连接连过来的时候,先把以前的聊天记录发过去,这样,我想体验更好一些

然后,大家可以愉快的聊天了。哈哈

你可能感兴趣的

23 条评论
zgr0629 · 2016-02-23

server里面有connections存了客户端句柄,没有必要自己存

// 广播
foreach($server->connections as $fd) {
    $server->push($fd, $data);
}

+4 回复

0

大神请问,用你这种方式和存文件的方式哪种性能好,。

Rateltalk · 2017-03-10
0

@鬼魅迷屋 这个好啊 你那个存文件 别人下线了 也会遍历发送

马克图布 · 2017-04-11
0

@鬼魅迷屋 肯定是这种啊,不存在磁盘io。

zgr0629 · 2017-10-13
王子 · 2017-01-18

你好!我是一个开发者,名字叫simon,我看了您发布的利用swoole编写一个简单的聊天程序的文章,想请教一下,如何可以分房间聊天?静候您的回复,谢谢!

+2 回复

0

简单实现的话,可以在客户端刚连接到swoole的websocket服务器的时候发一条请求加入房间消息,就在message里处理。这里可以做一下简单的验证,验证通过就加进来。至于房间分组信息什么的可以单独存放在内存、文件、数据库或者Redis这种内存数据库里。

kangmingxuan · 2018-05-08
dolphin · 2017-05-18

我在试验的时候,用全局变量是可以实现聊天室的效果的、、、

+1 回复

eechen · 2017-06-12

@传々奇[fengchi]
Swoole使用了多进程,而不同PHP进程的全局变量都是隔离的,这点需要注意.
在代码上下文中执行var_dump(get_object_vars($server));就可以看到:
$server这个对象包含有一个名为connections的swoole_connection_iterator,
这个connections就记录有所有的客户端连接的$fd,所以要实现广播向所有客户端推送信息,只需:
foreach($server->connections as $fd) { $server->push($fd, '需要推送的消息文本'); }

另外,通过var_export(get_class_methods('swoole_websocket_server'));可以看到swoole这个类还提供了一个名叫connection_list的方法,这个方法的使用和示例代码可以在Swoole官方文档中搜索找到:
https://wiki.swoole.com/wiki/...
connection_list方法用来遍历当前Server所有的客户端连接,connection_list方法是基于共享内存的,不存在IOWait,遍历的速度很快。另外connection_list会返回所有TCP连接,而不仅仅是当前worker进程的TCP连接。

+1 回复

IM鑫爷 · 2015-08-07

可以用fooking啊,全局发消息只需要调用一个sendAll,不需要保存fd.
websocket聊天室:http://my.oschina.net/scgywx/blog/394925

回复

夏诺风 · 2015-08-13

如何验证用户是否登录呢?

回复

Asongni · 2015-10-27

也是遇到 map不能存储所有的客户端列表的问题。 不知道为什么会这样。 保存到文本中,这性能有很大问题哦。

回复

杨佰 · 2015-12-29

fooking是什么哇

回复

杨佰 · 2015-12-29

swoole有没有windows的版本呢!

回复

在我面前跪下 · 2016-02-02

<?php

class user{

public $user =[];

private static $instance;

/**
 * @return user
 */
public static function getInstance()
{
    if(!isset(self::$instance))
    {
        self::$instance = new self();
    }
    return self::$instance;
}

public function getAll(){
    return $this->user;
}

public function set($key,$value){
    $this->user[$key] = $value;
}

public function del($key){
    unset($this->user[$key]);
}

}

用这类来存放临时用户~~
user::getInstance()->set($frame->fd,"opcode:{$frame->opcode},fin:{$frame->finish}");
user::getInstance()->del($fd);
print_r(user::getInstance()->getAll());

回复

0

这方法不行啊,大神,还有其他方法吗?

孤独探戈 · 2018-05-30
sunwenzheng 作者 · 2016-02-26

学习了

回复

big_cat · 2016-03-17

我和你一样图样图深破,开始的广播思路都是一样的.....

回复

Nickrism · 2017-03-11

client.php:7 WebSocket connection to 'ws://120.24.244.77:9501/' failed: Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT

回复

不存在 · 2017-05-16

我在linux服务器搭建好了,在linux可以实现 但是到win10访问连接不上怎么处理?

回复

PP_Test · 2017-11-20
  1. 麻烦问下 如果退出页面 再次进入会怎么样 服务端怎么识别你是刚才哪一个

回复

corxit · 2018-02-20

看了楼上各位的回答, 如果只是聊天室, 应该用Swoole内置的群发功能, 是最方便的方案

不过我找到一个其它方案, 可以解决全局变量无法存储的问题:
就是把变量保存为Server对象的属性, 像这样:

$server = new swoole_websocket_server("0.0.0.0", 9501);
$server->UserIDs = array(); //创建一个属性UserIDs, 并指定它为数组

//然后在需要的时候, 保存或取用就可以了
$server->UserIDs[] = $frame->fd;

//利用这个方法, 我把 $frame->fd 和昵称 保存起来, 实现了用户名
//不知道Swoole是否内置用户名的功能, 期待小伙伴告知!

回复

考拉_kora · 2018-02-28

为什么不能使用server的ip不能设置成127.0.0.1呢?

回复

学会 · 2018-08-01

请问opcode:{$frame->opcode},fin:{$frame->finish}是什么意思

回复

载入中...