基于websocket的扫码登录

扫码登录流程
启动socket服务端代码
1,用户访问页面,生成一个随机的uid,并且将uid跟手机端访问页面链接绑定到一起,然后生成一个二维码,连接socket服务器,等待socket服务器信息
2,用户扫码,进入手机端页面,开始进行登录操作,登录成功之后,将uid跟用户进行绑定,连接socket服务器,并向socket服务器发送uid,发送完成后断开连接
3,socket服务器收到uid后,获取uid绑定的用户信息,并且向所有pc端发送uid跟用户信息,同时断开与移动端的连接。
4,pc端收到信息后,跟生成uid进行对比,如果一致,说明用户登录成功,并向服务器返回登陆成功信息
5,服务器收到登陆成功信息后断开与pc端的连接

server.php 服务端
login.php pc端
sign.php 移动端
config.php 配置文件,socket地址跟端口
同时还需要有一个生成二维码的类库,php或者jq都可以,我这里使用phpqrcode这个类库
以上就是websocket扫码登陆流程,下面上代码
server.php
socket服务端,跟之前博客写的差不多


set_time_limit(0);
class server{
    private $host = 'localhost';
    private $port = 8080;
    private $maxuser = 10;
    public  $accept = array();//所有客户端
    private $cycle = array(); //循环连接池
    private $isHand = array();//握手信息
    public  $pcAccept = array(); //连接的pc端

    function __construct($host='', $port='', $max='') {
        if(!empty($host)) $this->host = $host;
        if(!empty($port)) $this->port = $port;
        if(!empty($maxuser)) $this->maxuser = $max;
    }

    public function start() {
        //挂起socket服务端
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, TRUE);
        socket_bind($this->socket, $this->host, $this->port);
        socket_listen($this->socket, $this->maxuser);
        while(TRUE) {
            //获取所有socket连接
            $this->cycle = $this->accept;
            $this->cycle[] = $this->socket;

            //阻塞用,有新连接时才会结束
            socket_select($this->cycle, $write, $except, null);
            //遍历循环池
            foreach ($this->cycle as $k => $v) {
                //添加连接
                if($v === $this->socket) {
                    if (($accept = socket_accept($v)) < 0) {
                        continue;
                    }
                    $this->add($accept);
                    continue;
                }

                //阻塞已断开连接
                $acceptId = array_search($v, $this->accept);
                if ($acceptId === NULL) {
                    continue;
                }

                //没消息的socket就断开
                if (!@socket_recv($v, $data, 1024, 0) || !$data) {
                    $this->close($v);
                    continue;
                }

                //判断是否需要握手
                if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$data,$match)){
                    //进行握手
                    if (!$this->isHand[$acceptId]) {
                        $this->upgrade($v, $match[1], $acceptId);
                        continue;
                    }
                }

                //获取头文件数据,判断是否需要解码
                if(substr($data, 0, 9) == 'noencode:'){
                    $data = substr($data,9,strlen($data));
                }else{
                    //将数据进行解码
                    $data = $this->decode($data);
                    //登陆成功,关闭与PC端连接
                    if($data == 'success'){
                        $this->close($v);
                    }
                    continue;
                }

                //向所有PC发送uid
                if(!empty($this->pcAccept)){
                    $data = $this->frame($data);
                    $this->send($data);
                }
                //断开移动端连接,减少损耗
                $this->close($v);
            }
            sleep(1);
        }
    }

    //往连接池里添加新连接
    private function add($accept) {
        $this->accept[] = $accept;
        $accept = array_keys($this->accept);
        $acceptId = end($accept);
        $this->isHand[$acceptId] = FALSE;
    }

    //向所有pc端发送信息
    private function send($data){
        foreach ($this->pcAccept as $accept) {
            socket_write($accept, $data, strlen($data));
        }
    }

    //关闭一个连接
    private function close($accept) {
        $acceptId = array_search($accept, $this->accept);
        socket_close($accept);
        //销毁变量
        unset($this->accept[$acceptId]);
        unset($this->isHand[$acceptId]);
    }

    //与客户端握手
    private function upgrade($accept, $key, $acceptId) {
        //服务端生成对应key值返回
        $key = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "Sec-WebSocket-Accept: " . $key . "\r\n\r\n";  //必须以两个回车结尾
        //向套接口写入数据
        socket_write($accept, $upgrade, strlen($upgrade));
        $this->isHand[$acceptId] = TRUE;
        $this->pcAccept[$acceptId] = $accept;
    }

    //websocket在传输中是要进行编码
    //编码
    public function frame($s){
        //将数据转为数组,一个元素长度最高为125
        $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;
    }

    //解码
    public function decode($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;
    }
}

//开启socket服务
$config = include('config.php');
$ip = $config['ip'];
$port = $config['port'];
$socket = new server($ip,$port);
$socket->start();

login.php
PC访问页面,其实此处用用js生成随机数跟二维码更好,但是我对php更熟悉,所以就用了php生成,前段通过ajax获取图片地址跟随机数


/**
 * @param $filename     文件名 PNG格式
 * @param $data         二维码内容
 * @param string $errorCorrectionLevel      纠错程度
 * @param int $matrixPointSize              二维码大小
 * return  返回访问路径
 */
