JS+ PHP WebSocket 握手成功

本文介绍了一种利用 PHP 和 WebSocket 实现的内部消息通知系统。该系统通过编写 PHP 脚本来启动 WebSocket 服务器,并结合 JavaScript 客户端进行消息的发送与接收。文章详细展示了如何设置 WebSocket 的连接、消息传递及断开连接等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

公司有一个内部管理系统,需要经常员工对员工实时消息接收,之前是ajax轮循环,因为只是内部使用人员少也并没有出现什么问题。但是心里不爽啊!

测试环境

  • Windows 10

  • PHP5.3+

  • JS

  • Chrome 50+

PHP

    start_ws.php

require('class_ws.php');
$ws = new Ws('127.0.0.1', '8080', 10);
$ws->function['add'] = 'user_add_callback';
$ws->function['send'] = 'send_callback';
$ws->function['close'] = 'close_callback';
$ws->start_server();

//回调函数们
function user_add_callback($ws) {
	$data = count($ws->accept);
 	send_to_all($data, 'num', $ws);
}

function close_callback($ws) {
	$data = count($ws->accept);
	send_to_all($data, 'num', $ws);
}

function send_callback($data, $index, $ws) {
	$data = json_encode(array(
						'text' => $data,
						'user' => $index,
						));
	send_to_all($data, 'text', $ws);
}

function send_to_all($data, $type, $ws){
	$res = array(
			'msg' => $data,
			'type' => $type,
			);
	$res = json_encode($res);
	$res = $ws->frame($res);
	foreach ($ws->accept as $key => $value) {
		socket_write($value, $res, strlen($res));
	}
}

    class_ws.php

<?php
class Ws{
	private $host = '127.0.0.1';
	private $port = 8080;
	private $maxuser = 10;
	public  $accept = array(); //连接的客户端
	private $cycle = array(); //循环连接池
	private $isHand = array(); 
	/*
		接受三个回调函数,分别在新用户连接、有消息到达、用户断开时触发
		function add、function send、function close
	*/
	public $function = array();
	//Constructor
	function __construct($host, $port, $max) {
		$this->host = $host;
		$this->port = $port;
		$this->maxuser = $max;
	}
	//挂起socket
	public function start_server() {
		$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);
		//最多10个人连接,超过的客户端连接会返回WSAECONNREFUSED错误
		socket_listen($this->socket, $this->maxuser); 
		while(TRUE) {
			$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($accept);
					continue;
				}
				$index = array_search($v, $this->accept);
				if ($index === NULL) {
					continue;
				}
				if (!@socket_recv($v, $data, 1024, 0) || !$data) {//没消息的socket就跳过
					$this->close($v);
					continue;
				}
				if (!$this->isHand[$index]) {
					$this->upgrade($v, $data, $index);
					if(!empty($this->function['add'])) {
						call_user_func_array($this->function['add'], array($this));
					}
					continue;
				}
				$data = $this->decode($data);
				if(!empty($this->function['send'])) {
					call_user_func_array($this->function['send'], array($data, $index, $this));
				}
			}
			sleep(1);
		}
	}
	//增加一个初次连接的用户
	private function add_accept($accept) {
		$this->accept[] = $accept;
		$index = array_keys($this->accept);
		$index = end($index);
		$this->isHand[$index] = FALSE;
	}
	//关闭一个连接
	private function close($accept) {
		$index = array_search($accept, $this->accept);
		socket_close($accept);
		unset($this->accept[$index]);
		unset($this->isHand[$index]);
		if(!empty($this->function['close'])) {
			call_user_func_array($this->function['close'], array($this));
		}
	}
	//响应升级协议
	private function upgrade($accept, $data, $index) {
		if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$data,$match)) {
			$key = base64_encode(sha1($match[1] . '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[$index] = TRUE;
		}
	}
	//体力活
	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;
	}
	//体力活
	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;
	}
}
// END ws class

/* End of file class_ws.php */
/* Location: ./server/class_ws.php */

JS

(function(){
	var $ = function(id){return document.getElementById(id) || null;}
	var wsServer = 'ws://127.0.0.1:8080'; 
	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) }; 
	function onOpen(evt) { 
		console.log("连接服务器成功");
		isConnect = true;
	} 
	function onClose(evt) { 
		//console.log("Disconnected"); 
	} 
	function onMessage(evt) {
		var data = JSON.parse(evt.data);
		switch (data.type) {
			case 'text':
				addMsg(data.msg);
				break;
			case 'num' :
				updataUserNum(data.msg);
				break;
		}
		
		console.log('Retrieved data from server: ' + evt.data);
	}
	function onError(evt) { 
		//console.log('Error occured: ' + evt.data); 
	}
	function sendMsg() {
		if(isConnect){
			ws.send($('input').value);
			$('input').value = '';
		}
	}
	function addMsg(msg) {
		msg = JSON.parse(msg);
		var text = '用户' + msg.user + '说:\n' + msg.text + '\n';
		$('message').value += text;
		$('message').scrollTop = $('message').scrollHeight;
	}
	function updataUserNum(msg) {
		$('userNum').innerText = msg;
	}
	$('sub').addEventListener('click',sendMsg,false);
})();

最重要的一步:命令行要运行 D:\xampp\php>php e:web/blog/server/start_ws.php

然后浏览器测试握手,成功咯!

参考博客:http://www.jnecw.com/p/1523#comment-35125


转载于:https://my.oschina.net/deacyn/blog/663588

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值