整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

使用php+swoole+redis 简单实现网页即

使用php+swoole+redis 简单实现网页即时聊天

用php+swoole+redis 简单实现网页即时聊天,需要浏览器支持html5的websocket,

websocket是不同于http的另外一种网络通信协议,能够进行双向通信,基于此,可开发出各种实时通信产品,简单做了个聊天demo,顺便分享一下

效果图如下:

环境:

  • 系统 centos7.5
  • php7.2.9
  • redis5.0.0
  • swoole4.2.2
  • nginx 1.8

参考文档:

  • redis官网 https://redis.io
  • 教程 http://www.runoob.com/redis/
  • swoole 官网 https://swoole.com
  • swoole 的webSocket手册:https://wiki.swoole.com/wiki/page/397.html
  • php扩展库地址 http://pecl.php.net/

IP与端口:

  • 虚拟机的IP: 192.168.1.100
  • webSocket服务端口是 9520
  • redis服务端口是 6379

服务器端代码 websocket.php

<?php
class Server
{
private $serv;
private $conn=null;
private static $fd=null;
public function __construct()
{
$this->redis_connect();
$this->serv=new swoole_websocket_server("0.0.0.0", 9502);
$this->serv->set(array(
'worker_num'=> 8,
'daemonize'=> false,
'max_request'=> 10000,
'dispatch_mode'=> 2,
'debug_mode'=> 1
));
echo "start \n";
$this->serv->on('Open', array($this, 'onOpen'));
$this->serv->on('Message', array($this, 'onMessage'));
$this->serv->on('Close', array($this, 'onClose'));
$this->serv->start();
}
function onOpen($server, $req)
{
echo "connection open: {$req->fd} \n";
// $server->push($req->fd, json_encode(33));
}
public function onMessage($server, $frame)
{
//echo "received data $frame->data \n";
//$server->push($frame->fd, json_encode(["hello", "world"]));
$pData=json_decode($frame->data,true);
$fd=$frame->fd;
if(empty($pData)){
echo "received data null \n";
return;
}
echo "received fd=>{$fd} message: {$frame->data}\n";
$data=[];
if (isset($pData['content'])) {
$f_fd=$this->getFd($pData['fid']); //获取绑定的fd
$data=$this->add($pData['uid'], $pData['fid'], $pData['content']); //保存消息
$server->push($f_fd, json_encode($data)); //推送到接收者
$json_data=json_encode($data);
echo "推送到接收者 fd=>{$f_fd} message: {$json_data}\n";
} else {
$this->unBind($pData['uid']); //首次接入,清除绑定数据
if ($this->bind($pData['uid'], $fd)) { //绑定fd
$data=$this->loadHistory($pData['uid'], $pData['fid']); //加载历史记录
} else {
$data=array("content"=> "无法绑定fd");
}
}
$json_data=json_encode($data);
echo "推送到发送者 fd=>{$fd} message: {$json_data}\n";
$server->push($fd, json_encode($data)); //推送到发送者
}
public function onClose($server, $fd)
{
//$this->unBind($fd);
echo "connection close: {$fd}\n";
}
/*******************/
/**
* redis
* @param string $host
* @param string $port
* @return bool
*/
function redis_connect($host='127.0.0.1',$port='6379')
{
$this->conn=new Redis();
try{
$this->conn->connect($host, $port);
}catch (\Exception $e){
user_error(print_r($e));
}
return true;
}
/**
* 保存消息
* @param $uid 发送者uid
* @param $fid 接收者uid
* @param $content 内容
* @return array
*/
public function add($uid, $fid, $content)
{
$msg_data=[];
$msg_data['uid']=$uid;
$msg_data['fid']=$fid;
$msg_data['content']=$content;
$msg_data['time']=time();
$key=K::KEY_MSG;
$data=$this->conn->get($key);
if(!empty($data)){
$data=json_decode($data,true);
}else{
$data=[];
}
$data[]=$msg_data;
$this->conn->set($key,json_encode($data));
$return_msg[]=$msg_data;
return $return_msg;
}
/**
* 绑定FD
* @param $uid
* @param $fd
* @return bool
*/
public function bind($uid, $fd)
{
$key=K::KEY_UID."{$uid}";
$ret=$this->conn->set($key,$fd);
if(!$ret){
echo "bind fail \n";
return false;
}
return true;
}
/**
* 获取FD
* @param $uid
* @return mixed
*/
public function getFd($uid)
{
$key=K::KEY_UID."{$uid}";
$fd=$this->conn->get($key);
return $fd;
}
/**
* 清除绑定
* @param $uid
* @return bool
*/
public function unBind($uid)
{
$key=K::KEY_UID."{$uid}";
$ret=$this->conn->delete($key);
if(!$ret){
return false;
}
return true;
}
/**
* 历史记录
* @param $uid
* @param $fid
* @param null $id
* @return array
*/
public function loadHistory($uid, $fid)
{
$msg_data=[];
$key=K::KEY_MSG;
$this->conn->delete($key);
$data=$this->conn->get($key);
if($data){
echo $data;
$json_data=json_decode($data,true);
foreach ($json_data as $k=>$info){
if(($info['uid']==$uid&&$info['fid']==$fid)||($info['uid']==$fid&&$info['fid']==$uid)){
$msg_data[]=$info;
}
}
}
return $msg_data;
}
}
//Key 定义
class K{
const KEY_MSG='msg_data';
const KEY_FD='fd_data';
const KEY_UID='uid';
}
//启动服务器
$server=new Server();