function createQrcode($fileName,$data,$errorCorrectionLevel = 'L',$matrixPointSize = 3){
    //这里引入自己的二维码生成类库就OK了
    include("class/phpqrcode/qrlib.php");
    $filePath = "./public/";//文件访问路径
    $savePath = './public/';//文件保存路径
    //判断保存路径是否可以使用
    if (!file_exists($savePath))
        mkdir($savePath);

    $savePath = $savePath.$fileName;//文件保存路径
    \QRcode::png($data, $savePath, $errorCorrectionLevel, 15, 2);//生成二维码
    $info = file_get_contents($filePath.basename($savePath));
    if(!empty($info)){
        return $filePath.basename($savePath);
    }else{
        return false;
    }
}
//获取随机数
function getRandomString($len, $chars=null)
{
    if (is_null($chars)){
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    }  
    mt_srand(10000000*(double)microtime());
    for ($i = 0, $str = '', $lc = strlen($chars)-1; $i < $len; $i++){
        $str .= $chars[mt_rand(0, $lc)];  
    }
    return $str;
}

function landing(){
    $config = include('config.php');
    $host = $config['host'];
    $port = $config['port'];
    //生成8位的随机数
    $uid = getRandomString(8);
    //此处必须为完整链接
    $imgUri = createQrcode("$uid.png","http://$host/qrcode/sign.php/?uid=$uid");
    echo json_encode(['uid' => $uid , 'imgUri' => $imgUri,'wsServer'=>"ws://$host:$port"]);
}
if(!empty($_GET['func'])) {
    call_user_func($_GET['func']);
    exit();
}
include('view/login.html');

login.html
php文件include的login页面

<head>
    <script type="text/javascript" src="js/jquery-1.8.3.min.js">script>
    <script type="text/javascript">
    (function() {
        $.ajax({
            type: "get",
            url: 'login.php?func=landing',
            data: {},
            success: function(result) {
                var data = JSON.parse(result);
                get('img').innerHTML="";
                start(data.wsServer,data.uid);
            }
        }); 
    })();

    function get(id){return document.getElementById(id);}

    function start(wsServer,uid){
        //配置websocket
        var ws = new WebSocket(wsServer);
        var isConnect = false;
        ws.onopen = function (evt) { onOpen(evt) };
        ws.onclose = function (evt) { onClose(evt) }; 
        ws.onmessage = function (evt) { onMessage(evt) }; 
        ws.onerror = function (evt) { onError(evt) }; 
        //连接socket服务器
        function onOpen(evt) { 
            console.log("connect success");
            isConnect = true;
        }   
        //处理socket服务器返回数据
        function onMessage(evt) {
            var data = JSON.parse(evt.data);
            console.log(data);
            if(data.uid==uid){
                sendMsg();
                get('img').innerHTML = data.name+' login success!!';
            }
            console.log('Retrieved data from server: ' + evt.data);
        }
        //处理socket连接断开
        function onClose(evt) { 
            console.log("Disconnected"); 
        } 
        //处理socket错误
        function onError(evt) { 
            console.log('Error occured: ' + evt.data); 
        }
        //发送socket数据
        function sendMsg() {
            if(isConnect){
                ws.send('success');
            }
        }
    }
    script>

head>
<body>
    <a id='img'>a>
body>

sign.php
手机端访问页面,这里如果用微信授权可以不要html页面,将login换成微信授权的代码就ok了


function login(){
    if(!empty($_POST)){
        var_dump($_POST);
        send($_POST['uid'],$_POST['name']);
        exit();
    }   
}
function send($uid,$name){
    if(empty($uid)) exit('error');
    //加载配置
    $config = include('config.php');
    $host = $config['host'];
    $port = $config['port'];
    $json = json_encode(['uid'=>$uid,'name'=>$name]);
    $data = 'noencode:'.$json;
    //连接socket服务器
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    $result = socket_connect($socket, $host, $port);
    socket_write($socket, $data, strlen($data));
    socket_close($socket);
    @unlink("public/".$uid.".png");
}
login();
include('view/sign.html');

sign.html
前端页面,这个纯粹是为了演示效果,是一个可有可无的页面

<head>
    <script type="text/javascript" src="js/jquery-1.8.3.min.js">script>
    <script>
    function ajax(){
        var name = document.getElementById('name').value;
        var uid = GetQueryString('uid');
        console.log(uid);
        $.ajax({
            type: "POST",
            data:'uid='+ uid +'&name='+ name,
            url: 'sign.php',
            success: function(result) {
                console.log(result);
            }.bind(this),
            error: function(data) {
                console.log(data);
            }
        });
    }

    function GetQueryString(name)
    {
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if(r!=null) return unescape(r[2]); 
        return null;
    }
    script>
head>
<body>
    <input type="text" id='name'>input>
    <button onclick="ajax()">登陆button>
body>

config.php
全局配置文件


return [
    'host' => '192.168.1.102',//socket服务器地址
    'port' => '8080',       //socket监听端口
    ];

以上就是一个简单的扫码登录,因为这些代码是我花了几个小时做出来的,可能有些粗糙,如果要用最好封装起来使用
github地址
https://github.com/sssxxxzzz/socket

你可能感兴趣的:(PHP,websocket)