laravel集成Workerman搭建websocket服务,前端调用,服务器启动

本文有相对完整的集成代码和前端测试代码,以及服务器启动注意事项

一、后端(laravel5.5

1、composer安装Workerman

composer require workerman/workerman

2、生成执行命令,执行后会在App\Console\Command文件夹下生成一个Workerman.php的文件

php artisan make:command Workerman

3、打开Workerman.php的文件,并修改代码,如下:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Tymon\JWTAuth\JWTAuth;
use Workerman\Timer;
use Workerman\Worker;
// 心跳间隔55秒
define('HEARTBEAT_TIME', 3600);
class Workerman extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'Workerman {action} {--daemonize}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        global $argv;//定义全局变量
        $arg = $this->argument('action');
        $argv[1] = $arg;
        $argv[2] = $this->option('daemonize') ? '-d' : '';//该参数是以daemon(守护进程)方式启动

        global $text_worker;
        // 创建一个Worker监听9991端口,使用websocket协议通讯
        $text_worker = new Worker("websocket://0.0.0.0:9991");
        $text_worker->uidConnections = array();//在线用户连接对象
        $text_worker->uidInfo = array();//在线用户的用户信息
        // 启动4个进程对外提供服务
        $text_worker->count = 1;
        //当启动workerman的时候 触发此方法
        $text_worker->onWorkerStart =function(){
            //监听一个内部端口,用来接收服务器的消息,转发给浏览器
            $inner_text_worker = new Worker('text://127.0.0.1:5678');
            $inner_text_worker->onMessage = function($connection_admin, $data)
            {
                global $text_worker;
                // $data数组格式,里面有uid,表示向那个uid的页面推送数据
                $buffer = json_decode($data, true);
                //var_dump($buffer);
                $to_uid = $buffer['to_uid'];
                //var_dump($to_uid);

                // 通过workerman,向uid的页面推送数据
                if(isset($text_worker->uidConnections[$to_uid])){
                    $connection = $text_worker->uidConnections[$to_uid];
                    $ret = $connection->send($data);
                } else {
                    var_dump($to_uid . ': not define');
                    $ret = false;
                }
                // 返回推送结果
                $connection_admin->send($ret ? 'ok' : 'fail');
            };

            $inner_text_worker->listen();

            // 进程启动后设置一个每10秒运行一次的定时器
            Timer::add(10, function(){
                global $text_worker;
                $time_now = time();
                foreach($text_worker->connections as $connection) {
                    // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
                    if (empty($connection->lastMessageTime)) {
                        $connection->lastMessageTime = $time_now;
                        continue;
                    }
                    // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
                    if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
                        var_dump("delete:" . $connection->uid);
                        unset($text_worker->uidConnections["{$connection->uid}"]);
                        $connection->close();
                    }
                }
            });
        };
        //当浏览器连接的时候触发此函数
        $text_worker->onConnect = function($connection) {
        };
        //向用户发送信息的时候触发
        //$connection 当前连接的人的信息 $data 发送的数据
        $text_worker->onMessage = function($connection,$data){
            // 给connection临时设置一个lastMessageTime属性,用来记录上次收到消息的时间
            $connection->lastMessageTime = time();
            // 其它业务逻辑...
            $data = json_decode($data, true);
            if($data['type']=='login') {
                $this->create_uid($connection,$data);
            }
            if($data['type']=='send_message'){
                $this->send_message($connection,$data);
            }
        };
        //浏览器断开链接的时候触发
        $text_worker->onClose = function($connection){};
        Worker::runAll();
    }

    //创建uid方法
    public function create_uid($connection,$data){
        global $text_worker;
        $connection->uid = $data['uid'];
        //保存用户的uid
        $text_worker->uidConnections["{$connection->uid}"] = $connection;
        //向自己的浏览器返回创建成功的信息
        $connection->send(json_encode([
            "uid"=>$connection->uid,
            "msgType"=>'text',
            "content"=>'聊天创建成功,您可以开发发送消息了'
        ]));
    }

    public function send_message($connection,$data) {
        global $text_worker;
        if(isset($data['to_uid'])){
            if(isset($text_worker->uidConnections["{$data['to_uid']}"])){
                $to_connection=$text_worker->uidConnections["{$data['to_uid']}"];
                $to_connection->send(json_encode([
                    "uid"=>$data['uid'],
                    "msgType"=>$data['msgType'],
                    "content"=>$data['message']
                ]));
            }
        }
    }
}

