通过PHP(socket_select)阻塞函数实现公共即时聊天

研究了一周,重要搞明白liunx网络编程的socket函数。下面是通过socket_select阻塞的方式实现php公共聊天的简易聊天室。啥都不说了,上代码。

<?php

class webSocket
{
    /**
     * 服务端地址
     *
     * @var [type]
     */
    private $address;



    /**
     * 服务端绑定的端口号
     *
     * @var [type]
     */
    private $port;


    /**
     * 监听端口
     *
     * @var [type]
     */
    private $socket;


    public function __construct($address='127.0.0.1',$port=9501)
    {
        $socket=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
        socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($socket,$address,$port);
        socket_listen($socket);
        $this->socket=$socket;
        $this->port=$port;
        $this->address=$address;
        echo '已启动websockt服务器地址:'.$address.';端口:'.$port.PHP_EOL;
    }   

    public function run()
    {
        //将原客户端加入监听连接池
        $sockets[]=$this->socket;  
        //定义写入监听连接池
        $write=null;
        //定义权限接受连接池
        $except=null;
        //定义超时时间
        $time_out=null;
        //启动循环阻塞任务
        while(true)
        {
            //复制连接池
            $changes=$sockets;
            //设置同步阻塞监听函数
            socket_select($changes,$write,$except,$time_out);
            //监听端口可读后操作
            foreach ($changes as $sock) {
                //如果监听到的是原端口
                if($sock==$this->socket)
                {
                    //读取错误
                    if(($client=socket_accept($sock))===false)
                    {
                        die('failed to accept socket: '.socket_strerror($sock)."\n");
                    }
                    //获取客户端发送内容
                    $content=trim(socket_read($client,1024));
                    //执行http协议升级websocket
                    $this->handshaking($client,$content);
                    $id=rand(1000,9999);
                    $message='id:'.$id.'客户端已加入连接池';   
                    //通知全体客户端
                    $this->sendAll($sockets,$sock,$message);
                    //客户端加入连接池
                    $sockets[$id]=$client; 
                    //服务端提示
                    echo 'id:'.$id.'客户端已加入连接池'.PHP_EOL;
                }
                //已经握手完毕
                else
                {
                    //接收数据
                    socket_recv($sock,$buf,1024,0); 
                    //群发消息 
                    $str='';
                    foreach ($sockets as $k => $v) {
                        if($sock==$v)
                        {
                            $str='来自 '.$k.' 的消息:';
                            break;
                        }
                    }     
                    $this->sendAll($sockets,$sock,$str.$this->message($buf));
                }
            }
        }
    }


    /**
     * 升级协议
     *
     * @param [type] $client
     * @param [type] $content
     * @return void
     */
    public function handshaking($client,$content)
    {
        //定义头部信息
        $headers=array();
        if(preg_match('/Sec-WebSocket-Key:.*\r\n/',$content,$matchs))
        {
            $headers['Sec-WebSocket-Key'] =trim(chop(str_replace('Sec-WebSocket-Key:',"",$matchs[0])));
        }
        //设置返回头
        $secKey = $headers['Sec-WebSocket-Key'];
        $websocket_accept=base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        $upgrade  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "WebSocket-Origin: $this->address\r\n" .
            "WebSocket-Location: ws://$this->address:$this->port/websocket/websocket\r\n".
            "Sec-WebSocket-Accept:$websocket_accept\r\n\r\n";
        //写入缓冲
        return socket_write($client,$upgrade,strlen($upgrade));
    }


    /**
     * 解析接收数据
     * @param $buffer
     * @return null|string
     */
    public function message($buffer)
    {
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;
        if ($len === 126)  
        {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127)  
        {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } 
        else  
        {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) 
        {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        return $decoded;
    }

    /**
     * 消息广播
     *
     * @param [type] $socket
     * @param [type] $sock
     * @param [type] $message
     * @return void
     */
    public function sendAll($socket,$sock,$message)
    {
        foreach ($socket as $so) {
            if($so!=$sock && $so!=$this->socket)
            {
                $this->send($so,$message);
            }
        }
    }

     
    /**
     * 发送数据
     * @param $newClinet 新接入的socket
     * @param $msg   要发送的数据
     * @return int|string
     */
    public function send($clinet, $msg){
        $msg = $this->frame($msg);
        socket_write($clinet, $msg, strlen($msg));
    }
    

    /**
     * 处理数据帧
     *
     * @param [type] $s
     * @return void
     */
    public function frame($s) 
    {
        $a = str_split($s, 125);
        if (count($a) == 1) 
        {
            return "\x81" . chr(strlen($a[0])) . $a[0];
        }
        $ns = "";
        foreach ($a as $o) 
        {
            $ns .= "\x81" . chr(strlen($o)) . $o;
        }
        return $ns;
    }
}

$socketobj=new webSocket();
$socketobj->run();

这里是前端内容

<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>websocket</title>
 </head>
 <body>
 <input id="text" value="">
 <input type="submit" value="send" onclick="start()">
<div id="msg"></div>
 <script>
    /**
      *0:未连接
      *1:连接成功,可通讯
      *2:正在关闭
      *3:连接已关闭或无法打开
      */
    //创建一个webSocket 实例
    var webSocket  = new  WebSocket("ws://127.0.0.1:9501");
 
 
    webSocket.onerror = function (event){
        onError(event);
    };
 
    // 打开websocket
    webSocket.onopen = function (event){
        onOpen(event);
    };
 
    //监听消息
    webSocket.onmessage = function (event){
        onMessage(event);
    };
 
 
    webSocket.onclose = function (event){
        onClose(event);
    }
 
    //关闭监听websocket
    function onError(event){
        document.getElementById("msg").innerHTML = "<p>close</p>";
        console.log("error"+event.data);
    };
 
    function onOpen(event){
        console.log("open:"+sockState());
        document.getElementById("msg").innerHTML = "<p>Connect to Service</p>";
    };
    
    function onMessage(event){
        console.log("onMessage");
        document.getElementById("msg").innerHTML += "<p>response:"+event.data+"</p>"
    };
 
    function onClose(event){
        document.getElementById("msg").innerHTML = "<p>close</p>";
        console.log("close:"+sockState());
        webSocket.close();
    }
 
    function sockState(){
        var status = ['未连接','连接成功,可通讯','正在关闭','连接已关闭或无法打开'];
            return status[webSocket.readyState];
    }
 
    function start(event){
        console.log(webSocket);
        var msg = document.getElementById('text').value;
        document.getElementById('text').value = '';
        console.log("send:"+sockState());
        console.log("msg="+msg);
        webSocket.send("msg="+msg);
        document.getElementById("msg").innerHTML += "<p>request"+msg+"</p>"
    };
 
    function close(event){
        webSocket.close();
    }
 </script>
 </body>
</html>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值