客户端代码 chat.html

<!DOCTYPE html>
<html lang="en">
<html>
<head>
 <title>CHAT A</title>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <script src="jquery.min.js"></script>
 <script src="jquery.json.min.js"></script>
 <style type="text/css">
 .talk_con{
 width:600px;
 height:500px;
 border:1px solid #666;
 margin:50px auto 0;
 background:#f9f9f9;
 }
 .talk_show{
 width:580px;
 height:420px;
 border:1px solid #666;
 background:#fff;
 margin:10px auto 0;
 overflow:auto;
 }
 .talk_input{
 width:580px;
 margin:10px auto 0;
 }
 .whotalk{
 width:80px;
 height:30px;
 float:left;
 outline:none;
 }
 .talk_word{
 width:420px;
 height:26px;
 padding:0px;
 float:left;
 margin-left:10px;
 outline:none;
 text-indent:10px;
 }
 .talk_sub{
 width:56px;
 height:30px;
 float:left;
 margin-left:10px;
 }
 .close{
 width:56px;
 height:30px;
 float:left;
 margin-left:10px;
 }
 .atalk{
 margin:10px;
 }
 .atalk span{
 display:inline-block;
 background:#0181cc;
 border-radius:10px;
 color:#fff;
 padding:5px 10px;
 }
 .btalk{
 margin:10px;
 text-align:right;
 }
 .btalk span{
 display:inline-block;
 background:#ef8201;
 border-radius:10px;
 color:#fff;
 padding:5px 10px;
 }
 </style>
 <script type="text/javascript">
 var uid='A'; //发送者uid
 var fid='B'; //接收者uid
 var wsUrl='ws://192.168.1.100:9502';
 var webSocket=new WebSocket(wsUrl);
 //创建Socket
 webSocket.onopen=function (event) {
 console.log('onOpen=' + event.data);
 //webSocket.send("hello webSocket");
 initData(); //初始化数据,加载历史记录
 };
 //接收数据事件
 webSocket.onmessage=function (event) {
 console.log('onMessage=' + event.data);
 loadData($.parseJSON(event.data)); //导入消息记录,加载新的消息
 }
 //关闭socket
 webSocket.onclose=function (event) {
 console.log('close');
 };
 //socket连接错误
 webSocket.onerror=function (event) {
 console.log('error-data:' + event.data);
 }
 //========================================================//向服务器发送数据
 function sendMsg() {
 var pData={
 content: document.getElementById('content').value,
 uid: uid,
 fid: fid,
 }
 if (pData.content=='') {
 alert("消息不能为空");
 return;
 }
 webSocket.send($.toJSON(pData)); //发送消息
 }
 function initData() {
 //var Who=document.getElementById("who").value;
 console.log('initData uid:' + uid + ' fid:'+fid);
 var pData={
 uid: uid,
 fid: fid,
 }
 webSocket.send($.toJSON(pData)); //获取消息记录,绑定fd
 var html='<div class="atalk"><span id="asay">' + 'WebSocket连接成功' + '</div>';
 $("#words").append(html);
 }
 function loadData(data) {
 for (var i=0; i < data.length; i++) {
 if(data[i].uid=='A'){
 var html='<div class="atalk"><span id="asay">' + data[i].uid + '说: ' + data[i].content + '</div>';
 }else{
 var html='<div class="btalk"><span id="asay">' + data[i].uid + '说: ' + data[i].content + '</div>';
 }
 $("#words").append(html);
 }
 }
 //关闭连接
 function closeWebSocket() {
 console.log('close');
 webSocket.close();
 var html='<div class="atalk"><span id="asay">' + '已和服务器断开连接' + '</div>';
 $("#words").append(html);
 }
 </script>