4、修改App\Console下面的Kernel.php文件,如下:

protected $commands = [
        Commands\Workerman::class,
];

5、启动Workerman服务,启动命令如下:

php artisan Workerman start

出现上图所示,则表示启动成功

二、前端如何连接Workerman服务(websocket)服务

1、新建两个html页面用于测试,当然,也可以直接在浏览器打开F12在console里面测试,我这里有写好的页面,直接换个ws地址就能用,支持发送文字,图片

37websoket.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
  />
  <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> <!-- 引入vue.js -->
</head>
<body>
  <div id="app">
    <div style="padding: 10px;height: 76vh;overflow-y: scroll;" ref="scrollDiv">
      <div v-for="(item,index) in content" :key="index">
        <div class="left-box" v-if="item.uid == 38">
          <div>
            <img src="./nv.png" style="width:35px;flex-shrink: 0;">
          </div>
          <div class="msg-box-l" v-if="item.msgType=='text'">
            <div class="msg-text-l">{{item.content}}</div>
          </div>
          <div class="msg-box-l" v-if="item.msgType=='img'" style="max-width:40%;background: none;box-shadow: none;">
            <img :src="item.content" style="width: 100%;border-top-right-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;">
          </div>
        </div>
        <div class="right-box" v-else>
          <div class="msg-box-r" v-if="item.msgType=='text'">
            <div class="msg-text-r">{{item.content}}</div>
          </div>
          <div class="msg-box-r" v-if="item.msgType=='img'" style="max-width:40%;background: none;box-shadow: none;">
            <img :src="item.content" style="width: 100%;border-top-left-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;">
          </div>
          <div>
            <img src="./nan.png" style="width:35px;flex-shrink: 0;">
          </div>
        </div>
      </div>
    </div>
    
  	<div class="ta">
        <textarea type="text" name="" v-model="msg" class="ta1"></textarea>
        <div class="r-b">
          <input type="file" id="file" @change="sendPic" class="input-file" />
          <div @click="send" class="send-btn">发消息</div>
          <label for="file" class="send-btn">发图片</label>
        </div>
    </div>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
          content:[],
          msg: "",
          path:"ws://127.0.0.1:9991//ws",
          socket:""
      },
      mounted () {
        // 初始化
        this.init()
      },
      methods:{
        scrollToBottom() {
          this.$nextTick(() => {
            let scrollElem = this.$refs.scrollDiv;
            scrollElem.scrollTo({ top: scrollElem.scrollHeight, behavior: 'smooth' });
          });
        },
        sendPic(file) {
          console.log(file)
          const reader = new FileReader();
          reader.readAsDataURL(file.target.files[0]);
          reader.onload = () => {
            // const src = reader.result;
            this.content.push({
              uid: 37,
              msgType: 'img',
              content: reader.result
            })
            this.socket.send(`{"type":"send_message","to_uid":38,"uid":37,"msgType":"img", "message":"${reader.result}"}`)
            this.scrollToBottom()
            // 这里的reader.result就是文件的base64了。如果是图片的话,就可以直接放到src中展示了
          };
        },
      	init: function () {
            if(typeof(WebSocket) === "undefined"){
                alert("您的浏览器不支持socket")
            }else{
                // 实例化socket
                this.socket = new WebSocket(this.path)
                // 监听socket连接
                this.socket.onopen = this.open
                // 监听socket错误信息
                this.socket.onerror = this.error
                // 监听socket消息
                this.socket.onmessage = this.getMessage
            }
        },
        open: function () {
          this.socket.send('{"uid":37,"type":"login"}');
            console.log("socket连接成功")
        },
        error: function () {
            console.log("连接错误")
        },
        getMessage: function (msg) {
          this.content.push({
            uid: JSON.parse(msg.data).uid,
            msgType: JSON.parse(msg.data).msgType,
            content: JSON.parse(msg.data).content
          })
          this.scrollToBottom()
            console.log(msg.data)
        },
        // 发送消息给被连接的服务端
        send: function () {
          this.content.push({
              uid: 37,
              msgType: 'text',
              content: this.msg
            })
            this.socket.send(`{"type":"send_message","to_uid":38,"uid":37,"msgType":"text","message":"${this.msg.replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"}`)
            this.scrollToBottom()
            this.msg = ""
        },
        close: function () {
            console.log("socket已经关闭")
        }
      },
      // destroyed () {
      //   // 销毁监听
      //   this.socket.onclose = this.close
      // }
    });
  </script>
