什么TCP/IP協(xié)議呀,什么WebSocket原理呀,到處都能搜到,我就不重復(fù)了;
因?yàn)樽罱诟憧缙脚_(tái)的游戲,這時(shí)要即時(shí)通訊怎么辦?當(dāng)然WebSocket!
so! 怎么用它發(fā)送消息呢?看代碼:
客戶端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<input type="text" placeholder="說(shuō)點(diǎn)啥?">
<button onclick="sendMsg()">發(fā)送</button>
</body>
<script>
var input = document.getElementsByTagName('input')[0];
//請(qǐng)求服務(wù)器握手,端口和地址根據(jù)自己需要改動(dòng),沒(méi)說(shuō)一定是1935,只要與服務(wù)端一致就好
var ws = new WebSocket("ws://127.0.0.1:1935");
name = prompt('來(lái)個(gè)名字吧:');
//握手成功觸發(fā)
ws.onopen = function(){
console.log("握手成功");
if(ws.readyState==1){
ws.send(name+"加入房間");
}
};
//服務(wù)器發(fā)送消息,觸發(fā)
ws.onmessage = function(e){
//e就是服務(wù)器發(fā)送的信息
console.log( JSON.parse(e.data) );
//接下來(lái)愛(ài)干嘛干嘛去、
//...code...
};
//出錯(cuò)時(shí)觸發(fā)方法
ws.onerror = function(){
console.log("error");
};
function sendMsg(){
var msg = input.value;
//發(fā)送消息給服務(wù)器
ws.send(name+' : '+msg);
input.value = '';
}
</script>
</html>
沒(méi)錯(cuò),就是這么簡(jiǎn)單,50行不到的代碼就OK!
服務(wù)器端
服務(wù)器語(yǔ)言用的PHP,用node.js會(huì)簡(jiǎn)單許多,而且PHP也不太適合拿來(lái)干這個(gè),不過(guò)在流量不大的情況,還是沒(méi)問(wèn)題的;
<?php
error_reporting(E_ALL);
ob_implicit_flush();
$sk=new Sock('127.0.0.1',1935);
$sk->run();
class Sock{
private $sockets;//socket數(shù)組
private $users;
private $master;
public function __construct($address, $port){
//開(kāi)啟端口,并監(jiān)聽(tīng)
$this->master=$this->WebSocket($address, $port);
$this->sockets=array('s'=>$this->master);
}
public function run(){
while(true){
$changes=$this->sockets;
socket_select($changes,$write=NULL,$except=NULL,NULL);
//遍歷連接
foreach($changes as $sock){
if($sock==$this->master){
//接受一個(gè)Socket連接
$client=socket_accept($this->master);
//把該連接存到數(shù)組
$this->sockets[]=$client;
//給該連接一個(gè)識(shí)別符
$this->users[]=array(
'socket'=>$client,
'isShakeHand'=>false
);
}else{
//從連接里拿出請(qǐng)求信息
$len = socket_recv($sock,$buffer,2048,0);
$k = $this->search($sock);
if($len<7){
$this->close($sock);
continue;
}
if(!$this->users[$k]['isShakeHand']){
//先握手
$this->woshou($k,$buffer);
}else{
//握手后,處理請(qǐng)求數(shù)據(jù)
$buffer = $this->uncode($buffer);
//推送給所有客戶端
$this->send($k,$buffer);
}
}
}
}
}
//關(guān)閉連接
private function close($sock){
$k=array_search($sock, $this->sockets);
socket_close($sock);
unset($this->sockets[$k]);
unset($this->users[$k]);
$this->e("連接:$k 關(guān)閉");
}
//獲取發(fā)送socket的客戶端
private function search($sock){
foreach ($this->users as $k=>$v){
if($sock==$v['socket'])
return $k;
}
return false;
}
//建立端口監(jiān)聽(tīng)
private function WebSocket($address,$port){
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($server, $address, $port);
socket_listen($server);
$this->e('服務(wù)開(kāi)啟 : '.date('Y-m-d H:i:s'));
$this->e('開(kāi)始監(jiān)聽(tīng) : '.$address.' ,端口 : '.$port);
return $server;
}
//php的websocket特殊處理,比起node.js哎,說(shuō)多了都是淚
private function woshou($k,$buffer){
$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));
$this->users[$k]['isShakeHand']=true;
return true;
}
//幀數(shù)據(jù)解碼,php在流淚
private function uncode($str){
$mask = array();
$data = '';
$msg = unpack('H*',$str);
$head = substr($msg[1],0,2);
if (hexdec($head{1}) === 8) {
$data = false;
}else if (hexdec($head{1}) === 1){
$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;
$e = strlen($msg[1])-2;
$n = 0;
for ($i=$s; $i<= $e; $i+= 2) {
$data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2)));
$n++;
}
}
return $data;
}
//幀數(shù)據(jù)編碼(發(fā)回客戶端用),php還是在流淚
private function code($msg){
$msg = preg_replace(array('/\r$/','/\n$/','/\r\n$/',), '', $msg);
$frame = array();
$frame[0] = '81';
$len = strlen($msg);
$frame[1] = $len<16?'0'.dechex($len):dechex($len);
$frame[2] = $this->ord_hex($msg);
$data = implode('',$frame);
return pack("H*", $data);
}
private function ord_hex($data) {
$msg = '';
$l = strlen($data);
for ($i= 0; $i<$l; $i++) {
$msg .= dechex(ord($data{$i}));
}
return $msg;
}
//編碼后發(fā)回到客戶端
private function send($k,$msg){
$msg = json_encode($msg);
$msg = $this->code($msg);
$this->e($msg);
foreach($this->users as $v){
//發(fā)送到客戶端
socket_write($v['socket'],$msg,strlen($msg));
}
}
//記錄到log
private function e($str){
$path=dirname(__FILE__).'/log.txt';
$str=$str."\n";
error_log($str,3,$path);
}
}
?>
測(cè)試
-
用命令行運(yùn)行php
Paste_Image.png -
開(kāi)始監(jiān)聽(tīng)端口
Paste_Image.png -
運(yùn)行網(wǎng)頁(yè),F(xiàn)12,一切正常
Paste_Image.png -
第二個(gè)頁(yè)面,通訊成功
Paste_Image.png



