PHP WebSocket:技术解析与实用指南
本文旨在帮助初学者掌握在PHP中使用WebSocket的关键概念和技术。我们将深入讨论从建立连接、绑定到监听等各方面的操作,并提供易于理解和实践的指导。
一、socket协议的简介
WebSocket是什么,有什么优点
WebSocket是一个持久化的协议,这是相对于http非持久化来说的。
举个简单的例子
HTTP/1.0和HTTP/1.1协议在每次请求-响应周期中都需要建立和关闭连接,这在实时通信场景下效率较低。虽然HTTP/1.1引入了持久连接(keep-alive),但仍然存在着单向通信的限制,服务器无法主动向客户端推送数据,这对于实时通信是不利的。
相比之下,WebSocket协议通过建立一次握手连接后,可以实现持久化连接,并且支持全双工通信。这意味着服务器和客户端之间可以在同一个连接上双向发送数据,服务器可以主动向客户端推送消息,从而实现了实时通信的要求。
通过使用WebSocket,开发者可以建立一个稳定的、低延迟的双向通信通道,适用于需要实时交互和推送消息的应用场景,比如在线聊天、实时数据更新等。
二、介绍client与server之间的socket连接原理
client与server之间的Socket连接原理是建立在TCP/IP协议栈之上的。在介绍Socket连接原理之前,我们先来理解一下Socket的概念:
Socket是应用层与传输层之间的接口,它允许应用程序通过网络进行通信。在TCP/IP协议中,Socket是通过IP地址和端口号来唯一标识的。
下面是client与server之间Socket连接的简要原理:
1. 建立连接(Connection Establishment):
客户端(client)向服务器(server)发起连接请求。客户端需要知道服务器的IP地址和端口号。
服务器在指定端口上监听(listen)传入的连接请求。
服务器接受连接请求,创建一个新的套接字(socket),用于与客户端通信。
一旦连接建立成功,客户端和服务器之间就可以通过各自的套接字进行数据交换。
2. 数据传输(Data Transfer):
一旦连接建立,客户端和服务器可以通过各自的套接字发送和接收数据。
数据通过套接字的读取和写入操作进行传输,使用TCP协议确保可靠的数据传输。
3. 关闭连接(Connection Termination):
当通信结束或者需要关闭连接时,客户端或服务器可以发起关闭连接的请求。
发起关闭连接的一方发送一个终止连接的请求,另一方确认,并且双方的套接字被关闭。
Socket连接的建立和维护是通过TCP协议来实现的,它提供了可靠的、全双工的数据传输通道,使得客户端和服务器能够进行双向通信。在实际应用中,开发者需要使用编程语言提供的Socket API来实现Socket连接,这样就可以在应用程序中创建、管理和控制Socket连接,实现数据的发送和接收。
socket建立连接原理图
三、PHP中建立socket的过程讲解
1、在PHP中,client与server之间建立socket通信,首先在PHP中创建socket并监听端口信息,代码如下:
//传相应的IP与端口进行创建socket操作
function WebSocket($address,$port){$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的数据包socket_bind($server, $address, $port);socket_listen($server);return $server;
}
2、设计一个循环挂起WebSocket通道,进行数据的接收、处理和发送
//对创建的socket循环进行监听,处理数据
function run(){//死循环,直到socket断开while(true){$changes=$this->sockets;$write=NULL;$except=NULL;/*//这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行。socket_select ($sockets, $write = NULL, $except = NULL, NULL);$sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。$write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。$except是$sockets里面要被排除的元素,传入NULL是”监听”全部。最后一个参数是超时时间如果为0:则立即结束如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回如果为null:如遇某一个连接有新动态,则返回*/socket_select($changes,$write,$except,NULL);foreach($changes as $sock){//如果有新的client连接进来,则if($sock==$this->master){//接受一个socket连接$client=socket_accept($this->master);//给新连接进来的socket一个唯一的ID$key=uniqid();$this->sockets[]=$client; //将新连接进来的socket存进连接池$this->users[$key]=array('socket'=>$client, //记录新连接进来client的socket信息'shou'=>false //标志该socket资源没有完成握手);//否则1.为client断开socket连接,2.client发送信息}else{$len=0;$buffer='';//读取该socket的信息,注意:第二个参数是引用传参即接收数据,第三个参数是接收数据的长度do{$l=socket_recv($sock,$buf,1000,0);$len+=$l;$buffer.=$buf;}while($l==1000);//根据socket在user池里面查找相应的$k,即健ID$k=$this->search($sock);//如果接收的信息长度小于7,则该client的socket为断开连接if($len<7){//给该client的socket进行断开操作,并在$this->sockets和$this->users里面进行删除$this->send2($k);continue;}//判断该socket是否已经握手if(!$this->users[$k]['shou']){//如果没有握手,则进行握手处理$this->woshou($k,$buffer);}else{//走到这里就是该client发送信息了,对接受到的信息进行uncode处理$buffer = $this->uncode($buffer,$k);if($buffer==false){continue;}//如果不为空,则进行消息推送操作$this->send($k,$buffer);}}}}}
3、以上服务器端完成的WebSocket的前期工作后,就等着client连接进行,client创建WebSocket很简单,代码如下:
var ws = new WebSocket("ws://IP:端口");
//握手监听函数
ws.onopen=function(){//状态为1证明握手成功,然后把client自定义的名字发送过去if(so.readyState==1){//握手成功后对服务器发送信息so.send('type=add&ming='+n);}
}
//错误返回信息函数
ws.onerror = function(){console.log("error");
};
//监听服务器端推送的消息
ws.onmessage = function (msg){console.log(msg);
}//断开WebSocket连接
ws.onclose = function(){ws = false;
}
四、聊天室实例代码
1、PHP部分
<?php
error_reporting(E_ALL ^ E_NOTICE);
ob_implicit_flush();//地址与接口,即创建socket时需要服务器的IP和端口
$sk=new Sock('127.0.0.1',8000);//对创建的socket循环进行监听,处理数据
$sk->run();//下面是sock类
class Sock{public $sockets; //socket的连接池,即client连接进来的socket标志public $users; //所有client连接进来的信息,包括socket、client名字等public $master; //socket的resource,即前期初始化socket时返回的socket资源private $sda=array(); //已接收的数据private $slen=array(); //数据总长度private $sjen=array(); //接收数据的长度private $ar=array(); //加密keyprivate $n=array();public function __construct($address, $port){//创建socket并把保存socket资源在$this->master$this->master=$this->WebSocket($address, $port);//创建socket连接池$this->sockets=array($this->master);}//对创建的socket循环进行监听,处理数据function run(){//死循环,直到socket断开while(true){$changes=$this->sockets;$write=NULL;$except=NULL;/*//这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行。socket_select ($sockets, $write = NULL, $except = NULL, NULL);$sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。$write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。$except是$sockets里面要被排除的元素,传入NULL是”监听”全部。最后一个参数是超时时间如果为0:则立即结束如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回如果为null:如遇某一个连接有新动态,则返回*/socket_select($changes,$write,$except,NULL);foreach($changes as $sock){//如果有新的client连接进来,则if($sock==$this->master){//接受一个socket连接$client=socket_accept($this->master);//给新连接进来的socket一个唯一的ID$key=uniqid();$this->sockets[]=$client; //将新连接进来的socket存进连接池$this->users[$key]=array('socket'=>$client, //记录新连接进来client的socket信息'shou'=>false //标志该socket资源没有完成握手);//否则1.为client断开socket连接,2.client发送信息}else{$len=0;$buffer='';//读取该socket的信息,注意:第二个参数是引用传参即接收数据,第三个参数是接收数据的长度do{$l=socket_recv($sock,$buf,1000,0);$len+=$l;$buffer.=$buf;}while($l==1000);//根据socket在user池里面查找相应的$k,即健ID$k=$this->search($sock);//如果接收的信息长度小于7,则该client的socket为断开连接if($len<7){//给该client的socket进行断开操作,并在$this->sockets和$this->users里面进行删除$this->send2($k);continue;}//判断该socket是否已经握手if(!$this->users[$k]['shou']){//如果没有握手,则进行握手处理$this->woshou($k,$buffer);}else{//走到这里就是该client发送信息了,对接受到的信息进行uncode处理$buffer = $this->uncode($buffer,$k);if($buffer==false){continue;}//如果不为空,则进行消息推送操作$this->send($k,$buffer);}}}}}//指定关闭$k对应的socketfunction close($k){//断开相应socketsocket_close($this->users[$k]['socket']);//删除相应的user信息unset($this->users[$k]);//重新定义sockets连接池$this->sockets=array($this->master);foreach($this->users as $v){$this->sockets[]=$v['socket'];}//输出日志$this->e("key:$k close");}//根据sock在users里面查找相应的$kfunction search($sock){foreach ($this->users as $k=>$v){if($sock==$v['socket'])return $k;}return false;}//传相应的IP与端口进行创建socket操作function WebSocket($address,$port){$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的数据包socket_bind($server, $address, $port);socket_listen($server);$this->e('Server Started : '.date('Y-m-d H:i:s'));$this->e('Listening on : '.$address.' port '.$port);return $server;}/** 函数说明:对client的请求进行回应,即握手操作* @$k clien的socket对应的健,即每个用户有唯一$k并对应socket* @$buffer 接收client请求的所有信息*/function woshou($k,$buffer){//截取Sec-WebSocket-Key的值并加密,其中$key后面的一部分258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串应该是固定的$buf = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);$key = trim(substr($buf,0,strpos($buf,"\r\n")));$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_write($this->users[$k]['socket'],$new_message,strlen($new_message));//对已经握手的client做标志$this->users[$k]['shou']=true;return true;}//解码函数function uncode($str,$key){$mask = array(); $data = ''; $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;}}//与uncode相对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; }//用户加入或client发送信息function send($k,$msg){//将查询字符串解析到第二个参数变量中,以数组的形式保存如:parse_str("name=Bill&age=60",$arr)parse_str($msg,$g);$ar=array();if($g['type']=='add'){//第一次进入添加聊天名字,把姓名保存在相应的users里面$this->users[$k]['name']=$g['ming'];$ar['type']='add';$ar['name']=$g['ming'];$key='all';}else{//发送信息行为,其中$g['key']表示面对大家还是个人,是前段传过来的信息$ar['nrong']=$g['nr'];$key=$g['key'];}//推送信息$this->send1($k,$ar,$key);}//对新加入的client推送已经在线的clientfunction getusers(){$ar=array();foreach($this->users as $k=>$v){$ar[]=array('code'=>$k,'name'=>$v['name']);}return $ar;}//$k 发信息人的socketID $key接受人的 socketID ,根据这个socketID可以查找相应的client进行消息推送,即指定client进行发送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;//如果是add表示新加的clientif($ar['type']=='add'){$ar['type']='madd';$ar['users']=$this->getusers(); //取出所有在线者,用于显示在在线用户列表中$str1 = $this->code(json_encode($ar)); //单独对新client进行编码处理,数据不一样//对新client自己单独发送,因为有些数据是不一样的socket_write($users[$k]['socket'],$str1,strlen($str1));//上面已经对client自己单独发送的,后面就无需再次发送,故unsetunset($users[$k]);}//除了新client外,对其他client进行发送信息。数据量大时,就要考虑延时等问题了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));}}//用户退出向所用client推送信息function send2($k){$this->close($k);$ar['type']='rmove';$ar['nrong']=$k;$this->send1(false,$ar,'all');}//记录日志function e($str){//$path=dirname(__FILE__).'/log.txt';$str=$str."\n";//error_log($str,3,$path);//编码处理echo iconv('utf-8','gbk//IGNORE',$str);}
}
?>
2、client部分
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
<title>HTML5 websocket 网页聊天室 javascript php</title>
<style type="text/css">
body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;}
#ltian,.rin{width:98%; margin:5px auto;}
#ltian{border:1px #ccc solid;overflow-y:auto; overflow-x:hidden; position:relative;}
#ct{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;}
#us{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;}
#us p{padding:3px 5px; color:#08C; line-height:20px; height:20px; cursor:pointer; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
#us p:hover,#us p:active,#us p.ck{background-color:#069; color:#FFF;}
#us p.my:hover,#us p.my:active,#us p.my{color:#333;background-color:transparent;}
button{float:right; width:80px; height:35px; font-size:18px;}
input{width:100%; height:30px; padding:2px; line-height:20px; outline:none; border:solid 1px #CCC;}
.rin p{margin-right:160px;}
.rin span{float:right; padding:6px 5px 0px 5px; position:relative;}
.rin span img{margin:0px 3px; cursor:pointer;}
.rin span form{position:absolute; width:25px; height:25px; overflow:hidden; opacity:0; top:5px; right:5px;}
.rin span input{width:180px; height:25px; margin-left:-160px; cursor:pointer}#ct p{padding:5px; line-height:20px;}
#ct a{color:#069; cursor:pointer;}
#ct span{color:#999; margin-right:10px;}
.c2{color:#999;}
.c3{background-color:#DBE9EC; padding:5px;}
.qp{position:absolute; font-size:12px; color:#666; top:5px; right:130px; text-decoration:none; color:#069;}
#ems{position:absolute; z-index:5; display:none; top:0px; left:0px; max-width:230px; background-color:#F1F1F1; border:solid 1px #CCC; padding:5px;}
#ems img{width:44px; height:44px; border:solid 1px #FFF; cursor:pointer;}
#ems img:hover,#ems img:active{border-color:#A4B7E3;}
#ems a{color:#069; border-radius:2px; display:inline-block; margin:2px 5px; padding:1px 8px; text-decoration:none; background-color:#D5DFFD;}
#ems a:hover,#ems a:active,#ems a.ck{color:#FFF; background-color:#069;}
.tc{text-align:center; margin-top:5px;}
</style>
</head><body>
<div id="ltian"><div id="us" class="jb"></div><div id="ct"></div><a href="javascript:;" class="qp" onClick="this.parentNode.children[1].innerHTML=''">清屏</a>
</div>
<div class="rin"><button id="sd">发送</button><span><img src="http://www.yxsss.com/ui/sk/t.png" title="表情" id="imgbq"><img src="http://www.yxsss.com/ui/sk/e.png" title="上传图片"><form><input type="file" title="上传图片" id="upimg"></form></span><p><input id="nrong"></p>
</div>
<div id="ems"><p></p><p class="tc"></p></div>
<script>
if(typeof(WebSocket)=='undefined'){alert('你的浏览器不支持 WebSocket ,推荐使用Google Chrome 或者 Mozilla Firefox');
}
</script>
<script src="http://www.yxsss.com/ui/p/a.js" type="text/javascript"></script>
<script>
(function(){var key='all',mkey;var users={};var url='ws://127.0.0.1:8000';var so=false,n=false;var lus=A.$('us'),lct=A.$('ct');function st(){n=prompt('请给自己取一个响亮的名字:');n=n.substr(0,16);if(!n){return ; }//创建socket,注意URL的格式:ws://ip:端口so=new WebSocket(url);//握手监听函数so.onopen=function(){//状态为1证明握手成功,然后把client自定义的名字发送过去if(so.readyState==1){so.send('type=add&ming='+n);}}//握手失败或者其他原因连接socket失败,则清除so对象并做相应提示操作so.onclose=function(){so=false;lct.appendChild(A.$$('<p class="c2">退出聊天室</p>'));}//数据接收监听,接收服务器推送过来的信息,返回的数据给msg,然后进行显示so.onmessage=function(msg){eval('var da='+msg.data);var obj=false,c=false;if(da.type=='add'){var obj=A.$$('<p>'+da.name+'</p>');lus.appendChild(obj);cuser(obj,da.code);obj=A.$$('<p><span>['+da.time+']</span>欢迎<a>'+da.name+'</a>加入</p>');c=da.code;}else if(da.type=='madd'){mkey=da.code;da.users.unshift({'code':'all','name':'大家'});for(var i=0;i<da.users.length;i++){var obj=A.$$('<p>'+da.users[i].name+'</p>');lus.appendChild(obj);if(mkey!=da.users[i].code){cuser(obj,da.users[i].code);}else{obj.className='my';document.title=da.users[i].name;}}obj=A.$$('<p><span>['+da.time+']</span>欢迎'+da.name+'加入</p>');users.all.className='ck';}if(obj==false){if(da.type=='rmove'){var obj=A.$$('<p class="c2"><span>['+da.time+']</span>'+users[da.nrong].innerHTML+'退出聊天室</p>');lct.appendChild(obj);users[da.nrong].del();delete users[da.nrong];}else{da.nrong=da.nrong.replace(/{\\(\d+)}/g,function(a,b){return '<img src="sk/'+b+'.gif">';}).replace(/^data\:image\/png;base64\,.{50,}$/i,function(a){return '<img src="'+a+'">';});//da.code 发信息人的codeif(da.code1==mkey){obj=A.$$('<p class="c3"><span>['+da.time+']</span><a>'+users[da.code].innerHTML+'</a>对我说:'+da.nrong+'</p>');c=da.code;}else if(da.code==mkey){if(da.code1!='all')obj=A.$$('<p class="c3"><span>['+da.time+']</span>我对<a>'+users[da.code1].innerHTML+'</a>说:'+da.nrong+'</p>');elseobj=A.$$('<p><span>['+da.time+']</span>我对<a>'+users[da.code1].innerHTML+'</a>说:'+da.nrong+'</p>');c=da.code1;}else if(da.code==false){obj=A.$$('<p><span>['+da.time+']</span>'+da.nrong+'</p>');}else if(da.code1){obj=A.$$('<p><span>['+da.time+']</span><a>'+users[da.code].innerHTML+'</a>对'+users[da.code1].innerHTML+'说:'+da.nrong+'</p>');c=da.code;}}}if(c){obj.children[1].onclick=function(){users[c].onclick();}}lct.appendChild(obj);lct.scrollTop=Math.max(0,lct.scrollHeight-lct.offsetHeight);}}A.$('sd').onclick=function(){if(!so){return st();}var da=A.$('nrong').value.trim();if(da==''){alert('内容不能为空');return false; }A.$('nrong').value='';so.send('nr='+esc(da)+'&key='+key);}A.$('nrong').onkeydown=function(e){var e=e||event;if(e.keyCode==13){A.$('sd').onclick();}}function esc(da){da=da.replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');return encodeURIComponent(da);}function cuser(t,code){users[code]=t;t.onclick=function(){t.parentNode.children.rcss('ck','');t.rcss('','ck');key=code;}}A.$('ltian').style.height=(document.documentElement.clientHeight - 70)+'px';st();var bq=A.$('imgbq'),ems=A.$('ems');var l=80,r=4,c=5,s=0,p=Math.ceil(l/(r*c));var pt='sk/';bq.onclick=function(e){var e=e||event;if(!so){return st();}ems.style.display='block';document.onclick=function(){gb(); }ct();try{e.stopPropagation();}catch(o){}}for(var i=0;i<p;i++){var a=A.$$('<a href="javascript:;">'+(i+1)+'</a>');ems.children[1].appendChild(a);ef(a,i);}ems.children[1].children[0].className='ck';function ct(){var wz=bq.weiz();with(ems.style){top=wz.y-242+'px';left=wz.x+bq.offsetWidth-235+'px';}}function ef(t,i){t.onclick=function(e){var e=e||event;s=i*r*c;ems.children[0].innerHTML='';hh();this.parentNode.children.rcss('ck','');this.rcss('','ck');try{e.stopPropagation();}catch(o){}}}function hh(){var z=Math.min(l,s+r*c);for(var i=s;i<z;i++){var a=A.$$('<img src="'+pt+i+'.gif">');hh1(a,i);ems.children[0].appendChild(a);}ct();}function hh1(t,i){t.onclick=function(e){var e=e||event;A.$('nrong').value+='{\\'+i+'}';if(!e.ctrlKey){gb();}try{e.stopPropagation();}catch(o){}}}function gb(){ems.style.display='';A.$('nrong').focus();document.onclick='';}hh();A.on(window,'resize',function(){A.$('ltian').style.height=(document.documentElement.clientHeight - 70)+'px';ct();}) var fimg=A.$('upimg');var img=new Image();var dw=400,dh=300;A.on(fimg,'change',function(ev){if(!so){st();return false;}if(key=='all'){alert('由于资源限制 发图只能私聊');return false; }var f=ev.target.files[0];if(f.type.match('image.*')){var r = new FileReader();r.onload = function(e){img.setAttribute('src',e.target.result);};r.readAsDataURL(f);}});img.onload=function(){ih=img.height,iw=img.width;if(iw/ih > dw/dh && iw > dw){ih=ih/iw*dw;iw=dw;}else if(ih > dh){iw=iw/ih*dh;ih=dh;}var rc = A.$$('canvas');var ct = rc.getContext('2d');rc.width=iw;rc.height=ih;ct.drawImage(img,0,0,iw,ih);var da=rc.toDataURL();so.send('nr='+esc(da)+'&key='+key);}})();
</script>
</body>
</html>
相关文章:
PHP WebSocket:技术解析与实用指南
本文旨在帮助初学者掌握在PHP中使用WebSocket的关键概念和技术。我们将深入讨论从建立连接、绑定到监听等各方面的操作,并提供易于理解和实践的指导。 一、socket协议的简介 WebSocket是什么,有什么优点 WebSocket是一个持久化的协议,这是…...
K8S实战:Centos7部署Kubernetes1.24.0集群
本人在参考Kubernetes(k8s) 1.24.0版本基于Containerd的集群安装部署部署Kubernetes1.24.0集群时,遇到几个问题,下面将要注意的点罗列在下面: 集群需要配置hosts,如下所示,IP根据自己的实际情况填写,否则在kubeadm in…...
webpack的使用(中)
前言:(承接webpack的使用(上))在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块,其他非 js 后缀名结尾的模块,webpack 默认处理不了,需要调用 loader 加载器才可以正常打包&a…...
matlab经验模式分解的R波检测算法
1、内容简介 略 56-可以交流、咨询、答疑 2、内容说明 略 心血管疾病是威胁人类生命的主要疾病之一,而心电信号(electrocardiogram, ECG) 则是评价心脏功能的主要依据,因此,关于心电信号检测处理的研究一直为各方所…...
win10编译openjdk源码
上篇文章作者在ubuntu系统上实践完成openjdk源码的编译,但是平常使用更多的是window系统,ubuntu上编译出来JDK无法再windows上使用。所以作者又花费了很长时间在windows系统上完成openjdk源码的编译,陆续花费一个月的时间终于完成了编译。 本…...
mysql 自定义函数create function
方便后续查询,做以下记录; 自定义函数是一种与存储过程十分相似的过程式数据库对象, 它与存储过程一样,都是由 SQL 语句和过程式语句组成的代码片段,并且可以被应用程序和其他 SQL 语句调用。 自定义函数与存储过程之间…...
【析】装卸一体化车辆路径问题的自适应并行遗传算法
0 引言 国内外有关 VRPSPD的文献较多,求解目标多以最小化车辆行驶距离为主,但现实中可能存在由租赁费用产生的单次派出成本,需要综合考 虑单次派车成本和配送路径成本。…...
react项目中的redux以及react-router-dom
扫盲知识点: 1 传递自定义事件: <button onClick{(e)>{change(e)}}>获取事件对象e</button> 将事件对象e传递到了change的这个方法中。 2 同时传递自定义事件和参数: <button onClick{(e)>{change(‘我…...
django学习网址
https://www.django.cn/ Django中文网 https://docs.djangoproject.com/zh-hans/5.0/ Django 教程 | 菜鸟教程 (runoob.com) https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui DjangoStarter: 基于Django定制的快速Web开发模板,功能包括:Do…...
@ 代码随想录算法训练营第4周(C语言)|Day21(二叉树)
代码随想录算法训练营第4周(C语言)|Day21(二叉树) Day21、二叉树(包含题目 ● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先 ) 530.二叉搜索树的最小绝对差 题目…...
Android的消息机制--Handler
一、四大组件概述 Android的消息机制是由Handler、Message、MessageQueue,Looper四个类支撑,撑起了Android的消息通讯机制,Android是一个消息驱动系统,由这几个类来驱动消息与事件的执行 Handler: 用来发送消息和处…...
获取用户信息与token理解
获取用户信息和token是在开发Web应用程序时常见的需求,可以通过以下步骤来实现: 用户登录:用户在应用程序中输入用户名和密码进行登录验证。一旦验证成功,应用程序会生成一个唯一的token,并将其返回给客户端。存储tok…...
网络设备和网络软件
文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器,简称网卡。网…...
全连接层是什么
个人浅显的看法: 当前层的每一个神经元,都和下一层的每一个神经元有连接,叫全连接层。 当前层有n个神经元,下一层有m个神经元,则全连接层,当前层的n个神经元和下一层m个神经元都有连接...
JAVA工程师面试专题-《Redis》篇
目录 一、基础 1、Redis 是什么 2、说一下你对redis的理解 3、Redis 为什么这么快? 4、项目中如何使用缓存? 5、为什么使用缓存? 6、Redis key 和value 可以存储最大值分别多是多少? 7、Redis和memcache有什么区别…...
JavaScript BOM
BOM:浏览器对象模型,可以让我们通过js来操作浏览器 window 代表整个浏览器窗口 同时也是页面中的全局对象 Location 代表浏览器地址栏信息 Navigator 代表浏览器信息 可以获取不同的浏览器信息 History 代表浏览器的历史记录 Screen 代表用户的屏幕信…...
uniapp微信小程序-项目实战修改密码
图标是使用uview里面的图标,icfont也可以 以下是所有代码 <template><view><!-- 密码三个 --><view class"password" v-for"(item,index) in userList"><view class"contentuser"><view class&qu…...
linux系统---防火墙拓展
目录 一、iptables 1.基本语法 2.四表五链——重点记忆 2.1四表 2.2五链 2.3总结 3.iptables选项示例 3.1 -Z 清空流量计数 3.2 -P 修改默认规则 3.3 -D 删除规则 3.4 -R 指定编号替换规则 4.白名单 5.通用匹配 6.示例 6.1添加回环网卡 6.2可以访问端口 6.3 主…...
就业的二三事
先说一下当前本人的情况:双非本一,研二在读,一篇图像处理方面的sci一区(二作),日常工作语言为python,有过一段开源实习。要开始准备实习了,发个帖子记录一下自己所收集的信息。 前几…...
Go语言必知必会100问题-05 接口污染
接口污染 在Go语言中,接口是我们设计和编写代码的基石。然而,像很多概念一样,滥用它是不好的。接口污染是指用不必要的抽象来编写代码(刻意使用接口),使得代码更难以理解。这是具有不同习惯,特…...
FastBee商业版本源码获取下载
一、系统功能 系统功能功能说明开源版本商业版本产品管理产品详情、产品物模型、产品分类、设备授权、产品固件支持支持设备管理设备详情、设备分组、设备日志、设备分享、设备实时控制、实时状态、数据监测支持支持物模型管理属性(设备状态和监测数据)…...
Java实战:Spring Boot集成Elasticsearch全文搜索引擎
本文将详细介绍如何在Spring Boot应用程序中集成Elasticsearch全文搜索引擎。我们将探讨Elasticsearch的基本概念,以及如何使用Spring Boot和Spring Data Elasticsearch模块来实现全文搜索功能。此外,我们将通过具体的示例来展示如何在Spring Boot应用程…...
python 进程笔记二(通讯) (概念+示例代码)
1、为什么要掌握进程间通信 Python代码效率由于受制于GIL全局锁限制,多线程不能利用多核CPU来加速,而多进程方式却可以绕过GIL限制, 发挥多CPU加速的优势,达到提高程序的性能的目的。 然而进程间通信却是不得不考虑的问题。 进程不同于线程&a…...
电机控制-----电机极对数,相电感,相电阻以及磁链常数的测量
电机控制-----电机极对数,相电感,相电阻以及磁链常数的测量 我们在做电机控制的时候,拿到一个电机首先要知道它的参数,然后才能进行相应的开发,我这里介绍的是通过平常常用的手段去获得电机的参数:极对数&…...
SQL注入之oracle注入+SQLbypass+sqlmap实战
学习路还很长,切莫轻言放弃! 目录 Oracle数据库介绍 Oracle数据库和MySQL数据库的差别 Oracle数据库注入 SQLbypass姿势 sqlmap工具实战(kali自带) Oracle数据库介绍 Oracle数据库是全球最知名的关系型数据库管理系统(RDBMS)…...
【GPTs分享】GPTs分享之Write For Me
Write For Me 是一个专门定制的GPT版本,旨在为用户提供高质量的文本内容创作服务。它适用于各种写作需求,从商业计划、学术文章到创意故事等。下面是从简介、主要功能、使用案例、优点和局限性几个方面对Write For Me 的详细介绍。 简介 Write For Me …...
css4浮动+清除浮动
浮动 一.常见网页布局1.三种布局方式2.布局准则 二.浮动(float)1.好处2.概念3.三大特性4.使用5.常见网页布局模板6.注意点 三.清除浮动1.why2.本质3.语法4.四种way(后三个都是给父级添加)清除浮动总结 一.常见网页布局 1.三种布局…...
外包干了3个月,技术倒退明显...
先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...
STM32控制数码管从0显示到99
首先 先画电路图吧!打开proteus,导入相关器件,绘制电路图。如下:(记得要保存啊!发现模拟一遍程序就自动退出了,有bug,我是解决不了,所以就是要及时保存,自己重…...
【机器学习算法】KNN鸢尾花种类预测案例和特征预处理。全md文档笔记(已分享,附代码)
本系列文章md笔记(已分享)主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习,伴随浅显易懂的数学知识,让大家掌握机器学习常见算法原理,应用Scikit-learn实现机器学习算法的应用࿰…...
wordpress 更改icon/营销型网站建设推广
最根本的原因就是,走弯路是自然界的一种常态,走直路而是一种非常态。佛学院的一名禅师在上课时把一幅中国地图展开,问:“这幅图上的河流有什么特点?”“都不是直线,而是弯弯的曲线。”“为什么会是这样呢?也就是说,河…...
路由器映射做网站稳定吗/百度快照怎么看
对于使用Eclipse进行日常编程工作的工程师来说,调试是每天都在进行的工作。针对一般意义上的调试过程,相信每一位工程师朋友都熟练掌握了。这里介绍几个调试中比较实用的高级功能。Eclipse远程调试Java自身支持调试功能,并提供了一个简单的调…...
免费wordpress虚拟主机/文大侠seo博客
1. ajax是什么? * asynchronous javascript and xml:异步的js和xml * 它能使用js访问服务器,而且是异步访问! * 服务器给客户端的响应一般是整个页面,一个html完整页面!但在ajax中因为是局部刷新,那么服…...
北海手机网站建设/企业营销咨询
本文实例讲述了PHP使用mkdir创建多级目录的方法。分享给大家供大家参考,具体如下:PHP中使用mkdir()可以创建多级目录,相比之前自己一级一级的创建,这个函数非常好用。下面是php手册上的函数介绍:bool mkdir ( string $…...
网站多少图片怎么做超链接/个人接广告的平台
本文作者是法国里尔大学Inria SequeL团队的博士生,Yannis Flet-Berliac,他在本文中对分层强化学习(HRL)的研究进行了总结,文章首先回顾了强化学习(RL)的基本原理,并阐述了其目前所面…...
七牛云加速WordPress/济南seo网站优化
前段时间遇到了这个问题 也百度了很多 不过还是用自己的方法解决了 一个超级简单的方法 简单到令人发指 由于直接写文本太丑了 所以还是截图吧 嘻嘻嘻 假如有一个这样的数组 (这是假如 可能每个人的数据都不一样哦 但是方法都是可用的) 因为我这个项目功…...