WebSocket数据包协议详解 http://www.cnblogs.com/smark/archive/2012/11/26/2789812.html
Socket编程(4)TCP粘包问题及解决方案 http://www.cnblogs.com/QG-whz/p/5537447.html
websocket协议详解及数据处理实例 https://www.xxling.com/blog/article/3103.aspx
含有接收分帧处理的websocket,符合前端一直发送后端一直接收数据 https://github.com/ghedipunk/PHP-Websockets
users.php
<?php
class WebSocketUser {
public $socket;
public $id;
public $headers = array();
public $handshake = false;
public $handlingPartialPacket = false;
public $partialBuffer = "";
public $sendingContinuous = false;
public $partialMessage = "";
public $hasSentClose = false;
public $clientFileName ;
public $serverFileName ;
public $fileHandler ;
public $fileSize ;
public $recLength = 0 ;
function __construct($id, $socket) {
$this->id = $id;
$this->socket = $socket;
}
}
websockets.php
<?php
//require_once('./daemonize.php');
require_once('./users.php');
abstract class WebSocketServer {
protected $userClass = 'WebSocketUser'; // redefine this if you want a custom user class. The custom user class should inherit from WebSocketUser.
protected $maxBufferSize;
protected $master;
protected $sockets = array();
protected $users = array();
protected $heldMessages = array();
protected $interactive = true;
protected $headerOriginRequired = false;
protected $headerSecWebSocketProtocolRequired = false;
protected $headerSecWebSocketExtensionsRequired = false;
function __construct($addr, $port, $bufferLength = 1024) {
$this->maxBufferSize = $bufferLength * 1024 + 8;
$this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Failed: socket_create()");
socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("Failed: socket_option()");
socket_bind($this->master, $addr, $port) or die("Failed: socket_bind()");
socket_listen($this->master,20) or die("Failed: socket_listen()");
$this->sockets['m'] = $this->master;
$this->stdout("Server started\nListening on: $addr:$port\nMaster socket: ".$this->master);
}
abstract protected function process($user,$message); // Called immediately when the data is recieved.
abstract protected function connected($user); // Called after the handshake response is sent to the client.
abstract protected function closed($user); // Called after the connection is closed.
protected function connecting($user) {
// Override to handle a connecting user, after the instance of the User is created, but before
// the handshake has completed.
}
protected function send($user, $message) {
if ($user->handshake) {
$message = $this->frame($message,$user);
$result = @socket_write($user->socket, $message, strlen($message));
}
else {
// User has not yet performed their handshake. Store for sending later.
$holdingMessage = array('user' => $user, 'message' => $message);
$this->heldMessages[] = $holdingMessage;
}
}
protected function tick() {
// Override this for any process that should happen periodically. Will happen at least once
// per second, but possibly more often.
}
protected function _tick() {
// Core maintenance processes, such as retrying failed messages.
foreach ($this->heldMessages as $key => $hm) {
$found = false;
foreach ($this->users as $currentUser) {
if ($hm['user']->socket == $currentUser->socket) {
$found = true;
if ($currentUser->handshake) {
unset($this->heldMessages[$key]);
$this->send($currentUser, $hm['message']);
}
}
}
if (!$found) {
// If they're no longer in the list of connected users, drop the message.
unset($this->heldMessages[$key]);
}
}
}
/**
* Main processing loop
*/
public function run() {
while(true) {
if (empty($this->sockets)) {
$this->sockets['m'] = $this->master;
}
$read = $this->sockets;
$write = $except = null;
$this->_tick();
$this->tick();
@socket_select($read,$write,$except,1);
foreach ($read as $socket) {
if ($socket == $this->master) {
$client = socket_accept($socket);
if ($client < 0) {
$this->stderr("Failed: socket_accept()");
continue;
}
else {
$this->connect($client);
$this->stdout("Client connected. " . $client);
}
}
else {
$numBytes = @socket_recv($socket, $buffer, $this->maxBufferSize, 0);
if ($numBytes === false) {
$sockErrNo = socket_last_error($socket);
switch ($sockErrNo)
{
case 102: // ENETRESET -- Network dropped connection because of reset
case 103: // ECONNABORTED -- Software caused connection abort
case 104: // ECONNRESET -- Connection reset by peer
case 108: // ESHUTDOWN -- Cannot send after transport endpoint shutdown -- probably more of an error on our part, if we're trying to write after the socket is closed. Probably not a critical error, though.
case 110: // ETIMEDOUT -- Connection timed out
case 111: // ECONNREFUSED -- Connection refused -- We shouldn't see this one, since we're listening... Still not a critical error.
case 112: // EHOSTDOWN -- Host is down -- Again, we shouldn't see this, and again, not critical because it's just one connection and we still want to listen to/for others.
case 113: // EHOSTUNREACH -- No route to host
case 121: // EREMOTEIO -- Rempte I/O error -- Their hard drive just blew up.
case 125: // ECANCELED -- Operation canceled
$this->stderr("Unusual disconnect on socket " . $socket);
$this->disconnect($socket, true, $sockErrNo); // disconnect before clearing error, in case someone with their own implementation wants to check for error conditions on the socket.
break;
default:
$this->stderr('Socket error: ' . socket_strerror($sockErrNo));
}
}
elseif ($numBytes == 0) {
$this->disconnect($socket);
$this->stderr("Client disconnected. TCP connection lost: " . $socket);
}
else {
$user = $this->getUserBySocket($socket);
if (!$user->handshake) {
$tmp = str_replace("\r", '', $buffer);
if (strpos($tmp, "\n\n") === false ) {
continue; // If the client has not finished sending the header, then wait before sending our upgrade response.
}
$this->doHandshake($user,$buffer);
}
else {
//split packet into frame and send it to deframe
$this->split_packet($numBytes,$buffer, $user);
}
}
}
}
}
}
protected function connect($socket) {
$user = new $this->userClass(uniqid('u'), $socket);
$this->users[$user->id] = $user;
$this->sockets[$user->id] = $socket;
$this->connecting($user);
}
protected function disconnect($socket, $triggerClosed = true, $sockErrNo = null) {
$disconnectedUser = $this->getUserBySocket($socket);
if ($disconnectedUser !== null) {
unset($this->users[$disconnectedUser->id]);
if (array_key_exists($disconnectedUser->id, $this->sockets)) {
unset($this->sockets[$disconnectedUser->id]);
}
if (!is_null($sockErrNo)) {
socket_clear_error($socket);
}
if ($triggerClosed) {
$this->stdout("Client disconnected. ".$disconnectedUser->socket);
$this->closed($disconnectedUser);
socket_close($disconnectedUser->socket);
}
else {
$message = $this->frame('', $disconnectedUser, 'close');
@socket_write($disconnectedUser->socket, $message, strlen($message));
}
}
}
protected function doHandshake($user, $buffer) {
$magicGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
$headers = array();
$lines = explode("\n",$buffer);
foreach ($lines as $line) {
if (strpos($line,":") !== false) {
$header = explode(":",$line,2);
$headers[strtolower(trim($header[0]))] = trim($header[1]);
}
elseif (stripos($line,"get ") !== false) {
preg_match("/GET (.*) HTTP/i", $buffer, $reqResource);
$headers['get'] = trim($reqResource[1]);
}
}
if (isset($headers['get'])) {
$user->requestedResource = $headers['get'];
}
else {
// todo: fail the connection
$handshakeResponse = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
}
if (!isset($headers['host']) || !$this->checkHost($headers['host'])) {
$handshakeResponse = "HTTP/1.1 400 Bad Request";
}
if (!isset($headers['upgrade']) || strtolower($headers['upgrade']) != 'websocket') {
$handshakeResponse = "HTTP/1.1 400 Bad Request";
}
if (!isset($headers['connection']) || strpos(strtolower($headers['connection']), 'upgrade') === FALSE) {
$handshakeResponse = "HTTP/1.1 400 Bad Request";
}
if (!isset($headers['sec-websocket-key'])) {
$handshakeResponse = "HTTP/1.1 400 Bad Request";
}
else {
}
if (!isset($headers['sec-websocket-version']) || strtolower($headers['sec-websocket-version']) != 13) {
$handshakeResponse = "HTTP/1.1 426 Upgrade Required\r\nSec-WebSocketVersion: 13";
}
if (($this->headerOriginRequired && !isset($headers['origin']) ) || ($this->headerOriginRequired && !$this->checkOrigin($headers['origin']))) {
$handshakeResponse = "HTTP/1.1 403 Forbidden";
}
if (($this->headerSecWebSocketProtocolRequired && !isset($headers['sec-websocket-protocol'])) || ($this->headerSecWebSocketProtocolRequired && !$this->checkWebsocProtocol($headers['sec-websocket-protocol']))) {
$handshakeResponse = "HTTP/1.1 400 Bad Request";
}
if (($this->headerSecWebSocketExtensionsRequired && !isset($headers['sec-websocket-extensions'])) || ($this->headerSecWebSocketExtensionsRequired && !$this->checkWebsocExtensions($headers['sec-websocket-extensions']))) {
$handshakeResponse = "HTTP/1.1 400 Bad Request";
}
// Done verifying the _required_ headers and optionally required headers.
if (isset($handshakeResponse)) {
socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
$this->disconnect($user->socket);
return;
}
$user->headers = $headers;
$user->handshake = $buffer;
$webSocketKeyHash = sha1($headers['sec-websocket-key'] . $magicGUID);
$rawToken = "";
for ($i = 0; $i < 20; $i++) {
$rawToken .= chr(hexdec(substr($webSocketKeyHash,$i*2, 2)));
}
$handshakeToken = base64_encode($rawToken) . "\r\n";
$subProtocol = (isset($headers['sec-websocket-protocol'])) ? $this->processProtocol($headers['sec-websocket-protocol']) : "";
$extensions = (isset($headers['sec-websocket-extensions'])) ? $this->processExtensions($headers['sec-websocket-extensions']) : "";
$handshakeResponse = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $handshakeToken$subProtocol$extensions\r\n";
socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
$this->connected($user);
}
protected function checkHost($hostName) {
return true; // Override and return false if the host is not one that you would expect.
// Ex: You only want to accept hosts from the my-domain.com domain,
// but you receive a host from malicious-site.com instead.
}
protected function checkOrigin($origin) {
return true; // Override and return false if the origin is not one that you would expect.
}
protected function checkWebsocProtocol($protocol) {
return true; // Override and return false if a protocol is not found that you would expect.
}
protected function checkWebsocExtensions($extensions) {
return true; // Override and return false if an extension is not found that you would expect.
}
protected function processProtocol($protocol) {
return ""; // return either "Sec-WebSocket-Protocol: SelectedProtocolFromClientList\r\n" or return an empty string.
// The carriage return/newline combo must appear at the end of a non-empty string, and must not
// appear at the beginning of the string nor in an otherwise empty string, or it will be considered part of
// the response body, which will trigger an error in the client as it will not be formatted correctly.
}
protected function processExtensions($extensions) {
return ""; // return either "Sec-WebSocket-Extensions: SelectedExtensions\r\n" or return an empty string.
}
protected function getUserBySocket($socket) {
foreach ($this->users as $user) {
if ($user->socket == $socket) {
return $user;
}
}
return null;
}
public function stdout($message) {
if ($this->interactive) {
echo "$message\n";
}
}
public function stderr($message) {
if ($this->interactive) {
echo "$message\n";
}
}
protected function frame($message, $user, $messageType='text', $messageContinues=false) {
switch ($messageType) {
case 'continuous':
$b1 = 0;
break;
case 'text':
$b1 = ($user->sendingContinuous) ? 0 : 1;
break;
case 'binary':
$b1 = ($user->sendingContinuous) ? 0 : 2;
break;
case 'close':
$b1 = 8;
break;
case 'ping':
$b1 = 9;
break;
case 'pong':
$b1 = 10;
break;
}
if ($messageContinues) {
$user->sendingContinuous = true;
}
else {
$b1 += 128;
$user->sendingContinuous = false;
}
$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;
}
//check packet if he have more than one frame and process each frame individually
protected function split_packet($length,$packet, $user) {
//add PartialPacket and calculate the new $length
if ($user->handlingPartialPacket) {
$packet = $user->partialBuffer . $packet;
$user->handlingPartialPacket = false;
$length=strlen($packet);
}
$fullpacket=$packet;
$frame_pos=0;
$frame_id=1;
while($frame_pos<$length) {
$headers = $this->extractHeaders($packet);
$headers_size = $this->calcoffset($headers);
$framesize=$headers['length']+$headers_size;
//split frame from packet and process it
$frame=substr($fullpacket,$frame_pos,$framesize);
if (($message = $this->deframe($frame, $user,$headers)) !== FALSE) {
if ($user->hasSentClose) {
$this->disconnect($user->socket);
} else {
// if ((preg_match('//u', $message)) || ($headers['opcode']==2)) {
//$this->stdout("Text msg encoded UTF-8 or Binary msg\n".$message);
$this->process($user, $message);
/*} else {
$this->stderr("not UTF-8\n");
}*/
}
}
//get the new position also modify packet data
$frame_pos+=$framesize;
$packet=substr($fullpacket,$frame_pos);
$frame_id++;
}
}
protected function calcoffset($headers) {
$offset = 2;
if ($headers['hasmask']) {
$offset += 4;
}
if ($headers['length'] > 65535) {
$offset += 8;
} elseif ($headers['length'] > 125) {
$offset += 2;
}
return $offset;
}
protected function deframe($message, &$user) {
//echo $this->strtohex($message);
$headers = $this->extractHeaders($message);
$pongReply = false;
$willClose = false;
switch($headers['opcode']) {
case 0:
case 1:
case 2:
break;
case 8:
// todo: close the connection
$user->hasSentClose = true;
return "";
case 9:
$pongReply = true;
case 10:
break;
default:
//$this->disconnect($user); // todo: fail connection
$willClose = true;
break;
}
/* Deal by split_packet() as now deframe() do only one frame at a time.
if ($user->handlingPartialPacket) {
$message = $user->partialBuffer . $message;
$user->handlingPartialPacket = false;
return $this->deframe($message, $user);
}
*/
if ($this->checkRSVBits($headers,$user)) {
return false;
}
if ($willClose) {
// todo: fail the connection
return false;
}
$payload = $user->partialMessage . $this->extractPayload($message,$headers);
if ($pongReply) {
$reply = $this->frame($payload,$user,'pong');
socket_write($user->socket,$reply,strlen($reply));
return false;
}
if ($headers['length'] > strlen($this->applyMask($headers,$payload))) {
$user->handlingPartialPacket = true;
$user->partialBuffer = $message;
return false;
}
$payload = $this->applyMask($headers,$payload);
if ($headers['fin']) {
$user->partialMessage = "";
return $payload;
}
$user->partialMessage = $payload;
return false;
}
protected function extractHeaders($message) {
$header = array('fin' => $message[0] & chr(128),
'rsv1' => $message[0] & chr(64),
'rsv2' => $message[0] & chr(32),
'rsv3' => $message[0] & chr(16),
'opcode' => ord($message[0]) & 15,
'hasmask' => $message[1] & chr(128),
'length' => 0,
'mask' => "");
$header['length'] = (ord($message[1]) >= 128) ? ord($message[1]) - 128 : ord($message[1]);
if ($header['length'] == 126) {
if ($header['hasmask']) {
$header['mask'] = $message[4] . $message[5] . $message[6] . $message[7];
}
$header['length'] = ord($message[2]) * 256
+ ord($message[3]);
}
elseif ($header['length'] == 127) {
if ($header['hasmask']) {
$header['mask'] = $message[10] . $message[11] . $message[12] . $message[13];
}
$header['length'] = ord($message[2]) * 65536 * 65536 * 65536 * 256
+ ord($message[3]) * 65536 * 65536 * 65536
+ ord($message[4]) * 65536 * 65536 * 256
+ ord($message[5]) * 65536 * 65536
+ ord($message[6]) * 65536 * 256
+ ord($message[7]) * 65536
+ ord($message[8]) * 256
+ ord($message[9]);
}
elseif ($header['hasmask']) {
$header['mask'] = $message[2] . $message[3] . $message[4] . $message[5];
}
//echo $this->strtohex($message);
//$this->printHeaders($header);
return $header;
}
protected function extractPayload($message,$headers) {
$offset = 2;
if ($headers['hasmask']) {
$offset += 4;
}
if ($headers['length'] > 65535) {
$offset += 8;
}
elseif ($headers['length'] > 125) {
$offset += 2;
}
return substr($message,$offset);
}
protected function applyMask($headers,$payload) {
$effectiveMask = "";
if ($headers['hasmask']) {
$mask = $headers['mask'];
}
else {
return $payload;
}
while (strlen($effectiveMask) < strlen($payload)) {
$effectiveMask .= $mask;
}
while (strlen($effectiveMask) > strlen($payload)) {
$effectiveMask = substr($effectiveMask,0,-1);
}
return $effectiveMask ^ $payload;
}
protected function checkRSVBits($headers,$user) { // override this method if you are using an extension where the RSV bits are used.
if (ord($headers['rsv1']) + ord($headers['rsv2']) + ord($headers['rsv3']) > 0) {
//$this->disconnect($user); // todo: fail connection
return true;
}
return false;
}
protected function strtohex($str) {
$strout = "";
for ($i = 0; $i < strlen($str); $i++) {
$strout .= (ord($str[$i])<16) ? "0" . dechex(ord($str[$i])) : dechex(ord($str[$i]));
$strout .= " ";
if ($i%32 == 7) {
$strout .= ": ";
}
if ($i%32 == 15) {
$strout .= ": ";
}
if ($i%32 == 23) {
$strout .= ": ";
}
if ($i%32 == 31) {
$strout .= "\n";
}
}
return $strout . "\n";
}
protected function printHeaders($headers) {
echo "Array\n(\n";
foreach ($headers as $key => $value) {
if ($key == 'length' || $key == 'opcode') {
echo "\t[$key] => $value\n\n";
}
else {
echo "\t[$key] => ".$this->strtohex($value)."\n";
}
}
echo ")\n";
}
}
testwebsock.php
#!/usr/bin/env php
<?php
require_once('./websockets.php');
class echoServer extends WebSocketServer {
//protected $maxBufferSize = 1048576; //1MB... overkill for an echo server, but potentially plausible for other applications.
protected $starttime ;
protected function process ($user, $message) {
//$this->send($user,$message);
if ('send_type' == substr($message,0,9)){//表示发送的内容是包含send_type的字符串
echo $message."\n" ;
$this->send1($user,$message);
}else{
if (false !== strpos($message, 'filename=')) {
$this ->starttime = explode(' ',microtime());
parse_str($message,$msg);//将字符串分割成数组
$user ->clientFileName = $msg['filename'];
$user ->fileSize = $msg['filesize'];
$user ->serverFileName = $this->saveFile($user ->clientFileName);
$user ->fileHandler = fopen($user ->serverFileName,"a+"); //打开文件准备以追加的方式
chown($user ->serverFileName,'apache') ; //修改文件所属用户
chgrp($user ->serverFileName,'apache') ; //修改文件所属租组
} else if(!empty($user ->fileHandler) && !empty($user ->serverFileName)){
$this ->saveFileContent($user ->fileHandler, $message);
$user ->recLength += strlen($message);
//$this->send($user,$user ->recLength);
$this->send($user,json_encode(array('recLength' => $user ->recLength ,'serverFN' => basename($user ->serverFileName))));
if($user ->recLength >= $user ->fileSize){
fclose($user ->fileHandler); //关闭文件
/* echo "fclose file recLength:".$user ->recLength."\n" ;
$endtime = explode(' ',microtime());
$thistime = $endtime[0]+$endtime[1]-($this ->starttime[0]+$this ->starttime[1]);
$thistime = round($thistime,3);
echo "run time long: ".$thistime." seconds".time();
$this->disconnect($user) ;*/
}
}
}
}
protected function connected ($user) {
// Do nothing: This is just an echo server, there's no need to track the user.
// However, if we did care about the users, we would probably have a cookie to
// parse at this step, would be looking them up in permanent storage, etc.
}
protected function closed ($user) {
// Do nothing: This is where cleanup would go, in case the user had any sort of
// open files or other objects associated with them. This runs after the socket
// has been closed, so there is no need to clean up the socket itself here.
}
//用户加入或client发送信息
protected function send1($user,$msg){
//将查询字符串解析到第二个参数变量中,以数组的形式保存如:parse_str("name=Bill&age=60",$arr)
parse_str($msg,$g);
$ar=array();
if(isset($g['status'])&&isset($g['command'])){
$ar['command']=$g['command'];
$ar['status']=$g['status'];
$ar['swdid']=$g['swdid'];
}else if(isset($g['command'])){
$ar['command']=$g['command'];
$ar['swdid']=$g['swdid'];
}
//推送信息
$this->send2($user,$ar);
}
//$k 发信息人的socketID $key接受人的 socketID ,根据这个socketID可以查找相应的client进行消息推送,即指定client进行发送
protected function send2($user,$ar){
$ar['time']=date('m-d H:i:s');
$users=$this->users;
//给除了自己以外的用户发消息
foreach($users as $k => $v){
if($v != $user){
$this->send($v,json_encode($ar));
}
}
}
//生成唯一uuid文件名称
protected function uuid($prefix = '')
{
$chars = md5(uniqid(mt_rand(), true));
$uuid = substr($chars,0,8) . '-';
$uuid .= substr($chars,8,4) . '-';
$uuid .= substr($chars,12,4) . '-';
$uuid .= substr($chars,16,4) . '-';
$uuid .= substr($chars,20,12);
return $prefix . $uuid;
}
//保存文件名到指定路径
/* protected function saveFile($filename){
if(!is_dir("./uploads/")){
mkdir("./uploads/");
}
$update_path = './uploads/';
$exe = substr($filename, strrpos($filename, '.'));
$exe = $exe == '.jpeg' ? '.jpg' : $exe;
$fileNewName = $this->uuid() . $exe;
$path = $update_path . $fileNewName;
return $path ;
}*/
//保存文件名到指定路径
protected function saveFile($filename){
if(!is_dir("../admin/app/storage/uploads/")){
mkdir("../admin/app/storage/uploads/");
}
$update_path = '../admin/app/storage/uploads/';
$exe = substr($filename, strrpos($filename, '.'));
$exe = $exe == '.jpeg' ? '.jpg' : $exe;
$fileNewName = $this->uuid() . $exe;
$path = $update_path . $fileNewName;
return $path ;
}
//保存文件内容
protected function saveFileContent($newFile,$content){
fwrite($newFile,$content); //写入二进制流到文件
}
}
$echo = new echoServer("0.0.0.0","4004");
try {
$echo->run();
}
catch (Exception $e) {
$echo->stdout($e->getMessage());
}
client.html(聊天客户测试页面)
<html><head><title>WebSocket</title>
<style type="text/css">
html,body {
font:normal 0.9em arial,helvetica;
}
#log {
width:600px;
height:300px;
border:1px solid #7F9DB9;
overflow:auto;
}
#msg {
width:400px;
}
</style>
<script type="text/javascript">
var socket;
function init() {
var host = "ws://127.0.0.1:4004/echobot"; // SET THIS TO YOUR SERVER
try {
socket = new WebSocket(host);
log('WebSocket - status '+socket.readyState);
socket.onopen = function(msg) {
log("Welcome - status "+this.readyState);
console.log("连接服务器成功") ;
};
socket.onmessage = function(msg) {
log("Received: "+msg.data);
};
socket.onclose = function(msg) {
log("Disconnected - status "+this.readyState);
};
}
catch(ex){
log(ex);
}
$("msg").focus();
}
function send(){
var txt,msg;
txt = $("msg");
msg = txt.value;
if(!msg) {
alert("Message can not be empty");
return;
}
txt.value="";
txt.focus();
try {
socket.send(msg);
log('Sent: '+msg);
} catch(ex) {
log(ex);
}
}
function quit(){
if (socket != null) {
log("Goodbye!");
socket.close();
socket=null;
}
}
function reconnect() {
quit();
init();
}
// Utilities
function $(id){ return document.getElementById(id); }
function log(msg){ $("log").innerHTML+="<br>"+msg; }
function onkey(event){ if(event.keyCode==13){ send(); } }
</script>
</head>
<body οnlοad="init()">
<h3>WebSocket v2.00</h3>
<div id="log"></div>
<input id="msg" type="textbox" οnkeypress="onkey(event)"/>
<button οnclick="send()">Send</button>
<button οnclick="quit()">Quit</button>
<button οnclick="reconnect()">Reconnect</button>
</body>
</html>
fenduanshangchuanClient.php(分段上传文件客户端测试页面)
<strong><span style="font-size:10px;">client端代码:</span></strong>
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Chat Client</title>
<meta charset="utf-8" />
</head>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">分段读取文件:</div>
<div class="panel-body" id="bodyOne">
<input type="file" id="file" multiple/><br />
</div>
</div>
</div>
<script>
//封装 单个文件上传实例
(function () {
var url = 'ws://127.0.0.1:4004/ashx/upload4.ashx';
//var url = 'ws://10.0.1.101:4004';
//指定上传文件,创建上传操作对象
function uploadOperate(file) {
var _this = this;
this.reader = new FileReader();//读取文件对象
this.step = 1024 ;//1024 * 256;//每次读取文件字节数
this.curLoaded = 0; //当前读取位置
this.file = file; //当前文件对象
this.enableRead = true; //指定是否可读取,
this.total = file.size; //当前文件总大小
this.startTime = new Date(); //开始读取时间
//创建显示
//this.createItem();
this.initWebSocket(function () {
_this.bindReader();
});
console.info('文件大小:' + this.total);
}
uploadOperate.prototype = {
//绑定读取事件
bindReader: function () {
var _this = this;
var reader = this.reader;
var ws = this.ws;
reader.onload = function (e) {
//判断是否能再次读取
if (_this.enableRead == false) return;
//根据当前缓冲区 控制读取速度
if (ws.bufferedAmount >= _this.step * 20) {
setTimeout(function () {
_this.loadSuccess(e.loaded);
}, 5);
console.info('---->进入等待');
} else {
_this.loadSuccess(e.loaded);
}
}
//开始读取
_this.readBlob();
},
//读取成功,操作处理
loadSuccess: function (loaded) {
var _this = this;
var ws = _this.ws;
//使用WebSocket 将二进制输出上传到服务器
var blob = _this.reader.result;
if (_this.curLoaded <= 0)
//ws.send(_this.file.name);
ws.send('filename='+_this.file.name+'&filesize='+_this.file.size);
ws.send(blob);
//当前发送完成,继续读取
_this.curLoaded += loaded;
if (_this.curLoaded < _this.total) {
_this.readBlob();
} else {
//发送读取完成
//ws.send('发送完成');
//读取完成
console.log('总共上传:' + _this.curLoaded + ',总共用时:' + (new Date().getTime() - _this.startTime.getTime()) / 1000);
}
//显示进度等
// _this.showProgress();
},
/* //创建显示项
createItem: function () {
var _this = this;
var blockquote = document.createElement('blockquote');
var abort = document.createElement('input');
abort.type = 'button';
abort.value = '中止';
abort.onclick = function () {
_this.stop();
};
blockquote.appendChild(abort);
var containue = document.createElement('input');
containue.type = 'button';
containue.value = '继续';
containue.onclick = function () {
_this.containue();
};
blockquote.appendChild(containue);
var progress = document.createElement('progress');
progress.style.width = '400px';
progress.max = 100;
progress.value = 0;
blockquote.appendChild(progress);
_this.progressBox = progress;
var status = document.createElement('p');
status.id = 'Status';
blockquote.appendChild(status);
_this.statusBox = status;
document.getElementById('bodyOne').appendChild(blockquote);
},
//显示进度
showProgress: function () {
var _this = this;
var percent = (_this.curLoaded / _this.total) * 100;
_this.progressBox.value = percent;
_this.statusBox.innerHTML = percent;
},*/
//执行读取文件
readBlob: function () {
var blob = this.file.slice(this.curLoaded, this.curLoaded + this.step);
this.reader.readAsArrayBuffer(blob);
},
//中止读取
stop: function () {
this.enableRead = false;
this.reader.abort();
console.log('读取中止,curLoaded:' + this.curLoaded);
},
//继续读取
containue: function () {
this.enableRead = true;
this.readBlob();
console.log('读取继续,curLoaded:' + this.curLoaded);
},
//初始化 绑定创建连接
initWebSocket: function (onSuccess) {
console.log('准备创建websocket连接');
var _this = this;
console.log('准备创建websocket连接');
var ws = this.ws = new WebSocket(url); //初始化上传对象
ws.onopen = function () {
console.log('连接服务器成功');
if (onSuccess)
onSuccess();
}
ws.onmessage = function (e) {
var data = e.data;
console.log("data:"+data) ;
if (isNaN(data) == false) {
console.info('后台接收成功:' + data);
} else {
console.info(data);
}
}
ws.onclose = function (e) {
//中止读取
_this.stop();
console.log('connect已经断开');
}
ws.onerror = function (e) {
//中止读取
_this.stop();
console.log('发生异常:' + e.message);
}
}
};
window.uploadOperate = uploadOperate;
})();
/*
* 测试WebSocket多文件上传
* 上传速度取决于 每次send() 的数据大小 ,Google之所以相对比较慢,是因为每次send的数据量太小
*/
var fileBox = document.getElementById('file');
fileBox.onchange = function () {
var files = this.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
var operate = new uploadOperate(file);
}
}
</script>
</html>