扫码登录流程
启动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