当前位置: 首页 > news >正文

CLI PHP WebSocket服务器


<?php
error_reporting(E_ALL ^ E_NOTICE);
ob_implicit_flush();
$sk = new Sock('127.0.0.1', 2345);
$sk->run();
class Sock{
    // socket池
    public $sockets;
    // 用户socket池
    public $users;
    // 主socket
    public $master;
    // 已接收的数据
    private $sda = array();
    // 数据总长度
    private $slen = array();
    // 接收数据的长度
    private $sjen = array();
    // 加密key
    private $ar = array();
    private $n = array();
    /**
     * 构造方法
     */
    public function __construct($address, $port){
        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($this->master, $address, $port);
        socket_listen($this->master);
        // 提示已监听
        $this->e('Server Started : '.date('Y-m-d H:i:s'));
        $this->e('Listening on   : '.$address.' port '.$port);
        // 把主socket放入socket池
        $this->sockets = [$this->master];
    }
    /**
     * 转码输出后换行
     */
    public function e($str){
        // GBK转码成UTF8输出
        $str = $str."\n";
        echo iconv('utf-8', 'gbk//IGNORE', $str);
    }
    /**
     * 启动
     */
    function run(){
        while (TRUE) {
            // 获取socket池
            $changes = $this->sockets;
            $write = NULL;
            $except = NULL;
            // 为了socket_accept无阻塞完成
            // 当没有可以操作的socket时 返回传入的参数返回空
            socket_select($changes, $write, $except, NULL);
            foreach($changes as $sock){
                if($sock == $this->master){
                    // 等待连接
                    $client = socket_accept($this->master);
                    // 分配唯一ID保存子socket
                    $key = uniqid();
                    // 子socket放入socket池
                    $this->sockets[] = $client;
                    // 放入用户socket池
                    $this->users[$key] = [
                        'socket' => $client,
                        'shou' => false, // 标记为未握手状态
                    ];
                } else {
                    $len = 0;
                    $buffer = '';
                    do {
                        // 读取当前socket中的内容并放入$buf中 读取长度为1000
                        $l = socket_recv($sock, $buf, 1000, 0);
                        $len += $l;
                        $buffer .= $buf;
                        // 为何 $l = 1000 继续循环?
                    } while ($l == 1000);
                    // 判断是否在用户socket池
                    $k = $this->search($sock);
                    // $len < 7 用户退出
                    if($len < 7){
                        $this->send2($k);
                        continue;
                    }
                    // 得到唯一ID的socket的握手状态 如果为false进行握手
                    if(!$this->users[$k]['shou']){
                        $this->woshou($k, $buffer);
                    }else{
                        // 如果已经握手
                        $buffer = $this->uncode($buffer, $k);
                        if(FALSE == $buffer){
                            continue;
                        }
                        $this->send($k, $buffer);
                    }
                }
            }
        }
    }
    /**
     * 判断传入socket是否在用户socket池
     */
    function search($sock){
        foreach ($this->users as $k => $v){
            if($sock == $v['socket'])
            return $k;
        }
        return false;
    }
    /**
     * 握手
     */
    function woshou($k, $buffer){
        // 获得 Sec-WebSocket-Key: 后的字符串
        $buf = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
        // 获得 Key
        $key = trim(substr($buf, 0, strpos($buf,"\r\n")));
        // 拼接魔幻字符串 加密 Key
        $new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11", TRUE));
        // 拼接协议
        $new_message = "HTTP/1.1 101 Switching Protocols\r\n";
        $new_message .= "Upgrade: websocket\r\n";
        $new_message .= "Sec-WebSocket-Version: 13\r\n";
        $new_message .= "Connection: Upgrade\r\n";
        $new_message .= "Sec-WebSocket-Accept: ".$new_key."\r\n\r\n";
        // 向当前socket写入
        socket_write($this->users[$k]['socket'], $new_message, strlen($new_message));
        // 修改用户socket池中的当前socket为true
        $this->users[$k]['shou'] = TRUE;
        return TRUE;
    }
    /**
     * 关闭指定ID的socket
     */
    function close($k){
        socket_close($this->users[$k]['socket']);
        unset($this->users[$k]);
        $this->sockets = [$this->master];
        foreach($this->users as $v){
            $this->sockets[] = $v['socket'];
        }
        $this->e("key:$k close");
    }

    function uncode($str, $key){
        $mask = [];
        $data = '';
        // 字符串按解包数据格式进行二进制解包
        //echo $str;
        $msg = unpack('H*', $str);
        $head = substr($msg[1], 0, 2);
        if ($head == '81' && !isset($this->slen[$key])) {
            $len = substr($msg[1], 2, 2);
            $len = hexdec($len);
            if(substr($msg[1], 2, 2) == 'fe'){
                $len = substr($msg[1], 4, 4);
                $len = hexdec($len);
                $msg[1] = substr($msg[1], 4);
            }else if(substr($msg[1], 2, 2) == 'ff'){
                $len = substr($msg[1], 4, 16);
                $len = hexdec($len);
                $msg[1] = substr($msg[1], 16);
            }
            $mask[] = hexdec(substr($msg[1], 4, 2));
            $mask[] = hexdec(substr($msg[1], 6, 2));
            $mask[] = hexdec(substr($msg[1], 8, 2));
            $mask[] = hexdec(substr($msg[1], 10, 2));
            $s = 12;
            $n = 0;
        }else if($this->slen[$key] > 0){
            $len = $this->slen[$key];
            $mask = $this->ar[$key];
            $n = $this->n[$key];
            $s = 0;
        }

        $e = strlen($msg[1]) - 2;
        for ($i = $s; $i <= $e; $i += 2) {
            $data .= chr($mask[$n%4]^hexdec(substr($msg[1], $i, 2)));
            $n++;
        }
        $dlen=strlen($data);
        if($len > 255 && $len > $dlen + intval($this->sjen[$key])){
            $this->ar[$key] = $mask;
            $this->slen[$key] = $len;
            $this->sjen[$key] = $dlen+intval($this->sjen[$key]);
            $this->sda[$key] = $this->sda[$key].$data;
            $this->n[$key] = $n;
            return false;
        }else{
            unset($this->ar[$key], $this->slen[$key], $this->sjen[$key], $this->n[$key]);
            $data = $this->sda[$key].$data;
            unset($this->sda[$key]);
            return $data;
        }
    }