</head>
<body>
<div class="talk_con">
 <div class="talk_show" id="words">
 <!--<div class="atalk"><span id="asay">A说:吃饭了吗?</span></div>-->
 <!--<div class="btalk"><span id="bsay">B说:还没呢,你呢?</span></div>-->
 </div>
 <div class="talk_input">
 <!--<select class="whotalk" id="who">-->
 <!--<option value="A" selected="selected">A说:</option>-->
 <!--<option value="B">B说:</option>-->
 <!--</select>-->
		<button class="close" onclick="closeWebSocket()">断开</button>
 <input type="text" class="talk_word" id="content">
 <input type="button" onclick="sendMsg()" value="发送" class="talk_sub" id="talksub"> 
 </div>
</div>
</body>
</html>

文件详情

  • 再复制一份客户端,修改一下发送者与接收者的uid,即可进行模拟实时聊天。
  • 此代码已经实现了加载历史记录的功能

使用方法:

安装完php、redis和swoole扩展之后,直接执行:

并可以观察下输出,看看websocket服务器是否正常

么是Workerman?

Workerman是一款 开源 高性能异步 PHP socket即时通讯框架 。支持高并发,超高稳定性,被广泛的用于手机app、移动通讯,微信小程序,手游服务端、网络游戏、PHP聊天室、硬件通讯、智能家居、车联网、物联网等领域的开发。 支持TCP长连接,支持Websocket、HTTP等协议,支持自定义协议。拥有异步Mysql、异步Redis、异步Http、MQTT物联网客户端、异步消息队列等众多高性能组件。


搭建步骤

1.第一步我们先把workerman里需要用到的扩展composer下来吧

"workerman/gateway-worker": "^3.0",
"workerman/gatewayclient": "^3.0",
"workerman/workerman": "^3.5",


2.第二步到官方网站把demo全部下载下来,然后放到我们项目中的目录

图片中我就把整个项目都放在了HTTP/Controller/Workerman中。


3.第三步我们需要把把以下3个文件的引用部分修改为以下。不然会报路径错误

start_businessworker,start_gateway,start_register

require_once __DIR__ . '/../../../../../vendor/autoload.php';


4.修改完成后我们就可以在liunx直接运行对应的启动文件

php start.php start -d

如果你是在window下就双击start_for_win.bat运行


5.运行成功后,你就应该可以看到以下的界面

到此我们搭建基于workerman的通信环境就已经完成。接下来我们就可以根据自己的项目需求进行开发。在此向大家重点说明。我们所有的聊天是逻辑都在目录中的Events.php进行修改。


下面我给大家贴一下我编写的部分份代码。

