我是websockets的新手,我正面临着一个我无法解决的问题。我从网上下载了服务器和客户端,并修改了它,使其按我的方式工作。问题是我想存储用户的数据(例如:名称、颜色等)在服务器端。当我想将消息从一个用户传播给所有其他用户时,有时有效,有时无效。有趣的是,它每次读取用户的id都很完美,但有时它会丢失名称或颜色,这些实际上都是同一个类的字段,我看不出有什么区别……请帮助。
服务器: <?php
/**
* Simple server class which manage WebSocket protocols
* @author Sann-Remy Chea <http://srchea.com>
* @license This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
* @version 0.1
*/
class Server {
/**
* The address of the server
* @var String
*/
private $address;
/**
* The port for the master socket
* @var int
*/
private $port;
/**
* The master socket
* @var Resource
*/
private $master;
/**
* The array of sockets (1 socket = 1 client)
* @var Array of resource
*/
private $sockets;
/**
* The array of connected clients
* @var Array of clients
*/
private $clients;
/**
* If true, the server will print messages to the terminal
* @var Boolean
*/
private $verboseMode;
private $maxConnections = 3;
/**
* Server constructor
* @param $address The address IP or hostname of the server (default: 127.0.0.1).
* @param $port The port for the master socket (default: 5001)
*/
function Server($address = '127.0.0.1', $port = 5001, $verboseMode = false) {
$this->console("Server starting...");
$this->address = $address;
$this->port = $port;
$this->verboseMode = $verboseMode;
// socket creation
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
if (!is_resource($socket))
$this->console("socket_create() failed: ".socket_strerror(socket_last_error()), true);
if (!socket_bind($socket, $this->address, $this->port))
$this->console("socket_bind() failed: ".socket_strerror(socket_last_error()), true);
if(!socket_listen($socket, 20))
$this->console("socket_listen() failed: ".socket_strerror(socket_last_error()), true);
$this->master = $socket;
$this->sockets = array($socket);
$this->console("Server started on {$this->address}:{$this->port}");
}
/**
* Create a client object with its associated socket
* @param $socket
*/
private function connect($socket) {
$this->console("Creating client...");
$client = new Client(uniqid(), $socket);
$this->clients[] = $client;
$this->sockets[] = $socket;
$this->console("Client #{$client->getId()} is successfully created!");
}
/**
* Do the handshaking between client and server
* @param $client
* @param $headers
*/
private function handshake($client, $headers) {
$this->console("Getting client WebSocket version...");
if(preg_match("/Sec-WebSocket-Version: (.*)rn/", $headers, $match))
$version = $match[1];
else {
$this->console("The client doesn't support WebSocket");
return false;
}
$this->console("Client WebSocket version is {$version}, (required: 13)");
if($version == 13) {
// Extract header variables
$this->console("Getting headers...");
if(preg_match("/GET (.*) HTTP/", $headers, $match))
$root = $match[1];
if(preg_match("/Host: (.*)rn/", $headers, $match))
$host = $match[1];
if(preg_match("/Origin: (.*)rn/", $headers, $match))
$origin = $match[1];
if(preg_match("/Sec-WebSocket-Key: (.*)rn/", $headers, $match))
$key = $match[1];
$this->console("Client headers are:");
$this->console("t- Root: ".$root);
$this->console("t- Host: ".$host);
$this->console("t- Origin: ".$origin);
$this->console("t- Sec-WebSocket-Key: ".$key);
$this->console("Generating Sec-WebSocket-Accept key...");
$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$acceptKey = base64_encode(sha1($acceptKey, true));
$upgrade = "HTTP/1.1 101 Switching Protocolsrn".
"Upgrade: websocketrn".
"Connection: Upgradern".
"Sec-WebSocket-Accept: $acceptKey".
"rnrn";
$this->console("Sending this response to the client #{$client->getId()}:rn".$upgrade);
socket_write($client->getSocket(), $upgrade);
$client->setHandshake(true);
$this->console("Handshake is successfully done!");
return true;
}
else {
$this->console("WebSocket version 13 required (the client supports version {$version})");
return false;
}
}
/**
* Disconnect a client and close the connection
* @param $socket
*/
private function disconnect($client)
{
$this->console("Disconnecting client #{$client->getId()}");
$i = array_search($client, $this->clients);
$j = array_search($client->getSocket(), $this->sockets);
if($j >= 0)
{
array_splice($this->sockets, $j, 1);
socket_shutdown($client->getSocket(), 2);
socket_close($client->getSocket());
$this->console("Socket closed");
}
if($i >= 0)
array_splice($this->clients, $i, 1);
$this->console("Client #{$client->getId()} disconnected");
/*foreach ($this->clients as $cnt)
$this->send($cnt,$this->usernames[$client->getId()] ." left!");*/
}
/**
* Get the client associated with the socket
* @param $socket
* @return A client object if found, if not false
*/
private function getClientBySocket($socket) {
foreach($this->clients as $client)
if($client->getSocket() == $socket) {
$this->console("Client found");
return $client;
}
return false;
}
/**
* Do an action
* @param $client
* @param $action
*/
private function action($client, $action)
{
$action = $this->unmask($action);
$this->console("Performing action: ".$action);
if($action == "exit" || $action == "quit")
{
$this->console("Killing a child process");
posix_kill($client->getPid(), SIGTERM);
$this->console("Process {$client->getPid()} is killed!");
}
}
/**
* Run the server
*/
public function run()
{
$this->console("Start running...");
while(true)
{
$changed_sockets = $this->sockets;
@socket_select($changed_sockets, $write = NULL, $except = NULL, 1);
foreach($changed_sockets as $socket)
{
if($socket == $this->master)
{
if(($acceptedSocket = socket_accept($this->master)) < 0)
{
$this->console("Socket error: ".socket_strerror(socket_last_error($acceptedSocket)));
}
else
{
$this->connect($acceptedSocket);
}
}
else
{
$this->console("Finding the socket that associated to the client...");
$client = $this->getClientBySocket($socket);
if($client)
{
$this->console("Receiving data from the client");
$data=null;
while($bytes = @socket_recv($socket, $r_data, 2048, MSG_DONTWAIT))
{
$data.=$r_data;
}
if(!$client->getHandshake())
{
$this->console("Doing the handshake");
if($this->handshake($client, $data))
$this->startProcess($client);
}
elseif($bytes === 0)
{
$this->console("disconnecting...");
$this->disconnect($client);
}
else
{
$data = (string)$this->unmask($data);
$data = json_decode($data);
if( $data != "" )$this->console("Got data from user: |" . implode(',',$data) . "|");
// When received data from client
if( $data[0] == "setName" )//set name, color for user with given id
{
$id = $client->getId();
$colors = array( '990099','660099','66ccff','339933','99ff00','ccff00','663300','cc0000' );
//$col = '#'.$colors[ rand(0, sizeof($colors)-1) ];
$col = '#'.$colors[ sizeof( $this->clients ) ];
$this->console( "Setting attributes for user $id | ". $data[1] ." | $col |" );
$client->setName( $data[1] );
$client->setColor( $col );
}
else if( $data != "" )
{
$counter = 0 ;
$id = $client->getId();
$name = null ;
$color = null ;
//while($name == null)
if(1)
{
foreach ($this->clients as $c)
{
if( $c->getId() == $id )
{
$name = $c->getName();
$color = $c->getColor();
break;
}
}
++$counter;
}
foreach ($this->clients as $c)
{
$this->send( $c, json_encode( array(
$name . "[$counter]",
$color,
$data[1] )
) );
}
}
}
}
}
}
}
}
/**
* Start a child process for pushing data
* @param unknown_type $client
*/
private function startProcess($client)
{
$this->console("Start a client process");
$pid = pcntl_fork();
if($pid == -1)
{
die('could not fork');
}
elseif($pid)
{ // process
$client->setPid($pid);
}
else
{
if( sizeof( $this->clients ) > $this->maxConnections )
{
$this->send( $client, json_encode(array("No free slots!")) );
$this->disconnect($client);
}
else
$this->send($client, json_encode($client->getId()));
// we are the child
while(false)
{
//if the client is broken, exit the child process
if($client->exists==false)
{
break;
}
// push something to the client
$seconds = rand(2, 5);
$this->send($client, "I am waiting {$seconds} seconds");
sleep($seconds);
}
}
}
/**
* Send a text to client
* @param $client
* @param $text
*/
private function send($client, $text) {
$this->console("Send '".$text."' to client #{$client->getId()}");
$text = $this->encode($text);
if( socket_write($client->getSocket(), $text, strlen($text)) === FALSE )
{
$client->exists=false; //flag the client as broken
$this->console("Unable to write to client #{$client->getId()}'s socket");
$this->disconnect($client);
}
else
$this->console ("data sent!");
}
/**
* Encode a text for sending to clients via ws://
* @param $text
* @param $messageType
*/
function encode($message, $messageType='text') {
switch ($messageType) {
case 'continuous':
$b1 = 0;
break;
case 'text':
$b1 = 1;
break;
case 'binary':
$b1 = 2;
break;
case 'close':
$b1 = 8;
break;
case 'ping':
$b1 = 9;
break;
case 'pong':
$b1 = 10;
break;
}
$b1 += 128;
$length = strlen($message);
$lengthField = "";
if ($length < 126) {
$b2 = $length;
} elseif ($length <= 65536) {
$b2 = 126;
$hexLength = dechex($length);
//$this->stdout("Hex Length: $hexLength");
if (strlen($hexLength)%2 == 1) {
$hexLength = '0' . $hexLength;
}
$n = strlen($hexLength) - 2;
for ($i = $n; $i >= 0; $i=$i-2) {
$lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
}
while (strlen($lengthField) < 2) {
$lengthField = chr(0) . $lengthField;
}
} else {
$b2 = 127;
$hexLength = dechex($length);
if (strlen($hexLength)%2 == 1) {
$hexLength = '0' . $hexLength;
}
$n = strlen($hexLength) - 2;
for ($i = $n; $i >= 0; $i=$i-2) {
$lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
}
while (strlen($lengthField) < 8) {
$lengthField = chr(0) . $lengthField;
}
}
return chr($b1) . chr($b2) . $lengthField . $message;
}
/**
* Unmask a received payload
* @param $buffer
*/
private function unmask($payload) {
$length = ord($payload[1]) & 127;
if($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
}
elseif($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
}
else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i%4];
}
return $text;
}
/**
* Print a text to the terminal
* @param $text the text to display
* @param $exit if true, the process will exit
*/
private function console($text, $exit = false) {
$text = date('[Y-m-d H:i:s] ').$text."rn";
if($exit)
die($text);
if($this->verboseMode)
echo $text;
}
}
error_reporting(E_ALL);
require_once 'client.php';
set_time_limit(0);
// variables
$address = '127.0.0.1';
$port = 5001;
$verboseMode = true;
$server = new Server($address, $port, $verboseMode);
$server->run();
?>
客户: <?php
/**
* Define a Client object
* @author Sann-Remy Chea <http://srchea.com>
* @license This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
* @version 0.1
*/
class Client {
private $id;
private $socket;
private $handshake;
private $pid;
private $name;
private $color;
public $exists=true; //to check if client is broken
function Client($id, $socket) {
$this->id = $id;
$this->socket = $socket;
$this->handshake = false;
$this->pid = null;
}
public function setName( $n )
{
$this->name = $n;
}
public function getName()
{
return $this->name;
}
public function setColor( $c )
{
$this->color = $c;
}
public function getColor()
{
return $this->color;
}
public function getId() {
return $this->id;
}
public function getSocket() {
return $this->socket;
}
public function getHandshake() {
return $this->handshake;
}
public function getPid() {
return $this->pid;
}
public function setId($id) {
$this->id = $id;
}
public function setSocket($socket) {
$this->socket = $socket;
}
public function setHandshake($handshake) {
$this->handshake = $handshake;
}
public function setPid($pid) {
$this->pid = $pid;
}
}
?>
指数:
<!DOCTYPE html>
<html lang="pl">
<head>
<title>Websockets</title>
<meta name="BB" content="" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta charset="UTF-8" />
<style>
html, body
{
padding: 0;
margin: 0;
}
#out
{
width:840px;
height:500px;
mragin-right: -20px;
color:#FFF;
font-family: Courier New;
overflow-x: hidden;
overflow-y: auto;
}
#outwrap
{
width:800px;
height:500px;
margin:40px auto 0 auto;
display: block;
border: 1px solid #F00;
background-color: #000;
padding: 10px;
overflow:hidden;
}
#field
{
width:820px;
margin:10px auto;
display: block;
background-color: #000;
color: #FFF;
height: 50px;
}
#btn
{
background-color: #000;
margin:0 auto;
display:block;
width:100px;
height:50px;
font-size:20px;
border-radius: 10px;
}
#btn:hover
{
background-color: #666;
}
</style>
<script>
"use strict";
function $(id){return document.getElementById(id);}
document.addEventListener("DOMContentLoaded",function(){
var name = "";
while( name === "" || name === null )name = window.prompt( "Name" );
var myId = '';
$("field").value = "";
$("field").focus();
//var colors = ['990099','660099','66ccff','339933','99ff00','ccff00','663300','cc0000'];
//var myColor = '#' + colors[Math.floor( Math.random()*colors.length )] ;
//$("btn").style.color = myColor ;
var ws = new WebSocket('ws://127.0.0.1:5001');
ws.onopen = function(msg) {
write( "connected as "+ name +"!" );
ws.send( JSON.stringify( new Array( "setName",name ) ) );
$("field").focus();
//ws.send( name+": connected" );
//write('Connection successfully opened (readyState ' + this.readyState+')');
};
ws.onclose = function(msg) {
if(this.readyState === 2)write( "disconnected!" );
/*write(
'Closing... The connection is going throught'
+ 'the closing handshake (readyState '+this.readyState+')'
);*/
else if(this.readyState === 3)write( "disconnected!" );
/*write(
'Connection closed... The connection has been closed'
+ 'or could not be opened (readyState '+this.readyState+')'
);*/
else write( "disconnected!" );
//write('Connection closed... (unhandled readyState '+this.readyState+')');
};
ws.onerror = function(event) {
if(typeof (event.data) != 'undefined')write( "ERROR:" + event.data );
};
ws.onmessage = function(e){
var msg = JSON.parse(e.data);
msg = msg.toString().split(",");
if( msg.length == 1 )myId = msg.toString();
else
{
write( "<p style='color:"+ msg[1] +";'>"+ msg[0] +": "+ msg[2] +"</p>" );
}
console.log( "Server -> " + msg );
//write( "inc: " + msg );
};
$("field").addEventListener("keydown",function(e){
if( e.keyCode == 13 )
{
$("btn").click();
}
});
$("btn").addEventListener("click",function(){
if( ws.readyState === 1 )
ws.send( JSON.stringify( new Array( myId, $("field").value ) ) );
else write( "Couldn't send the message..." );
$("field").value = "";
$("field").focus();
});
window.onbeforeunload = function()
{
//ws.send( name+": disconnected" );
ws.close();
}
function write( info )
{
//$("out").innerHTML = info + "<br />" + $("out").innerHTML;
var p = document.createElement( "p" );
p.innerHTML = info ;
$("out").appendChild( p );
$("out").scrollTop = $("out").scrollHeight;
}
});
</script>
</head>
<body>
<div id="outwrap">
<div id="out"></div>
</div>
<input type="text" id="field" />
<input type="button" value="SEND" id="btn" />
</body>
</html>
如果你在第278行(server.php)取消注释,并在下一行注释:如果我说的是循环无限…
你犯了一个常见的(初学者?)错误,假设从TCP套接字写入/读取发送/接收的字节数与调用被告知发送/接收的字节数相同。不一定是的情况。
检查这些函数(socket_recv()
/socket_send()
)返回的值并循环直到所有数据发送/接收完毕。