    function code($msg){
        $frame = array();
        $frame[0] = '81';
        $len = strlen($msg);
        if($len < 126){
            $frame[1] = $len < 16 ? '0'.dechex($len) : dechex($len);
        }else if($len < 65025){
            $s = dechex($len);
            $frame[1] = '7e'.str_repeat('0', 4 - strlen($s)).$s;
        }else{
            $s = dechex($len);
            $frame[1]= '7f'.str_repeat('0', 16 - strlen($s)).$s;
        }
        $frame[2] = $this->ord_hex($msg);
        $data = implode('', $frame);
        return pack("H*", $data);
    }

    function ord_hex($data){
        $msg = '';
        $l = strlen($data);
        for ($i= 0; $i < $l; $i++) {
            $msg .= dechex(ord($data{$i}));
        }
        return $msg;
    }

    //用户加入
    function send($k, $msg){
        parse_str($msg, $g);
        $ar=array();
        if($g['type'] == 'add'){
            $this->users[$k]['name'] = $g['ming'];
            $ar['type'] = 'add';
            $ar['name'] = $g['ming'];
            $key = 'all';
        }else{
            $ar['nrong'] = $g['nr'];
            $key = $g['key'];
        }
        $this->send1($k, $ar, $key);
    }

    function getusers(){
        $ar=array();
        foreach($this->users as $k => $v){
            $ar[] = ['code'=>$k,'name' => $v['name']];
        }
        return $ar;
    }

    //$k 发信息人的code $key接受人的 code
    function send1($k, $ar, $key = 'all'){
        $ar['code1'] = $key;
        $ar['code'] = $k;
        $ar['time'] = date('m-d H:i:s');
        $str = $this->code(json_encode($ar));
        if($key == 'all'){
            $users = $this->users;
            if($ar['type'] == 'add'){
                $ar['type'] = 'madd';
                $ar['users'] = $this->getusers();
                $str1 = $this->code(json_encode($ar));
                socket_write($users[$k]['socket'], $str1, strlen($str1));
                unset($users[$k]);
            }
            foreach($users as $v){
                socket_write($v['socket'], $str, strlen($str));
            }
        }else{
            socket_write($this->users[$k]['socket'], $str, strlen($str));
            socket_write($this->users[$key]['socket'], $str, strlen($str));
        }
    }

    //用户退出
    function send2($k){
        $this->close($k);
        $ar['type'] = 'rmove';
        $ar['nrong'] = $k;
        $this->send1(false, $ar, 'all');
    }
}

网上搜来的,出处未知,侵删。

相关文章:

  • Nginx 自带防盗链模块
  • 在MTK中添加TASK与常用函数分析
  • 大数据时代必不可少的大数据分析和制作工具大全
  • SubVersion服务器Windows安装指南
  • CentOS里ifcfg的device指的是什么?
  • SQLServer2005批量查询自定义对象脚本
  • 使用Let's encrypt保护你的网络通信
  • SQLServer中求两个字符串的交集
  • 139说客的优势和局限性
  • phpjm php加密的解密过程
  • 被遗忘的SQLServer比较运算符修饰词
  • PHP MySQL 数据字典生成器
  • 设计启动屏幕
  • CentOS 安装NodeJS
  • 潘多拉网吧防火墙 1.0 双线破解
  • [nginx文档翻译系列] 控制nginx
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • Java|序列化异常StreamCorruptedException的解决方法
  • nfs客户端进程变D,延伸linux的lock
  • React系列之 Redux 架构模式
  • RxJS: 简单入门
  • socket.io+express实现聊天室的思考(三)
  • uva 10370 Above Average
  • Web设计流程优化:网页效果图设计新思路
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 排序(1):冒泡排序
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 世界上最简单的无等待算法(getAndIncrement)
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 小程序开发之路(一)
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 云大使推广中的常见热门问题
  • Spring第一个helloWorld
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • (6)添加vue-cookie
  • (C++17) optional的使用
  • (二)斐波那契Fabonacci函数
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (四)c52学习之旅-流水LED灯
  • (一)Neo4j下载安装以及初次使用
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • (转)一些感悟
  • .NET gRPC 和RESTful简单对比
  • .NET 反射 Reflect
  • @Bean注解详解
  • @ModelAttribute使用详解
  • [ C++ ] STL_vector -- 迭代器失效问题
  • []error LNK2001: unresolved external symbol _m
  • [2016.7.test1] T2 偷天换日 [codevs 1163 访问艺术馆(类似)]
  • [20190113]四校联考
  • [android] 天气app布局练习
  • [bzoj 3534][Sdoi2014] 重建