Event.php

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */

/**
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */
//declare(ticks=1);

/**
 * 聊天主逻辑
 * 主要是处理 onMessage onClose
 */
use \GatewayWorker\Lib\Gateway;

class Events{
  /**
   * 作者:何志伟
   * 当客户端连接上来的时候
   * 创建时间:2018/10/25
   * @param $client_id 此ID为gatewayworker 自动生成ID
   */
  public static function onConnect($client_id)
  {
    Gateway::sendToClient($client_id, json_encode(array(
      'type'   => 'init',
      'client_id' => $client_id
    )));
  }


  /**
   * 有消息时
   * @param int $client_id
   * @param mixed $message
   */
  public static function onMessage($client_id, $message)
  {
    // debug
    echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id session:".json_encode($_SESSION)." onMessage:".$message."\n";

    // 客户端传递的是json数据
    $message_data = json_decode($message, true);
    if(!$message_data)
    {
      return ;
    }

    // 根据类型执行不同的业务
    switch($message_data['type'])
    {
      // 客户端回应服务端的心跳
      case 'pong':
        return;
      // 客户端登录 message格式: {type:login, name:xx, room_id:1} ,添加到客户端,广播给所有客户端xx进入聊天室
      case 'login':
        // 判断是否有房间号
        if(!isset($message_data['room_id']))
        {
          throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
        }

        // 把房间号昵称放到session中
        $room_id = $message_data['room_id'];
        $client_name = htmlspecialchars($message_data['client_name']);
        $_SESSION['room_id'] = $room_id;
        $_SESSION['client_name'] = $client_name;


        // 获取房间内所有用户列表
        $clients_list = Gateway::getClientSessionsByGroup($room_id);
        foreach($clients_list as $tmp_client_id=>$item)
        {
          $clients_list[$tmp_client_id] = $item['client_name'];
        }
//        $clients_list[$client_id] = $client_name;

        // 转播给当前房间的所有客户端,xx进入聊天室 message {type:login, client_id:xx, name:xx}
        $new_message = array('type'=>$message_data['type'], 'client_id'=>$client_id, 'client_name'=>htmlspecialchars($client_name), 'time'=>date('Y-m-d H:i:s'),'to'=>$message_data['to'],'room_id'=>$message_data['room_id'],
          'from'=>$message_data['from'],'tag'=>$message_data['tag']);
        Gateway::sendToGroup($room_id, json_encode($new_message));
        Gateway::joinGroup($client_id, $room_id);

        // 给当前用户发送用户列表
        $new_message['client_list'] = $clients_list;
        Gateway::sendToCurrentClient(json_encode($new_message));
        return;

      // 客户端发言 message: {type:say, to_client_id:xx, content:xx}
      case 'say':
        // 非法请求
        if(!isset($_SESSION['room_id']))
        {
          throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}");
        }
        $room_id = $_SESSION['room_id'];
        $client_name = $_SESSION['client_name'];

        // 私聊
//        if($message_data['to_client_id'] != 'all')
//        {
//          $new_message = array(
//            'type'=>'say',
//            'from_client_id'=>$client_id,
//            'from_client_name' =>$client_name,
//            'to_client_id'=>$message_data['to_client_id'],
//            'content'=>"<b>对你说: </b>".nl2br(htmlspecialchars($message_data['content'])),
//            'time'=>date('Y-m-d H:i:s'),
//          );
//          Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message));
//          $new_message['content'] = "<b>你对".htmlspecialchars($message_data['to_client_name'])."说: </b>".nl2br(htmlspecialchars($message_data['content']));
//          return Gateway::sendToCurrentClient(json_encode($new_message));
//        }

        $new_message = array(
          'type'=>'say',
          'from_client_id'=>$client_id,
          'from_client_name' =>$client_name,
          'to_client_id'=>'all',
          'content'=>nl2br(htmlspecialchars($message_data['content'])),
          'time'=>date('Y-m-d H:i:s'),

        );
        return Gateway::sendToGroup($room_id ,json_encode($new_message));
    }
  }
  /**
   * 当客户端断开连接时
   * @param integer $client_id 客户端id
   */
  public static function onClose($client_id)
  {
    // debug
    echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n";

    // 从房间的客户端列表中删除
    if(isset($_SESSION['room_id']))
    {
      $room_id = $_SESSION['room_id'];
      $new_message = array('type'=>'logout', 'from_client_id'=>$client_id, 'from_client_name'=>$_SESSION['client_name'], 'time'=>date('Y-m-d H:i:s'));
      Gateway::sendToGroup($room_id, json_encode($new_message));
    }
  }

}


