1. 程式人生 > >PHP-Websockets 上傳檔案2 速度提高很多

PHP-Websockets 上傳檔案2 速度提高很多

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 onload="init()">
<h3>WebSocket v2.00</h3>
<div id="log"></div>
<input id="msg" type="textbox" onkeypress="onkey(event)"/>
<button onclick="send()">Send</button>
<button onclick="quit()">Quit</button>
<button onclick="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>