</body>
<style type="text/css">
  html,body{margin: 0;padding:0}
  #app{height: 100vh;background: #F5F5F5}
  .left-box {display: flex;flex-shrink: 0;margin-bottom: 10px;margin-top: 10px;}
  .right-box {display: flex;justify-content: flex-end;width: 100%;margin-bottom: 10px;margin-top: 10px;}
  .msg-box-l{background: #fff;max-width: 80%;padding: 8px;border-top-right-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;margin-left: 10px;margin-top: 10px;box-shadow: 0 2px 11px -7px rgba(2,66,58,.5)}
  .msg-box-r{background: #95EC69;max-width: 80%;padding: 8px;border-top-left-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;margin-right: 10px;margin-top: 10px;box-shadow: 0 2px 11px -7px rgba(2,66,58,.5)}
  .msg-text-l{color: #000;word-break: break-all;}
  .msg-text-r{color: #000;word-break: break-all;}
.ta{resize: none;width: 100%;height: 100px;position: fixed;bottom: 20px;}
.ta1{resize: none;width: 70%;margin-left:5%;height: 100px;position: fixed;bottom: 20px;}
.r-b{display: flex;align-items: flex-end;flex-direction: column;}
.send-btn{background: #4287FD;color: #fff;padding: 5px 10px;margin: 10px;}
.input-file {
    width: 0.1px; 
    height: 0.1px; 
    opacity: 0; 
    overflow: hidden; 
    position: absolute; 
    z-index: -1;

</style>
</html>

38websoket.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
  />
  <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> <!-- 引入vue.js -->
</head>
<body>
  <div id="app">
    <div style="padding: 10px;height: 76vh;overflow-y: scroll;" ref="scrollDiv">
      <div v-for="(item,index) in content" :key="index">
        <div class="left-box" v-if="item.uid == 37">
          <div>
            <img src="./nv.png" style="width:35px;flex-shrink: 0;">
          </div>
          <div class="msg-box-l" v-if="item.msgType=='text'">
            <div class="msg-text-l">{{item.content}}</div>
          </div>
          <div class="msg-box-l" v-if="item.msgType=='img'" style="max-width:40%;">
            <img :src="item.content" style="width: 100%;border-top-right-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;">
          </div>
        </div>
        <div class="right-box" v-else>
          <div class="msg-box-r" v-if="item.msgType=='text'">
            <div class="msg-text-r">{{item.content}}</div>
          </div>
          <div class="msg-box-r" v-if="item.msgType=='img'" style="max-width:40%;background: none;box-shadow: none;">
            <img :src="item.content" style="width: 100%;border-top-left-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;">
          </div>
          <div>
            <img src="./nan.png" style="width:35px;flex-shrink: 0;">
          </div>
        </div>
      </div>
    </div>
    
    <div class="ta">
        <textarea type="text" name="" v-model="msg" class="ta1"></textarea>
        <div class="r-b">
          <input type="file" id="file" @change="sendPic" class="input-file" />
          <div @click="send" class="send-btn">发消息</div>
          <label for="file" class="send-btn">发图片</label>
        </div>
    </div>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
          content:[],
          msg: "",
          path:"ws://127.0.0.1:9991//ws",
          socket:""
      },
      mounted () {
        // 初始化
        this.init()
      },
      methods:{
        scrollToBottom() {
          this.$nextTick(() => {
            let scrollElem = this.$refs.scrollDiv;
            scrollElem.scrollTo({ top: scrollElem.scrollHeight, behavior: 'smooth' });
          });
        },
        sendPic(file) {
          console.log(file)
          const reader = new FileReader();
          reader.readAsDataURL(file.target.files[0]);
          reader.onload = () => {
            // const src = reader.result;
            this.content.push({
              uid: 38,
              msgType: 'img',
              content: reader.result
            })
            this.socket.send(`{"type":"send_message","to_uid":37,"uid":38,"msgType":"img", "message":"${reader.result}"}`)
            this.scrollToBottom()
            // 这里的reader.result就是文件的base64了。如果是图片的话,就可以直接放到src中展示了
          };
        },
        init: function () {
            if(typeof(WebSocket) === "undefined"){
                alert("您的浏览器不支持socket")
            }else{
                // 实例化socket
                this.socket = new WebSocket(this.path)
                // 监听socket连接
                this.socket.onopen = this.open
                // 监听socket错误信息
                this.socket.onerror = this.error
                // 监听socket消息
                this.socket.onmessage = this.getMessage
            }
        },
        open: function () {
          this.socket.send('{"uid":38,"type":"login"}');
            console.log("socket连接成功")
        },
        error: function () {
            console.log("连接错误")
        },
        getMessage: function (msg) {
          this.content.push({
            uid: JSON.parse(msg.data).uid,
            msgType: JSON.parse(msg.data).msgType,
            content: JSON.parse(msg.data).content
          })
          this.scrollToBottom()
            console.log(msg.data)
        },
        // 发送消息给被连接的服务端
        send: function () {
          this.content.push({
              uid: 38,
              msgType: 'text',
              content: this.msg
            })
            this.socket.send(`{"type":"send_message","to_uid":37,"uid":38,"msgType":"text","message":"${this.msg.replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"}`)
            this.scrollToBottom()
            this.msg = ""
        },
        close: function () {
            console.log("socket已经关闭")
        }
      },
      // destroyed () {
      //   // 销毁监听
      //   this.socket.onclose = this.close
      // }
    });
  </script>
</body>
<style type="text/css">
  html,body{margin: 0;padding:0}
  #app{height: 100vh;background: #F5F5F5}
  .left-box {display: flex;flex-shrink: 0;margin-bottom: 10px;margin-top: 10px;}
  .right-box {display: flex;justify-content: flex-end;width: 100%;margin-bottom: 10px;margin-top: 10px;}
  .msg-box-l{background: #fff;max-width: 80%;padding: 8px;border-top-right-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;margin-left: 10px;margin-top: 10px;box-shadow: 0 2px 11px -7px rgba(2,66,58,.5)}
  .msg-box-r{background: #95EC69;max-width: 80%;padding: 8px;border-top-left-radius: 10px;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;margin-right: 10px;margin-top: 10px;box-shadow: 0 2px 11px -7px rgba(2,66,58,.5)}
  .msg-text-l{color: #000;word-break: break-all;}
  .msg-text-r{color: #000;word-break: break-all;}
.ta{resize: none;width: 100%;height: 100px;position: fixed;bottom: 20px;}
.ta1{resize: none;width: 70%;margin-left:5%;height: 100px;position: fixed;bottom: 20px;}
.r-b{display: flex;align-items: flex-end;flex-direction: column;}
.send-btn{background: #4287FD;color: #fff;padding: 5px 10px;margin: 10px;}
.input-file {
    width: 0.1px; 
    height: 0.1px; 
    opacity: 0; 
    overflow: hidden; 
    position: absolute; 
    z-index: -1;

</style>
</html>

分别保存为两个html文件,两个都在浏览器打开,页面上出现 “聊天创建成功,您可以开发发送消息了”,则说明连接成功,可以正常发消息了

至此调试完成!

三、在服务器运行Workerman服务

我们本地调试完成后,最终需要上传到服务器上去运行,例如:Centos,服务上也需要composer安装Workerman,方式和最开头一样,我直接说说如何启动

在服务器上启动Workerman和在本地有一定的区别,因为通常我们希望在服务器上,Websocket能一直处于启动状态,所以,上述所说的启动命令,要稍微改一下,即在命令后加上:--daemonize(守护进程启动)

php artisan Workerman start --daemonize (关掉终端,服务不会停止)

php artisan Workerman stop --daemonize (服务停止)

php artisan Workerman start (关掉终端,服务会停止)

php artisan Workerman stop (服务停止)

至此,laravel集成Workerman搭建websocket服务已完成

 本文参考文章:laravel与workerman结合_laravel workerman-CSDN博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Unity是一种跨平台的游戏开发引擎,它的网络功能强大,并支持搭建WebSocket服务器。以下是搭建WebSocket服务器的一般步骤: 1. 导入WebSocket库:首先需要在Unity项目中导入WebSocket库。可以通过在Unity Asset Store中搜索和下载适用于Unity的WebSocket库,也可以在GitHub等平台上找到最新的WebSocket库。 2. 创建服务器脚本:在Unity中,可以使用C#编写服务器脚本。创建一个新的C#脚本,并将其附加到游戏对象上,作为WebSocket服务器的入口点。 3. 初始化WebSocket服务器:在服务器脚本中,需要使用WebSocket库提供的方法来初始化服务器。这通常涉及指定服务器的IP地址和端口号,并设置其他配置选项(如是否使用SSL等)。 4. 监听连接:通过调用WebSocket库提供的方法,可以开始监听连接请求。一旦有客户端连接到服务器,就会触发特定的事件,我们可以在事件处理程序中编写处理逻辑。 5. 处理消息:一旦客户端与服务器成功连接,它们可以通过WebSocket发送消息。服务器需要编写逻辑来处理接收到的消息,并根据其内容采取相应的操作。可以使用WebSocket库提供的方法来解析消息并回复客户端。 6. 关闭连接:当客户端断开连接时,服务器也需要相应地关闭连接。可以编写代码来处理关闭连接的事件,并进行清理工作。 7. 测试服务器:最后,可以启动Unity游戏,并使用WebSocket客户端连接到服务器进行测试。可以通过发送和接收消息来验证服务器的功能是否正常。 总的来说,通过以上步骤,我们可以在Unity中搭建一个WebSocket服务器服务器脚本负责初始化服务器,监听和处理连接,接收和发送消息,并关闭连接。 ### 回答2: Unity是一款强大的游戏开发引擎,它提供了许多网络功能,包括搭建WebSocket服务器WebSocket是一种全双工通信协议,可以在客户端和服务器之间实现实时通信。 要在Unity中搭建WebSocket服务器,可以使用第三方库或插件。以下是一个基本的步骤: 1. 导入WebSocket库:首先,在Unity的Asset Store中搜索并导入WebSocket库。例如,可以使用SocketIoClientDotNet或websocket-sharp等库。 2. 创建服务器脚本:在Unity中创建一个脚本,用于管理WebSocket服务器的逻辑。这个脚本需要实现WebSocket服务器的初始化、连接管理以及消息处理等功能。 3. 初始化服务器:在脚本中,使用WebSocket库来初始化服务器。根据所选的库,有关如何进行初始化和设置服务器的示例代码可以在其文档中找到。 4. 监听连接:设置服务器监听指定的端口,以便能够接收客户端的连接请求。 5. 处理连接和消息:当客户端连接到服务器时,将触发一个事件。在事件处理程序中,可以实现客户端的验证、连接管理以及消息处理等逻辑。 6. 发送和接收消息:通过WebSocket库的API,可以发送和接收消息。根据具体需求,可以实现自定义的消息协议和消息处理逻辑。 7. 关闭服务器:当服务器不再需要时,可以调用相应的API来关闭服务器。 在Unity中搭建WebSocket服务器可以实现多种功能,例如实时聊天、多人游戏、实时数据推送等。通过WebSocket的全双工通信特性,可以提供更好的用户体验和交互性。 需要注意的是,搭建WebSocket服务器需要熟悉网络编程和Unity的开发环境。同时,根据项目的需求,可能需要进一步扩展服务器的功能和优化性能。 ### 回答3: Unity是一款多平台游戏开发引擎,虽然它主要用于创建游戏,但也可以用于其他类型的实时应用程序的开发,其中包括搭建WebSocket服务器。 在Unity中搭建WebSocket服务器可以通过使用第三方库或扩展来实现。以下是一个使用Best HTTP库来搭建WebSocket服务器的简要步骤: 首先,下载并导入Best HTTP库到Unity项目中。它是一个功能强大的HTTP库,可以与常见的WebSocket实现一起使用。 接下来,创建一个新的C#脚本,在脚本中编写服务器的逻辑。需要使用Best HTTP库中的WebSocket组件。 在脚本中,你可以设置WebSocket服务器的IP地址和端口号,并定义WebSocket连接的回调函数。可以在服务器和客户端之间传递消息,并处理各种富文本通讯协议。 在Unity中,你可以创建一个空对象并附加刚才编写的脚本。当游戏或应用程序运行时,这个对象将充当WebSocket服务器。 最后,你可以使用网络工具,例如Chrome的WebSocket调试工具或自定义的客户端应用程序,通过WebSocket协议连接到Unity中运行的服务器。通过这种方式,你可以在Unity中接收和处理客户端发送的消息。 总结起来,Unity可以使用Best HTTP库来搭建WebSocket服务器。你需要导入Best HTTP库,编写服务器逻辑的脚本,并使用WebSocket组件来管理WebSocket连接和处理消息。然后,你可以在Unity中运行服务器,并使用网络工具与之建立WebSocket连接。这样,你就可以在Unity中实现WebSocket服务器的功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SevenSpurt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值