客户端页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>与{{$to->name}}的对话</title>
  <script type="text/javascript" src="{{asset('js')}}/swfobject.js"></script>
  <script type="text/javascript" src="{{asset('js')}}/web_socket.js"></script>
  <script type="text/javascript" src="{{asset('js')}}/jquery.min.js"></script>
  <link href="{{asset('css')}}/jquery-sinaEmotion-2.1.0.min.css" rel="external nofollow" rel="stylesheet">
  <link href="{{asset('css')}}/bootstrap.min.css" rel="external nofollow" rel="stylesheet">
  <link href="{{asset('css')}}/style.css" rel="external nofollow" rel="stylesheet">
  <script type="text/javascript" src="{{asset('js')}}/jquery-sinaEmotion-2.1.0.min.js"></script>

  {{--<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>--}}

</head>
<style>  #sinaEmotion {
    z-index: 999;
    width: 373px;
    padding: 10px;
    display: none;
    font-size: 12px;
    background: #fff;
    overflow: hidden;
    position: absolute;
    border: 1px solid #e8e8e8;
    top: 100px;
    left: 542.5px;
  }</style>
<body onload="connect();" style="margin: auto; text-align: center;">
<div style="margin: auto;">
  <div style="border: 1px solid red; height: 40px; width: 500px; margin: auto;">
    {{--对话窗口头部--}}
    <div>
      <div style="width: 80px; height: 40px; border: 1px solid blue; float: left">
        <img src="{{$to->heading}}" width="80px" height="40px">
      </div>
      <div style="width: 150px; height: 40px; border: 1px solid blue; float: left">
        {{$to->name}}
      </div>
    </div>
    {{--//对话窗口内容--}}
    <div class="content" style="width: 500px; height: 400px; border: 1px solid green; margin-top: 40px; overflow-y: auto">
      {{--对方的头像与文字--}}
      {{--<div style="min-height: 50px;margin-top: 10px;">--}}
        {{--<div style="width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left">--}}
          {{--<img src="{{$to->heading}}" width="50px" height="50px">--}}
        {{--</div>--}}
        {{--<div style="border: 1px solid red; float: left; min-height: 50px" >dsadsadsadsadsa</div>--}}
      {{--</div>--}}
      {{--我的头像与文字--}}
      {{--<div style= "min-height:50px;margin-top: 10px;">--}}
        {{--<div style="width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right">--}}
          {{--<img src="{{$from->heading}}" width="50px" height="50px">--}}
        {{--</div>--}}
        {{--<div style="border: 1px solid red; float: right; min-height: 50px" >dsadsadsadsadsa</div>--}}
      {{--</div>--}}
    </div>
    {{--对话发送窗口--}}
    <form onsubmit="return onSubmit(); return false;" id="ajaxfrom">
      <input type="hidden" name="to" value="{{$to->id}}">
      <input type="hidden" name="from" value="{{$from->id}}">
      <input type="hidden" name="room_id" value="{{$room}}">
      <input type="hidden" name="tag" value="{{$tag}}">
      <textarea id="textarea" name="content" class="Input_text" style="margin: 0px; width: 501px; height: 213px;"></textarea>
      <div class="say-btn">
        <input type="button" class="btn btn-default face pull-left" value="表情" />
        <button type="submit" class="btn btn-default">发表</button>
      </div>
    </form>
    房间号{{$room}}
  </div>
</div>

</body>
</html>
<script type="text/javascript">  if (typeof console == "undefined") {  this.console = { log: function (msg) { } };}
  // 如果浏览器不支持websocket,会使用这个flash自动模拟websocket协议,此过程对开发者透明
  WEB_SOCKET_SWF_LOCATION = "/swf/WebSocketMain.swf";
  // 开启flash的websocket debug
  WEB_SOCKET_DEBUG = true;
  var ws, name, client_list={};
  var to_client_id="";

  // 连接服务端初始化函数
  function connect() {
    // 创建websocket 届时可以替换为对应的服务器地址
    ws = new WebSocket("ws://"+document.domain+":7272");
    // 当socket连接打开时,输入用户名
    ws.onopen = onopen;
    // 当有消息时根据消息类型显示不同信息
    ws.onmessage = onmessage;
    //当连接丢失时,调用连接方法尝试重新连接
    ws.onclose = function() {
      console.log("连接关闭,定时重连");
      connect();
    };
    //当操作报错时,返回异常错误
    ws.onerror = function() {
      console.log("出现错误");
    };

    //发送ajax获取当前房间的通话记录
    $.post("/get_record", { "room":"{{$room}}" },
      function(msg){
        $.each(msg,function (v,k) {
          console.log(k);
          //判断
          if(k.tag!="{{$tag}}"){
            $(".content").append(
              '<div style="min-height: 50px;margin-top: 10px;">' +
              '<div style="width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left">'+
              '<img src="{{$to->heading}}" width="50px" height="50px">'+
              '</div>'+
              '<div style="border: 1px solid red; float: left; min-height: 50px" >'+k.content+'</div>'+
              '<div>'
            ).parseEmotion();
          }else{
            $(".content").append(
              '<div style="min-height: 50px;margin-top: 10px;">' +
              '<div style="width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right">'+
              '<img src="{{$from->heading}}" width="50px" height="50px">'+
              '</div>'+
              '<div style="border: 1px solid red; float: right; min-height: 50px" >'+k.content+'</div>'+
              '<div>'
            ).parseEmotion();
          }
        })
      });
  }

  // 连接建立时发送登录信息
  function onopen()
  {
    var login_data='{"type":"login","client_name":"{{$from->name}}","room_id":"{{$room}}","to":"{{$to->id}}","from":"{{$from->id}}","tag":"{{$tag}}"}';

    ws.send(login_data);
    console.log('登录成功')
  }

  // 服务端发来消息时
  function onmessage(e)
  {
    var data = JSON.parse(e.data);
    switch(data['type']){
      // 服务端ping客户端心跳
      case 'ping':
        ws.send('{"type":"pong"}');
        break;
      // 登录 更新用户列表
      case 'login':
        //讲需要的发送ID保存到本地to_client_id变量中
        for(var p in data['client_list']){
          to_client_id=p;
        }
        console.log(to_client_id);
        break;
      // 发言
      case 'say':
        console.log(data);
        say(data['from_client_id'], data['from_client_name'], data['content'], data['time']);
        break;
      // 用户退出 更新用户列表
      case 'logout':
        console.log(data);
        break;
      case 'init':
        //此处可以发送ajax用于绑定不同的用户ID和client
        console.log(data);
        break;

    }
  }

  // 提交对话
  function onSubmit() {
    //先检查当前的对话是否超过20条记录数
    var count=true;
    //发送ajax获取当前房间的通话记录
    $.ajax({
      url: "/check_count",
      type: "post",
      async:false,
      // cache: false,
      // contentType: false,
      // processData: false,
      data:{
      'room':"1",
      },
      success: function (msg) {
        if(msg>10){
          alert('当前的对话已经超过次数,请购买对应服务')
          count=false;
        }
      }

    });


    if(count){
      var neirong=$("#textarea").val().replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r');
      //ajax先把对应的内容发送到后台录入,回调成功后才把信息发送
      var fm=$("#ajaxfrom")[0];
      var formData = new FormData(fm);
      $.ajax({
        url: "/record",
        type: "post",
        cache: false,
        contentType: false,
        processData: false,
        data: formData,
        beforeSend:function(){

        },
        success: function (msg) {

          if(msg.code=="0"){
            ws.send('{"type":"say","to_client_id":"all","to_client_name":"{{$to->name}}","content":"'+neirong+'"}');
            //清空文本框内容
            $("#textarea").val("");
            //强制定位光标
            $("#textarea").focus();
          }else{

          }

        }

      });
    }

    return false;


  }



  // 发言
  function say(from_client_id, from_client_name, content, time){
    //判断当前的用户名称与发送消息的名称是否一致
    if( "{{$from->name}}" == from_client_name){
      $(".content").append(
        '<div style="min-height: 50px;margin-top: 10px;">' +
        '<div style="width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right">'+
        '<img src="{{$from->heading}}" width="50px" height="50px">'+
        '</div>'+
        '<div style="border: 1px solid red; float: right; min-height: 50px" >'+content+'</div>'+
        '<div>'
      ).parseEmotion();
    }else{
      $(".content").append(
        '<div style="min-height: 50px;margin-top: 10px;">' +
        '<div style="width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left">'+
        '<img src="{{$to->heading}}" width="50px" height="50px">'+
        '</div>'+
        '<div style="border: 1px solid red; float: left; min-height: 50px" >'+content+'</div>'+
        '<div>'
      ).parseEmotion();
    }

    // $("#dialog").append('<div class="speech_item"><img src="http://lorempixel.com/38/38/?'+from_client_id+'" class="user_icon" /> '+from_client_name+' <br> '+time+'<div style="clear:both;"></div><p class="triangle-isosceles top">'+content+'</p> </div>').parseEmotion();
  }
  $(function(){
    //全局用户ID
    select_client_id = 'all';

    //如果发送的用户有变化则对应的用户ID进行替换
    $("#client_list").change(function(){
      select_client_id = $("#client_list option:selected").attr("value");
    });
    //表情选择
    $('.face').click(function(event){
      $(this).sinaEmotion();
      event.stopPropagation();
    });
  });

  // document.write('<meta name="viewport" content="width=device-width,initial-scale=1">');
  $("textarea").on("keydown", function(e) {
    //按enter键自动提交
    if(e.keyCode === 13 && !e.ctrlKey) {
      e.preventDefault();
      $('form').submit();
      return false;
    }

    // 按ctrl+enter组合键换行
    if(e.keyCode === 13 && e.ctrlKey) {
      $(this).val(function(i,val){
        return val + "\n";
      });
    }
  });
</script>


上面是主要运行的核心代码。其他框架的自带参数需要各位自己去根据文档去调试优化。到此基于workerman的聊天用于功能demo已经搭建完毕。


领取方式:点赞关注小编后私信【资料】获取资料领取方式!

本我是准备接着写我那个多进程教程的,今天心血来潮想看看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,可以了。

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

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

在“疫情”期间已经淘汰了一批末端的业务coder,现在是自己努力成为资深程序员的好时机,才能在面对高薪职位邀请时,做到胸有成竹。为了大家能够顺利进阶PHP中高级程序员、架构师,我为大家准备了一份中高级的教程福利!

作为web开发的佼佼者PHP并不逊色其他语言,加上swoole后更加是如虎添翼!进军通信 、物联网行业开发百度地图、百度订单中心等!年后更是霸占程序员招聘语言第二名,疫情裁员期过后正是各大企业扩大招人的时期,现在市场初级程序员泛滥,进阶中高级程序员绝对是各大企业急需的人才,这套教程适合那些1-6年的PHP开发者进阶中高级提升自己,在春招中找到高薪职位!

领取方式:点赞关注小编后私信【资料】获取资料领取方式!