├── Base.php ├── Combine.php ├── Exception.php ├── FastDFS.php ├── README.md ├── Storage.php ├── Tracker.php └── testcase.php /Base.php: -------------------------------------------------------------------------------- 1 | _host = $host; 58 | $this->_port = $port; 59 | $this->_sock = $this->_connect(); 60 | 61 | } 62 | 63 | private function _connect() { 64 | 65 | $sock = @fsockopen( 66 | $this->_host, 67 | $this->_port, 68 | $this->_errno, 69 | $this->_errstr, 70 | self::CONNECT_TIME_OUT 71 | ); 72 | 73 | if( !is_resource($sock) ) 74 | throw new Exception($this->_errstr, $this->_errno); 75 | 76 | return $sock; 77 | 78 | } 79 | 80 | protected function send($data, $length = 0) { 81 | 82 | if(!$length) $length = strlen($data); 83 | 84 | if ( feof($this->_sock) 85 | || fwrite( $this->_sock, $data, $length ) !== $length ) { 86 | 87 | $this->_error = 'connection unexpectedly closed (timed out?)'; 88 | $this->_errno = 0; 89 | throw new Exception($this->_errstr, $this->_errno); 90 | } 91 | 92 | return TRUE; 93 | } 94 | 95 | protected function read($length) { 96 | 97 | if( feof($this->_sock) ) { 98 | 99 | $this->_error = 'connection unexpectedly closed (timed out?)'; 100 | $this->_errno = 0; 101 | throw new Exception($this->_errstr, $this->_errno); 102 | } 103 | 104 | $data = stream_get_contents($this->_sock, $length); 105 | 106 | assert( $length === strlen($data) ); 107 | 108 | return $data; 109 | 110 | } 111 | 112 | final static function padding($str, $len) { 113 | 114 | $str_len = strlen($str); 115 | 116 | return $str_len > $len 117 | ? substr($str, 0, $len) 118 | : $str . pack('x'. ($len - $str_len)); 119 | } 120 | 121 | final static function buildHeader($command, $length = 0) { 122 | 123 | return self::packU64($length) . pack('Cx', $command); 124 | 125 | } 126 | 127 | final static function buildMetaData($data) { 128 | 129 | $list = array(); 130 | foreach($data as $key => $val) { 131 | $list[] = $key . "\x02" . $val; 132 | }; 133 | 134 | return implode("\x01", $list); 135 | } 136 | 137 | final static function parseMetaData($data) { 138 | 139 | $arr = explode("\x01", $data); 140 | $result = array(); 141 | 142 | foreach($arr as $val) { 143 | list($k, $v) = explode("\x02", $val); 144 | $result[$k] = $v; 145 | } 146 | 147 | return $result; 148 | 149 | } 150 | 151 | final static function parseHeader($str) { 152 | 153 | assert(strlen($str) === self::HEADER_LENGTH); 154 | 155 | 156 | $result = unpack('C10', $str); 157 | 158 | $length = self::unpackU64(substr($str, 0, 8)); 159 | $command = $result[9]; 160 | $status = $result[10]; 161 | 162 | return array( 163 | 'length' => $length, 164 | 'command' => $command, 165 | 'status' => $status 166 | ); 167 | 168 | } 169 | 170 | /** 171 | * From: sphinxapi.php 172 | */ 173 | final static function unpackU64($v) { 174 | list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); 175 | 176 | if ( PHP_INT_SIZE>=8 ) 177 | { 178 | if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again 179 | if ( $lo<0 ) $lo += (1<<32); 180 | 181 | // x64, int 182 | if ( $hi<=2147483647 ) 183 | return ($hi<<32) + $lo; 184 | 185 | // x64, bcmath 186 | if ( function_exists("bcmul") ) 187 | return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); 188 | 189 | // x64, no-bcmath 190 | $C = 100000; 191 | $h = ((int)($hi / $C) << 32) + (int)($lo / $C); 192 | $l = (($hi % $C) << 32) + ($lo % $C); 193 | if ( $l>$C ) 194 | { 195 | $h += (int)($l / $C); 196 | $l = $l % $C; 197 | } 198 | 199 | if ( $h==0 ) 200 | return $l; 201 | return sprintf ( "%d%05d", $h, $l ); 202 | } 203 | 204 | // x32, int 205 | if ( $hi==0 ) 206 | { 207 | if ( $lo>0 ) 208 | return $lo; 209 | return sprintf ( "%u", $lo ); 210 | } 211 | 212 | $hi = sprintf ( "%u", $hi ); 213 | $lo = sprintf ( "%u", $lo ); 214 | 215 | // x32, bcmath 216 | if ( function_exists("bcmul") ) 217 | return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); 218 | 219 | // x32, no-bcmath 220 | $hi = (float)$hi; 221 | $lo = (float)$lo; 222 | 223 | $q = floor($hi/10000000.0); 224 | $r = $hi - $q*10000000.0; 225 | $m = $lo + $r*4967296.0; 226 | $mq = floor($m/10000000.0); 227 | $l = $m - $mq*10000000.0; 228 | $h = $q*4294967296.0 + $r*429.0 + $mq; 229 | 230 | $h = sprintf ( "%.0f", $h ); 231 | $l = sprintf ( "%07.0f", $l ); 232 | if ( $h=="0" ) 233 | return sprintf( "%.0f", (float)$l ); 234 | return $h . $l; 235 | } 236 | 237 | /** 238 | * From: sphinxapi.php 239 | */ 240 | final static function packU64 ($v) { 241 | 242 | 243 | assert ( is_numeric($v) ); 244 | 245 | // x64 246 | if ( PHP_INT_SIZE>=8 ) 247 | { 248 | assert ( $v>=0 ); 249 | 250 | // x64, int 251 | if ( is_int($v) ) 252 | return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); 253 | 254 | // x64, bcmath 255 | if ( function_exists("bcmul") ) 256 | { 257 | $h = bcdiv ( $v, 4294967296, 0 ); 258 | $l = bcmod ( $v, 4294967296 ); 259 | return pack ( "NN", $h, $l ); 260 | } 261 | 262 | // x64, no-bcmath 263 | $p = max ( 0, strlen($v) - 13 ); 264 | $lo = (int)substr ( $v, $p ); 265 | $hi = (int)substr ( $v, 0, $p ); 266 | 267 | $m = $lo + $hi*1316134912; 268 | $l = $m % 4294967296; 269 | $h = $hi*2328 + (int)($m/4294967296); 270 | 271 | return pack ( "NN", $h, $l ); 272 | } 273 | 274 | // x32, int 275 | if ( is_int($v) ) 276 | return pack ( "NN", 0, $v ); 277 | 278 | // x32, bcmath 279 | if ( function_exists("bcmul") ) 280 | { 281 | $h = bcdiv ( $v, "4294967296", 0 ); 282 | $l = bcmod ( $v, "4294967296" ); 283 | return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit 284 | } 285 | 286 | // x32, no-bcmath 287 | $p = max(0, strlen($v) - 13); 288 | $lo = (float)substr($v, $p); 289 | $hi = (float)substr($v, 0, $p); 290 | 291 | $m = $lo + $hi*1316134912.0; 292 | $q = floor($m / 4294967296.0); 293 | $l = $m - ($q * 4294967296.0); 294 | $h = $hi*2328.0 + $q; 295 | 296 | return pack ( "NN", $h, $l ); 297 | } 298 | 299 | public function __destruct() { 300 | 301 | if(is_resource($this->_sock)) 302 | fclose($this->_sock); 303 | } 304 | } -------------------------------------------------------------------------------- /Combine.php: -------------------------------------------------------------------------------- 1 | _host = $host; 60 | $this->_port = $port; 61 | $this->_sock = $this->_connect(); 62 | 63 | } 64 | 65 | private function _connect() { 66 | 67 | $sock = @fsockopen( 68 | $this->_host, 69 | $this->_port, 70 | $this->_errno, 71 | $this->_errstr, 72 | self::CONNECT_TIME_OUT 73 | ); 74 | 75 | if( !is_resource($sock) ) 76 | throw new Exception($this->_errstr, $this->_errno); 77 | 78 | return $sock; 79 | 80 | } 81 | 82 | protected function send($data, $length = 0) { 83 | 84 | if(!$length) $length = strlen($data); 85 | 86 | if ( feof($this->_sock) 87 | || fwrite( $this->_sock, $data, $length ) !== $length ) { 88 | 89 | $this->_error = 'connection unexpectedly closed (timed out?)'; 90 | $this->_errno = 0; 91 | throw new Exception($this->_errstr, $this->_errno); 92 | } 93 | 94 | return TRUE; 95 | } 96 | 97 | protected function read($length) { 98 | 99 | if( feof($this->_sock) ) { 100 | 101 | $this->_error = 'connection unexpectedly closed (timed out?)'; 102 | $this->_errno = 0; 103 | throw new Exception($this->_errstr, $this->_errno); 104 | } 105 | 106 | $data = stream_get_contents($this->_sock, $length); 107 | 108 | assert( $length === strlen($data) ); 109 | 110 | return $data; 111 | 112 | } 113 | 114 | final static function padding($str, $len) { 115 | 116 | $str_len = strlen($str); 117 | 118 | return $str_len > $len 119 | ? substr($str, 0, $len) 120 | : $str . pack('x'. ($len - $str_len)); 121 | } 122 | 123 | final static function buildHeader($command, $length = 0) { 124 | 125 | return self::packU64($length) . pack('Cx', $command); 126 | 127 | } 128 | 129 | final static function buildMetaData($data) { 130 | 131 | $list = array(); 132 | foreach($data as $key => $val) { 133 | $list[] = $key . "\x02" . $val; 134 | }; 135 | 136 | return implode("\x01", $list); 137 | } 138 | 139 | final static function parseMetaData($data) { 140 | 141 | $arr = explode("\x01", $data); 142 | $result = array(); 143 | 144 | foreach($arr as $val) { 145 | list($k, $v) = explode("\x02", $val); 146 | $result[$k] = $v; 147 | } 148 | 149 | return $result; 150 | 151 | } 152 | 153 | final static function parseHeader($str) { 154 | 155 | assert(strlen($str) === self::HEADER_LENGTH); 156 | 157 | 158 | $result = unpack('C10', $str); 159 | 160 | $length = self::unpackU64(substr($str, 0, 8)); 161 | $command = $result[9]; 162 | $status = $result[10]; 163 | 164 | return array( 165 | 'length' => $length, 166 | 'command' => $command, 167 | 'status' => $status 168 | ); 169 | 170 | } 171 | 172 | /** 173 | * From: sphinxapi.php 174 | */ 175 | final static function unpackU64($v) { 176 | list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); 177 | 178 | if ( PHP_INT_SIZE>=8 ) 179 | { 180 | if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again 181 | if ( $lo<0 ) $lo += (1<<32); 182 | 183 | // x64, int 184 | if ( $hi<=2147483647 ) 185 | return ($hi<<32) + $lo; 186 | 187 | // x64, bcmath 188 | if ( function_exists("bcmul") ) 189 | return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); 190 | 191 | // x64, no-bcmath 192 | $C = 100000; 193 | $h = ((int)($hi / $C) << 32) + (int)($lo / $C); 194 | $l = (($hi % $C) << 32) + ($lo % $C); 195 | if ( $l>$C ) 196 | { 197 | $h += (int)($l / $C); 198 | $l = $l % $C; 199 | } 200 | 201 | if ( $h==0 ) 202 | return $l; 203 | return sprintf ( "%d%05d", $h, $l ); 204 | } 205 | 206 | // x32, int 207 | if ( $hi==0 ) 208 | { 209 | if ( $lo>0 ) 210 | return $lo; 211 | return sprintf ( "%u", $lo ); 212 | } 213 | 214 | $hi = sprintf ( "%u", $hi ); 215 | $lo = sprintf ( "%u", $lo ); 216 | 217 | // x32, bcmath 218 | if ( function_exists("bcmul") ) 219 | return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); 220 | 221 | // x32, no-bcmath 222 | $hi = (float)$hi; 223 | $lo = (float)$lo; 224 | 225 | $q = floor($hi/10000000.0); 226 | $r = $hi - $q*10000000.0; 227 | $m = $lo + $r*4967296.0; 228 | $mq = floor($m/10000000.0); 229 | $l = $m - $mq*10000000.0; 230 | $h = $q*4294967296.0 + $r*429.0 + $mq; 231 | 232 | $h = sprintf ( "%.0f", $h ); 233 | $l = sprintf ( "%07.0f", $l ); 234 | if ( $h=="0" ) 235 | return sprintf( "%.0f", (float)$l ); 236 | return $h . $l; 237 | } 238 | 239 | /** 240 | * From: sphinxapi.php 241 | */ 242 | final static function packU64 ($v) { 243 | 244 | 245 | assert ( is_numeric($v) ); 246 | 247 | // x64 248 | if ( PHP_INT_SIZE>=8 ) 249 | { 250 | assert ( $v>=0 ); 251 | 252 | // x64, int 253 | if ( is_int($v) ) 254 | return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); 255 | 256 | // x64, bcmath 257 | if ( function_exists("bcmul") ) 258 | { 259 | $h = bcdiv ( $v, 4294967296, 0 ); 260 | $l = bcmod ( $v, 4294967296 ); 261 | return pack ( "NN", $h, $l ); 262 | } 263 | 264 | // x64, no-bcmath 265 | $p = max ( 0, strlen($v) - 13 ); 266 | $lo = (int)substr ( $v, $p ); 267 | $hi = (int)substr ( $v, 0, $p ); 268 | 269 | $m = $lo + $hi*1316134912; 270 | $l = $m % 4294967296; 271 | $h = $hi*2328 + (int)($m/4294967296); 272 | 273 | return pack ( "NN", $h, $l ); 274 | } 275 | 276 | // x32, int 277 | if ( is_int($v) ) 278 | return pack ( "NN", 0, $v ); 279 | 280 | // x32, bcmath 281 | if ( function_exists("bcmul") ) 282 | { 283 | $h = bcdiv ( $v, "4294967296", 0 ); 284 | $l = bcmod ( $v, "4294967296" ); 285 | return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit 286 | } 287 | 288 | // x32, no-bcmath 289 | $p = max(0, strlen($v) - 13); 290 | $lo = (float)substr($v, $p); 291 | $hi = (float)substr($v, 0, $p); 292 | 293 | $m = $lo + $hi*1316134912.0; 294 | $q = floor($m / 4294967296.0); 295 | $l = $m - ($q * 4294967296.0); 296 | $h = $hi*2328.0 + $q; 297 | 298 | return pack ( "NN", $h, $l ); 299 | } 300 | 301 | public function __destruct() { 302 | 303 | if(is_resource($this->_sock)) 304 | fclose($this->_sock); 305 | } 306 | } 307 | /** 308 | * PHP-FastDFS-Client (FOR FastDFS v4.0.6) 309 | * 310 | * 用PHP Socket实现的FastDFS客户端 311 | * 312 | * @author: $Author: QPWOEIRU96 313 | * @version: $Rev: 525 $ 314 | * @date: $Date: 2014-01-17 15:02:03 +0800 (周五, 17 一月 2014) $ 315 | */ 316 | 317 | 318 | 319 | /** 320 | * 异常类 321 | */ 322 | class Exception extends \Exception { 323 | 324 | /** 325 | * 构造器 326 | * 327 | * @param string = 328 | */ 329 | public function __construct($message = '', $code = 0) {} 330 | } 331 | /** 332 | * PHP-FastDFS-Client (FOR FastDFS v4.0.6) 333 | * 334 | * 用PHP Socket实现的FastDFS客户端 335 | * 336 | * @author: $Author: QPWOEIRU96 337 | * @version: $Rev: 525 $ 338 | * @date: $Date: 2014-01-17 15:02:03 +0800 (周五, 17 一月 2014) $ 339 | */ 340 | 341 | 342 | 343 | 344 | class Tracker extends Base{ 345 | 346 | /** 347 | * 根据GroupName申请Storage地址 348 | * 349 | * @command 104 350 | * @param string $group_name 组名称 351 | * @return array/boolean 352 | */ 353 | public function applyStorage($group_name) { 354 | 355 | $req_header = self::buildHeader(104, Base::GROUP_NAME_MAX_LEN); 356 | $req_body = self::padding($group_name, Base::GROUP_NAME_MAX_LEN); 357 | 358 | $this->send($req_header . $req_body); 359 | 360 | $res_header = $this->read(Base::HEADER_LENGTH); 361 | $res_info = self::parseHeader($res_header); 362 | 363 | if($res_info['status'] !== 0) { 364 | 365 | throw new FastDFSException( 366 | 'something wrong with get storage by group name', 367 | $res_info['status']); 368 | 369 | return FALSE; 370 | } 371 | 372 | $res_body = !!$res_info['length'] 373 | ? $this->read($res_info['length']) 374 | : ''; 375 | 376 | $group_name = trim(substr($res_body, 0, Base::GROUP_NAME_MAX_LEN)); 377 | $storage_addr = trim(substr($res_body, Base::GROUP_NAME_MAX_LEN, 378 | Base::IP_ADDRESS_SIZE - 1)); 379 | 380 | list(,,$storage_port) = unpack('N2', substr($res_body, 381 | Base::GROUP_NAME_MAX_LEN + Base::IP_ADDRESS_SIZE - 1, 382 | Base::PROTO_PKG_LEN_SIZE)); 383 | 384 | $storage_index = ord(substr($res_body, -1)); 385 | 386 | 387 | return array( 388 | 'group_name' => $group_name, 389 | 'storage_addr' => $storage_addr, 390 | 'storage_port' => $storage_port, 391 | 'storage_index' => $storage_index 392 | ); 393 | 394 | 395 | } 396 | 397 | } 398 | 399 | 400 | 401 | 402 | /** 403 | * PHP-FastDFS-Client (FOR FastDFS v4.0.6) 404 | * 405 | * 用PHP Socket实现的FastDFS客户端 406 | * 407 | * @author: $Author: QPWOEIRU96 408 | * @version: $Rev: 525 $ 409 | * @date: $Date: 2014-01-17 15:02:03 +0800 (周五, 17 一月 2014) $ 410 | */ 411 | 412 | 413 | 414 | class Storage extends Base { 415 | 416 | /** 417 | * 上传文件 418 | * 419 | * @command 11 420 | * @param string $index 索引 421 | * @param string $filename 422 | * @param string $ext 423 | * @throws Exception 424 | * @internal param string $文件扩展名 425 | * @return array 426 | */ 427 | public function uploadFile($index, $filename, $ext = '') { 428 | 429 | 430 | if(!file_exists($filename)) 431 | return FALSE; 432 | 433 | $pathInfo = pathinfo($filename); 434 | 435 | if(strlen($ext) > Base::FILE_EXT_NAME_MAX_LEN) 436 | throw new Exception('file ext too long.', 0); 437 | 438 | if($ext === '') { 439 | $ext = $pathInfo['extension']; 440 | } 441 | 442 | $fp = fopen($filename, 'rb'); 443 | flock($fp, LOCK_SH); 444 | 445 | $fileSize = filesize($filename); 446 | 447 | $requestBodyLength = 1 + Base::PROTO_PKG_LEN_SIZE + 448 | Base::FILE_EXT_NAME_MAX_LEN + $fileSize; 449 | 450 | $requestHeader = self::buildHeader(11, $requestBodyLength); 451 | $requestBody = pack('C', $index) . self::packU64($fileSize) . self::padding($ext, Base::FILE_EXT_NAME_MAX_LEN); 452 | $this->send($requestHeader . $requestBody); 453 | 454 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 455 | 456 | flock($fp, LOCK_UN); 457 | fclose($fp); 458 | 459 | $responseHeader = $this->read(Base::HEADER_LENGTH); 460 | $responseInfo = self::parseHeader($responseHeader); 461 | 462 | if($responseInfo['status'] !== 0) 463 | 464 | throw new Exception( 465 | 'something wrong with uplode file', 466 | $responseInfo['status']); 467 | 468 | 469 | $responseBody = $responseInfo['length'] 470 | ? $this->read($responseInfo['length']) 471 | : FALSE; 472 | 473 | $groupName = trim(substr($responseBody, 0, Base::GROUP_NAME_MAX_LEN)); 474 | $filePath = trim(substr($responseBody, Base::GROUP_NAME_MAX_LEN)); 475 | 476 | return array( 477 | 'group' => $groupName, 478 | 'path' => $filePath 479 | ); 480 | 481 | } 482 | 483 | /** 484 | * 上传Slave文件 485 | * 486 | * @command 21 487 | * @param string $filename 待上传的文件名称 488 | * @param string $masterFilePath 主文件名称 489 | * @param string $prefix_name 后缀的前缀名 490 | * @param string $ext 后缀名称 491 | * @throws Exception 492 | * @return array/boolean 493 | */ 494 | public function uploadSlaveFile($filename, $masterFilePath, $prefix_name, $ext = '') { 495 | 496 | if(!file_exists($filename)) 497 | return FALSE; 498 | 499 | $pathInfo = pathinfo($filename); 500 | 501 | if(strlen($ext) > Base::FILE_EXT_NAME_MAX_LEN) 502 | throw new Exception('file ext too long.', 0); 503 | 504 | if($ext === '') { 505 | $ext = $pathInfo['extension']; 506 | } 507 | 508 | $fp = fopen($filename, 'rb'); 509 | flock($fp, LOCK_SH); 510 | 511 | $fileSize = filesize($filename); 512 | $masterFilePathLen = strlen($masterFilePath); 513 | 514 | $requestBodyLength = 16 + Base::FILE_PREFIX_MAX_LEN + 515 | Base::FILE_EXT_NAME_MAX_LEN + $masterFilePathLen + $fileSize; 516 | 517 | $requestHeader = self::buildHeader(21, $requestBodyLength); 518 | $requestBody = pack('x4N', $masterFilePathLen) . self::packU64($fileSize) . self::padding($prefix_name, Base::FILE_PREFIX_MAX_LEN); 519 | $requestBody .= self::padding($ext, Base::FILE_EXT_NAME_MAX_LEN) . $masterFilePath; 520 | 521 | $this->send($requestHeader . $requestBody); 522 | 523 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 524 | 525 | flock($fp, LOCK_UN); 526 | fclose($fp); 527 | 528 | $responseHeader = $this->read(Base::HEADER_LENGTH); 529 | $responseInfo = self::parseHeader($responseHeader); 530 | 531 | if($responseInfo['status'] !== 0) { 532 | 533 | if($responseInfo['status'] == 17) { 534 | $msg = 'target slave file already existd'; 535 | } else { 536 | $msg = 'something in upload slave file'; 537 | } 538 | 539 | throw new Exception( 540 | $msg, 541 | $responseInfo['status']); 542 | } 543 | 544 | $responseBody = $responseInfo['length'] 545 | ? $this->read($responseInfo['length']) 546 | : FALSE; 547 | 548 | $groupName = trim(substr($responseBody, 0, Base::GROUP_NAME_MAX_LEN)); 549 | $filePath = trim(substr($responseBody, Base::GROUP_NAME_MAX_LEN)); 550 | 551 | return array( 552 | "group" => $groupName, 553 | 'path' => $filePath 554 | ); 555 | 556 | } 557 | 558 | /** 559 | * 560 | * @command 23 561 | */ 562 | public function uploadAppenderFile($index, $filename, $ext = '') { 563 | 564 | if(!file_exists($filename)) 565 | return FALSE; 566 | 567 | $pathInfo = pathinfo($filename); 568 | 569 | if(strlen($ext) > Base::FILE_EXT_NAME_MAX_LEN) 570 | throw new Exception('file ext too long.', 0); 571 | 572 | if($ext === '') { 573 | $ext = $pathInfo['extension']; 574 | } 575 | 576 | $fp = fopen($filename, 'rb'); 577 | flock($fp, LOCK_SH); 578 | 579 | $fileSize = filesize($filename); 580 | 581 | $requestBodyLength = 1 + Base::PROTO_PKG_LEN_SIZE + 582 | Base::FILE_EXT_NAME_MAX_LEN + $fileSize; 583 | 584 | $requestHeader = self::buildHeader(23, $requestBodyLength); 585 | $requestBody = pack('C', $index) . self::packU64($fileSize) . self::padding($ext, Base::FILE_EXT_NAME_MAX_LEN); 586 | $this->send($requestHeader . $requestBody); 587 | 588 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 589 | 590 | flock($fp, LOCK_UN); 591 | fclose($fp); 592 | 593 | $responseHeader = $this->read(Base::HEADER_LENGTH); 594 | $responseInfo = self::parseHeader($responseHeader); 595 | 596 | if($responseInfo['status'] !== 0) { 597 | 598 | throw new Exception( 599 | 'something wrong with uplode file', 600 | $responseInfo['status']); 601 | 602 | return FALSE; 603 | } 604 | 605 | $responseBody = $responseInfo['length'] 606 | ? $this->read($responseInfo['length']) 607 | : FALSE; 608 | 609 | $groupName = trim(substr($responseBody, 0, Base::GROUP_NAME_MAX_LEN)); 610 | $filePath = trim(substr($responseBody, Base::GROUP_NAME_MAX_LEN)); 611 | 612 | return array( 613 | "group" => $groupName, 614 | 'path' => $filePath 615 | ); 616 | } 617 | 618 | /** 619 | * @command 24 620 | */ 621 | public function appendFile($content, $appenderFilePath) { 622 | 623 | $appenderFilePathLength = strlen($appenderFilePath); 624 | $content_length = strlen($content); 625 | $requestBodyLength = (2 * Base::PROTO_PKG_LEN_SIZE) + $appenderFilePathLength + $content_length; 626 | 627 | $requestHeader = self::buildHeader(24, $requestBodyLength); 628 | $requestBody = pack('x4N', $appenderFilePathLength) . self::packU64($content_length) . $appenderFilePath . $content; 629 | $this->send($requestHeader . $requestBody); 630 | 631 | $responseHeader = $this->read(Base::HEADER_LENGTH); 632 | $responseInfo = self::parseHeader($responseHeader); 633 | 634 | 635 | return !$responseInfo['status']; 636 | 637 | } 638 | 639 | /** 640 | * @command 34 641 | */ 642 | public function modifyFile($filename, $filePath, $offset = 0) { 643 | 644 | if(!file_exists($filename)) 645 | return FALSE; 646 | 647 | $filePathLength = strlen($filePath); 648 | $fileSize = filesize($filename); 649 | $requestBodyLength = (3 * Base::PROTO_PKG_LEN_SIZE) + $filePathLength + $fileSize; 650 | 651 | $requestHeader = self::buildHeader(34, $requestBodyLength); 652 | $requestBody = pack('x4N', $filePathLength) . self::packU64($offset) . self::packU64($fileSize) . $filePath; 653 | 654 | $this->send($requestHeader . $requestBody); 655 | 656 | $fp = fopen($filename, 'rb'); 657 | flock($fp, LOCK_SH); 658 | 659 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 660 | 661 | flock($fp, LOCK_UN); 662 | fclose($fp); 663 | 664 | $responseHeader = $this->read(Base::HEADER_LENGTH); 665 | $responseInfo = self::parseHeader($responseHeader); 666 | 667 | return !$responseInfo['status']; 668 | 669 | } 670 | 671 | /** 672 | * 删除文件 673 | * 674 | * @command 12 675 | * @param string $groupName 组名称 676 | * @param string $filePath 文件路径 677 | * @return boolean 删除成功与否 678 | */ 679 | public function deleteFile($groupName, $filePath) { 680 | 681 | $requestBodyLength = strlen($filePath) + Base::GROUP_NAME_MAX_LEN; 682 | $requestHeader = self::buildHeader(12, $requestBodyLength); 683 | $requestBody = self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath; 684 | 685 | $this->send($requestHeader . $requestBody); 686 | 687 | $responseHeader = $this->read(Base::HEADER_LENGTH); 688 | $responseInfo = self::parseHeader($responseHeader); 689 | 690 | return !$responseInfo['status']; 691 | 692 | } 693 | 694 | /** 695 | * 获取文件元信息 696 | * 697 | * @command 15 698 | * @param string $groupName 组名称 699 | * @param string $filePath 文件路径 700 | * @return array 元信息数组 701 | */ 702 | public function getFileMetaData($groupName, $filePath) { 703 | 704 | $requestBodyLength = strlen($filePath) + Base::GROUP_NAME_MAX_LEN; 705 | $requestHeader = self::buildHeader(15, $requestBodyLength); 706 | $requestBody = self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath; 707 | 708 | $this->send($requestHeader . $requestBody); 709 | 710 | $responseHeader = $this->read(Base::HEADER_LENGTH); 711 | $responseInfo = self::parseHeader($responseHeader); 712 | 713 | if(!!$responseInfo['status']) 714 | return FALSE; 715 | 716 | $responseBody = $responseInfo['length'] 717 | ? $this->read($responseInfo['length']) 718 | : FALSE; 719 | 720 | return self::parseMetaData($responseBody); 721 | 722 | } 723 | 724 | /** 725 | * 设置文件元信息 726 | * 727 | * @command 13 728 | * @param string $groupName 组名称 729 | * @param string $filePath 文件路径 730 | * @param array $meta_data 元信息数组 731 | * @param $flag 732 | * @return boolean 设置成功与否 733 | */ 734 | public function setFileMetaData($groupName, $filePath, array $meta_data, $flag = Base::OVERWRITE_METADATA) { 735 | 736 | $meta_data = self::buildMetaData($meta_data); 737 | $meta_data_length = strlen($meta_data); 738 | $filePathLength = strlen($filePath); 739 | $flag = $flag === Base::OVERWRITE_METADATA ? 'O' : 'M'; 740 | 741 | $requestBodyLength = (Base::PROTO_PKG_LEN_SIZE * 2) + 1 + $meta_data_length + $filePathLength + Base::GROUP_NAME_MAX_LEN; 742 | 743 | $requestHeader = self::buildHeader(13, $requestBodyLength); 744 | 745 | $requestBody = self::packU64($filePathLength) . self::packU64($meta_data_length); 746 | $requestBody .= $flag . self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath . $meta_data; 747 | 748 | $this->send($requestHeader . $requestBody); 749 | 750 | $responseHeader = $this->read(Base::HEADER_LENGTH); 751 | $responseInfo = self::parseHeader($responseHeader); 752 | 753 | return !$responseInfo['status']; 754 | 755 | } 756 | 757 | /** 758 | * 读取文件(不建议对大文件使用) 759 | * 760 | * @command 14 761 | * @param string $groupName 组名称 762 | * @param string $filePath 文件路径 763 | * @param int $offset 下载文件偏移量 764 | * @param int $length 下载文件大小 765 | * @return string 文件内容 766 | */ 767 | public function readFile($groupName, $filePath, $offset = 0, $length = 0) { 768 | 769 | $filePathLength = strlen($filePath); 770 | $requestBodyLength = (Base::PROTO_PKG_LEN_SIZE * 2) + $filePathLength + Base::GROUP_NAME_MAX_LEN; 771 | 772 | $requestHeader = self::buildHeader(14, $requestBodyLength); 773 | 774 | $requestBody = self::packU64($offset) . self::packU64($length) . self::padding($groupName, Base::GROUP_NAME_MAX_LEN); 775 | $requestBody .= $filePath; 776 | 777 | $this->send($requestHeader . $requestBody); 778 | 779 | $responseHeader = $this->read(Base::HEADER_LENGTH); 780 | $responseInfo = self::parseHeader($responseHeader); 781 | 782 | if(!!$responseInfo['status']) return FALSE; 783 | 784 | return $this->read($responseInfo['length']); 785 | } 786 | 787 | /** 788 | * 下载文件 789 | * 790 | * @param $groupName 791 | * @param $filePath 792 | * @param $targetPath 793 | * @param int $offset 794 | * @param int $length 795 | * @return bool 796 | */ 797 | public function downloadFile($groupName, $filePath, $targetPath, $offset = 0, $length = 0) 798 | { 799 | $filePathLength = strlen($filePath); 800 | $requestBodyLength = (Base::PROTO_PKG_LEN_SIZE * 2) + $filePathLength + Base::GROUP_NAME_MAX_LEN; 801 | $requestHeader = self::buildHeader(14, $requestBodyLength); 802 | $requestBody = self::packU64($offset) . self::packU64($length) . self::padding($groupName, Base::GROUP_NAME_MAX_LEN); 803 | $requestBody .= $filePath; 804 | 805 | $this->send($requestHeader . $requestBody); 806 | 807 | $responseHeader = $this->read(Base::HEADER_LENGTH); 808 | $responseInfo = self::parseHeader($responseHeader); 809 | 810 | if($responseInfo['status']) return FALSE; 811 | 812 | $fp = fopen($targetPath, 'w+b'); 813 | stream_copy_to_stream($this->_sock, $fp, $responseInfo['length'], 0); 814 | fclose($fp); 815 | 816 | 817 | return true; 818 | } 819 | 820 | /** 821 | * 检索文件信息 822 | * 823 | * @command 22 824 | * @param string $groupName 组名称 825 | * @param string $filePath 文件路径 826 | * @return array 827 | */ 828 | public function getFileInfo($groupName, $filePath) { 829 | 830 | $requestBodyLength = strlen($filePath) + Base::GROUP_NAME_MAX_LEN; 831 | $requestHeader = self::buildHeader(22, $requestBodyLength); 832 | $requestBody = self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath; 833 | 834 | $this->send($requestHeader . $requestBody); 835 | 836 | $responseHeader = $this->read(Base::HEADER_LENGTH); 837 | $responseInfo = self::parseHeader($responseHeader); 838 | 839 | if(!!$responseInfo['status']) return FALSE; 840 | 841 | $responseBody = $responseInfo['length'] 842 | ? $this->read($responseInfo['length']) 843 | : FALSE; 844 | 845 | $fileSize = self::unpackU64(substr($responseBody, 0, Base::PROTO_PKG_LEN_SIZE)); 846 | $timestamp = self::unpackU64(substr($responseBody, Base::PROTO_PKG_LEN_SIZE, Base::PROTO_PKG_LEN_SIZE)); 847 | list(,,$crc32) = unpack('N2', substr($responseBody, 2 * Base::PROTO_PKG_LEN_SIZE, Base::PROTO_PKG_LEN_SIZE)); 848 | $crc32 = base_convert(sprintf('%u', $crc32), 10, 16); 849 | $storageId = trim(substr($responseBody, -16)); 850 | 851 | return array( 852 | 'file_size' => $fileSize, 853 | 'timestamp' => $timestamp, 854 | 'crc32' => $crc32, 855 | 'storage_id' => $storageId 856 | ); 857 | 858 | } 859 | 860 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP-FastDFS-Client 2 | ================== 3 | 4 | 用PHP Socket 实现的 FastDFS 客户端. 5 | 目前支持版本为 FastDFS 4.06 6 | 7 | 实现功能: 8 | 9 | /** 10 | * 根据GroupName申请Storage地址 11 | * 12 | * @command 104 13 | * @param string $group_name 组名称 14 | * @return array/boolean 15 | */ 16 | 17 | FastDFS_Tracker::applyStorage($group_name) 18 | 19 | /** 20 | * 上传文件 21 | * 22 | * @command 11 23 | * @param char $index 索引 24 | * @param string $filename 25 | * @param string $文件扩展名 26 | * @return array 27 | */ 28 | 29 | FastDFS_Storage::uploadFile($index, $filename, $ext = '') 30 | 31 | /** 32 | * 上传Slave文件 33 | * 34 | * @command 21 35 | * @param string $filename 待上传的文件名称 36 | * @param string $master_file_path 主文件名称 37 | * @param string $prefix_name 后缀的前缀名 38 | * @param string $ext 后缀名称 39 | * @return array/boolean 40 | */ 41 | 42 | FastDFS_Storage::uploadSlaveFile($filename, $master_file_path, $prefix_name, $ext = '') 43 | 44 | /** 45 | * 删除文件 46 | * 47 | * @command 12 48 | * @param string $group_name 组名称 49 | * @param string $file_path 文件路径 50 | * @return boolean 删除成功与否 51 | */ 52 | 53 | FastDFS_Storage::deleteFile($group_name, $file_path) 54 | 55 | /** 56 | * 获取文件元信息 57 | * 58 | * @command 15 59 | * @param string $group_name 组名称 60 | * @param string $file_path 文件路径 61 | * @return array 元信息数组 62 | */ 63 | 64 | FastDFS_Storage::getFileMetaData($group_name, $file_path) 65 | 66 | /** 67 | * 设置文件元信息 68 | * 69 | * @command 13 70 | * @param string $group_name 组名称 71 | * @param string $file_path 文件路径 72 | * @param array $meta_data 元信息数组 73 | * @return boolean 设置成功与否 74 | */ 75 | 76 | FastDFS_Storage::setFileMetaData($group_name, $file_path, array $meta_data, $flag = FDFS_OVERWRITE_METADATA) 77 | 78 | /** 79 | * 下载文件(不建议对大文件使用) 80 | * 81 | * @command 14 82 | * @param string $group_name 组名称 83 | * @param string $file_path 文件路径 84 | * @param int $offset 下载文件偏移量 85 | * @param int $length 下载文件大小 86 | * @return string 文件内容 87 | */ 88 | 89 | FastDFS_Storage::downloadFile($group_name, $file_path, $offset = 0, $length = 0) 90 | 91 | /** 92 | * 检索文件信息 93 | * 94 | * @command 22 95 | * @param string $group_name 组名称 96 | * @param string $file_path 文件路径 97 | * @return array 98 | */ 99 | 100 | FastDFS_Storage::getFileInfo($group_name, $file_path) 101 | 102 | TODO LIST: 103 | 上传可更新文件: uploadAppenderFile() 104 | 附加可更新文件信息: appendFile() 105 | -------------------------------------------------------------------------------- /Storage.php: -------------------------------------------------------------------------------- 1 | Base::FILE_EXT_NAME_MAX_LEN) 36 | throw new Exception('file ext too long.', 0); 37 | 38 | if($ext === '') { 39 | $ext = $pathInfo['extension']; 40 | } 41 | 42 | $fp = fopen($filename, 'rb'); 43 | flock($fp, LOCK_SH); 44 | 45 | $fileSize = filesize($filename); 46 | 47 | $requestBodyLength = 1 + Base::PROTO_PKG_LEN_SIZE + 48 | Base::FILE_EXT_NAME_MAX_LEN + $fileSize; 49 | 50 | $requestHeader = self::buildHeader(11, $requestBodyLength); 51 | $requestBody = pack('C', $index) . self::packU64($fileSize) . self::padding($ext, Base::FILE_EXT_NAME_MAX_LEN); 52 | $this->send($requestHeader . $requestBody); 53 | 54 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 55 | 56 | flock($fp, LOCK_UN); 57 | fclose($fp); 58 | 59 | $responseHeader = $this->read(Base::HEADER_LENGTH); 60 | $responseInfo = self::parseHeader($responseHeader); 61 | 62 | if($responseInfo['status'] !== 0) 63 | 64 | throw new Exception( 65 | 'something wrong with uplode file', 66 | $responseInfo['status']); 67 | 68 | 69 | $responseBody = $responseInfo['length'] 70 | ? $this->read($responseInfo['length']) 71 | : FALSE; 72 | 73 | $groupName = trim(substr($responseBody, 0, Base::GROUP_NAME_MAX_LEN)); 74 | $filePath = trim(substr($responseBody, Base::GROUP_NAME_MAX_LEN)); 75 | 76 | return array( 77 | 'group' => $groupName, 78 | 'path' => $filePath 79 | ); 80 | 81 | } 82 | 83 | /** 84 | * 上传Slave文件 85 | * 86 | * @command 21 87 | * @param string $filename 待上传的文件名称 88 | * @param string $masterFilePath 主文件名称 89 | * @param string $prefix_name 后缀的前缀名 90 | * @param string $ext 后缀名称 91 | * @throws Exception 92 | * @return array/boolean 93 | */ 94 | public function uploadSlaveFile($filename, $masterFilePath, $prefix_name, $ext = '') { 95 | 96 | if(!file_exists($filename)) 97 | return FALSE; 98 | 99 | $pathInfo = pathinfo($filename); 100 | 101 | if(strlen($ext) > Base::FILE_EXT_NAME_MAX_LEN) 102 | throw new Exception('file ext too long.', 0); 103 | 104 | if($ext === '') { 105 | $ext = $pathInfo['extension']; 106 | } 107 | 108 | $fp = fopen($filename, 'rb'); 109 | flock($fp, LOCK_SH); 110 | 111 | $fileSize = filesize($filename); 112 | $masterFilePathLen = strlen($masterFilePath); 113 | 114 | $requestBodyLength = 16 + Base::FILE_PREFIX_MAX_LEN + 115 | Base::FILE_EXT_NAME_MAX_LEN + $masterFilePathLen + $fileSize; 116 | 117 | $requestHeader = self::buildHeader(21, $requestBodyLength); 118 | $requestBody = pack('x4N', $masterFilePathLen) . self::packU64($fileSize) . self::padding($prefix_name, Base::FILE_PREFIX_MAX_LEN); 119 | $requestBody .= self::padding($ext, Base::FILE_EXT_NAME_MAX_LEN) . $masterFilePath; 120 | 121 | $this->send($requestHeader . $requestBody); 122 | 123 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 124 | 125 | flock($fp, LOCK_UN); 126 | fclose($fp); 127 | 128 | $responseHeader = $this->read(Base::HEADER_LENGTH); 129 | $responseInfo = self::parseHeader($responseHeader); 130 | 131 | if($responseInfo['status'] !== 0) { 132 | 133 | if($responseInfo['status'] == 17) { 134 | $msg = 'target slave file already existd'; 135 | } else { 136 | $msg = 'something in upload slave file'; 137 | } 138 | 139 | throw new Exception( 140 | $msg, 141 | $responseInfo['status']); 142 | } 143 | 144 | $responseBody = $responseInfo['length'] 145 | ? $this->read($responseInfo['length']) 146 | : FALSE; 147 | 148 | $groupName = trim(substr($responseBody, 0, Base::GROUP_NAME_MAX_LEN)); 149 | $filePath = trim(substr($responseBody, Base::GROUP_NAME_MAX_LEN)); 150 | 151 | return array( 152 | "group" => $groupName, 153 | 'path' => $filePath 154 | ); 155 | 156 | } 157 | 158 | /** 159 | * 160 | * @command 23 161 | */ 162 | public function uploadAppenderFile($index, $filename, $ext = '') { 163 | 164 | if(!file_exists($filename)) 165 | return FALSE; 166 | 167 | $pathInfo = pathinfo($filename); 168 | 169 | if(strlen($ext) > Base::FILE_EXT_NAME_MAX_LEN) 170 | throw new Exception('file ext too long.', 0); 171 | 172 | if($ext === '') { 173 | $ext = $pathInfo['extension']; 174 | } 175 | 176 | $fp = fopen($filename, 'rb'); 177 | flock($fp, LOCK_SH); 178 | 179 | $fileSize = filesize($filename); 180 | 181 | $requestBodyLength = 1 + Base::PROTO_PKG_LEN_SIZE + 182 | Base::FILE_EXT_NAME_MAX_LEN + $fileSize; 183 | 184 | $requestHeader = self::buildHeader(23, $requestBodyLength); 185 | $requestBody = pack('C', $index) . self::packU64($fileSize) . self::padding($ext, Base::FILE_EXT_NAME_MAX_LEN); 186 | $this->send($requestHeader . $requestBody); 187 | 188 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 189 | 190 | flock($fp, LOCK_UN); 191 | fclose($fp); 192 | 193 | $responseHeader = $this->read(Base::HEADER_LENGTH); 194 | $responseInfo = self::parseHeader($responseHeader); 195 | 196 | if($responseInfo['status'] !== 0) { 197 | 198 | throw new Exception( 199 | 'something wrong with uplode file', 200 | $responseInfo['status']); 201 | 202 | return FALSE; 203 | } 204 | 205 | $responseBody = $responseInfo['length'] 206 | ? $this->read($responseInfo['length']) 207 | : FALSE; 208 | 209 | $groupName = trim(substr($responseBody, 0, Base::GROUP_NAME_MAX_LEN)); 210 | $filePath = trim(substr($responseBody, Base::GROUP_NAME_MAX_LEN)); 211 | 212 | return array( 213 | "group" => $groupName, 214 | 'path' => $filePath 215 | ); 216 | } 217 | 218 | /** 219 | * @command 24 220 | */ 221 | public function appendFile($content, $appenderFilePath) { 222 | 223 | $appenderFilePathLength = strlen($appenderFilePath); 224 | $content_length = strlen($content); 225 | $requestBodyLength = (2 * Base::PROTO_PKG_LEN_SIZE) + $appenderFilePathLength + $content_length; 226 | 227 | $requestHeader = self::buildHeader(24, $requestBodyLength); 228 | $requestBody = pack('x4N', $appenderFilePathLength) . self::packU64($content_length) . $appenderFilePath . $content; 229 | $this->send($requestHeader . $requestBody); 230 | 231 | $responseHeader = $this->read(Base::HEADER_LENGTH); 232 | $responseInfo = self::parseHeader($responseHeader); 233 | 234 | 235 | return !$responseInfo['status']; 236 | 237 | } 238 | 239 | /** 240 | * @command 34 241 | */ 242 | public function modifyFile($filename, $filePath, $offset = 0) { 243 | 244 | if(!file_exists($filename)) 245 | return FALSE; 246 | 247 | $filePathLength = strlen($filePath); 248 | $fileSize = filesize($filename); 249 | $requestBodyLength = (3 * Base::PROTO_PKG_LEN_SIZE) + $filePathLength + $fileSize; 250 | 251 | $requestHeader = self::buildHeader(34, $requestBodyLength); 252 | $requestBody = pack('x4N', $filePathLength) . self::packU64($offset) . self::packU64($fileSize) . $filePath; 253 | 254 | $this->send($requestHeader . $requestBody); 255 | 256 | $fp = fopen($filename, 'rb'); 257 | flock($fp, LOCK_SH); 258 | 259 | stream_copy_to_stream($fp, $this->_sock, $fileSize); 260 | 261 | flock($fp, LOCK_UN); 262 | fclose($fp); 263 | 264 | $responseHeader = $this->read(Base::HEADER_LENGTH); 265 | $responseInfo = self::parseHeader($responseHeader); 266 | 267 | return !$responseInfo['status']; 268 | 269 | } 270 | 271 | /** 272 | * 删除文件 273 | * 274 | * @command 12 275 | * @param string $groupName 组名称 276 | * @param string $filePath 文件路径 277 | * @return boolean 删除成功与否 278 | */ 279 | public function deleteFile($groupName, $filePath) { 280 | 281 | $requestBodyLength = strlen($filePath) + Base::GROUP_NAME_MAX_LEN; 282 | $requestHeader = self::buildHeader(12, $requestBodyLength); 283 | $requestBody = self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath; 284 | 285 | $this->send($requestHeader . $requestBody); 286 | 287 | $responseHeader = $this->read(Base::HEADER_LENGTH); 288 | $responseInfo = self::parseHeader($responseHeader); 289 | 290 | return !$responseInfo['status']; 291 | 292 | } 293 | 294 | /** 295 | * 获取文件元信息 296 | * 297 | * @command 15 298 | * @param string $groupName 组名称 299 | * @param string $filePath 文件路径 300 | * @return array 元信息数组 301 | */ 302 | public function getFileMetaData($groupName, $filePath) { 303 | 304 | $requestBodyLength = strlen($filePath) + Base::GROUP_NAME_MAX_LEN; 305 | $requestHeader = self::buildHeader(15, $requestBodyLength); 306 | $requestBody = self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath; 307 | 308 | $this->send($requestHeader . $requestBody); 309 | 310 | $responseHeader = $this->read(Base::HEADER_LENGTH); 311 | $responseInfo = self::parseHeader($responseHeader); 312 | 313 | if(!!$responseInfo['status']) 314 | return FALSE; 315 | 316 | $responseBody = $responseInfo['length'] 317 | ? $this->read($responseInfo['length']) 318 | : FALSE; 319 | 320 | return self::parseMetaData($responseBody); 321 | 322 | } 323 | 324 | /** 325 | * 设置文件元信息 326 | * 327 | * @command 13 328 | * @param string $groupName 组名称 329 | * @param string $filePath 文件路径 330 | * @param array $meta_data 元信息数组 331 | * @param $flag 332 | * @return boolean 设置成功与否 333 | */ 334 | public function setFileMetaData($groupName, $filePath, array $meta_data, $flag = Base::OVERWRITE_METADATA) { 335 | 336 | $meta_data = self::buildMetaData($meta_data); 337 | $meta_data_length = strlen($meta_data); 338 | $filePathLength = strlen($filePath); 339 | $flag = $flag === Base::OVERWRITE_METADATA ? 'O' : 'M'; 340 | 341 | $requestBodyLength = (Base::PROTO_PKG_LEN_SIZE * 2) + 1 + $meta_data_length + $filePathLength + Base::GROUP_NAME_MAX_LEN; 342 | 343 | $requestHeader = self::buildHeader(13, $requestBodyLength); 344 | 345 | $requestBody = self::packU64($filePathLength) . self::packU64($meta_data_length); 346 | $requestBody .= $flag . self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath . $meta_data; 347 | 348 | $this->send($requestHeader . $requestBody); 349 | 350 | $responseHeader = $this->read(Base::HEADER_LENGTH); 351 | $responseInfo = self::parseHeader($responseHeader); 352 | 353 | return !$responseInfo['status']; 354 | 355 | } 356 | 357 | /** 358 | * 读取文件(不建议对大文件使用) 359 | * 360 | * @command 14 361 | * @param string $groupName 组名称 362 | * @param string $filePath 文件路径 363 | * @param int $offset 下载文件偏移量 364 | * @param int $length 下载文件大小 365 | * @return string 文件内容 366 | */ 367 | public function readFile($groupName, $filePath, $offset = 0, $length = 0) { 368 | 369 | $filePathLength = strlen($filePath); 370 | $requestBodyLength = (Base::PROTO_PKG_LEN_SIZE * 2) + $filePathLength + Base::GROUP_NAME_MAX_LEN; 371 | 372 | $requestHeader = self::buildHeader(14, $requestBodyLength); 373 | 374 | $requestBody = self::packU64($offset) . self::packU64($length) . self::padding($groupName, Base::GROUP_NAME_MAX_LEN); 375 | $requestBody .= $filePath; 376 | 377 | $this->send($requestHeader . $requestBody); 378 | 379 | $responseHeader = $this->read(Base::HEADER_LENGTH); 380 | $responseInfo = self::parseHeader($responseHeader); 381 | 382 | if(!!$responseInfo['status']) return FALSE; 383 | 384 | return $this->read($responseInfo['length']); 385 | } 386 | 387 | /** 388 | * 下载文件 389 | * 390 | * @param $groupName 391 | * @param $filePath 392 | * @param $targetPath 393 | * @param int $offset 394 | * @param int $length 395 | * @return bool 396 | */ 397 | public function downloadFile($groupName, $filePath, $targetPath, $offset = 0, $length = 0) 398 | { 399 | $filePathLength = strlen($filePath); 400 | $requestBodyLength = (Base::PROTO_PKG_LEN_SIZE * 2) + $filePathLength + Base::GROUP_NAME_MAX_LEN; 401 | $requestHeader = self::buildHeader(14, $requestBodyLength); 402 | $requestBody = self::packU64($offset) . self::packU64($length) . self::padding($groupName, Base::GROUP_NAME_MAX_LEN); 403 | $requestBody .= $filePath; 404 | 405 | $this->send($requestHeader . $requestBody); 406 | 407 | $responseHeader = $this->read(Base::HEADER_LENGTH); 408 | $responseInfo = self::parseHeader($responseHeader); 409 | 410 | if($responseInfo['status']) return FALSE; 411 | 412 | $fp = fopen($targetPath, 'w+b'); 413 | stream_copy_to_stream($this->_sock, $fp, $responseInfo['length'], 0); 414 | fclose($fp); 415 | 416 | 417 | return true; 418 | } 419 | 420 | /** 421 | * 检索文件信息 422 | * 423 | * @command 22 424 | * @param string $groupName 组名称 425 | * @param string $filePath 文件路径 426 | * @return array 427 | */ 428 | public function getFileInfo($groupName, $filePath) { 429 | 430 | $requestBodyLength = strlen($filePath) + Base::GROUP_NAME_MAX_LEN; 431 | $requestHeader = self::buildHeader(22, $requestBodyLength); 432 | $requestBody = self::padding($groupName, Base::GROUP_NAME_MAX_LEN) . $filePath; 433 | 434 | $this->send($requestHeader . $requestBody); 435 | 436 | $responseHeader = $this->read(Base::HEADER_LENGTH); 437 | $responseInfo = self::parseHeader($responseHeader); 438 | 439 | if(!!$responseInfo['status']) return FALSE; 440 | 441 | $responseBody = $responseInfo['length'] 442 | ? $this->read($responseInfo['length']) 443 | : FALSE; 444 | 445 | $fileSize = self::unpackU64(substr($responseBody, 0, Base::PROTO_PKG_LEN_SIZE)); 446 | $timestamp = self::unpackU64(substr($responseBody, Base::PROTO_PKG_LEN_SIZE, Base::PROTO_PKG_LEN_SIZE)); 447 | list(,,$crc32) = unpack('N2', substr($responseBody, 2 * Base::PROTO_PKG_LEN_SIZE, Base::PROTO_PKG_LEN_SIZE)); 448 | $crc32 = base_convert(sprintf('%u', $crc32), 10, 16); 449 | $storageId = trim(substr($responseBody, -16)); 450 | 451 | return array( 452 | 'file_size' => $fileSize, 453 | 'timestamp' => $timestamp, 454 | 'crc32' => $crc32, 455 | 'storage_id' => $storageId 456 | ); 457 | 458 | } 459 | 460 | } -------------------------------------------------------------------------------- /Tracker.php: -------------------------------------------------------------------------------- 1 | send($req_header . $req_body); 30 | 31 | $res_header = $this->read(Base::HEADER_LENGTH); 32 | $res_info = self::parseHeader($res_header); 33 | 34 | if($res_info['status'] !== 0) { 35 | 36 | throw new FastDFSException( 37 | 'something wrong with get storage by group name', 38 | $res_info['status']); 39 | 40 | return FALSE; 41 | } 42 | 43 | $res_body = !!$res_info['length'] 44 | ? $this->read($res_info['length']) 45 | : ''; 46 | 47 | $group_name = trim(substr($res_body, 0, Base::GROUP_NAME_MAX_LEN)); 48 | $storage_addr = trim(substr($res_body, Base::GROUP_NAME_MAX_LEN, 49 | Base::IP_ADDRESS_SIZE - 1)); 50 | 51 | list(,,$storage_port) = unpack('N2', substr($res_body, 52 | Base::GROUP_NAME_MAX_LEN + Base::IP_ADDRESS_SIZE - 1, 53 | Base::PROTO_PKG_LEN_SIZE)); 54 | 55 | $storage_index = ord(substr($res_body, -1)); 56 | 57 | 58 | return array( 59 | 'group_name' => $group_name, 60 | 'storage_addr' => $storage_addr, 61 | 'storage_port' => $storage_port, 62 | 'storage_index' => $storage_index 63 | ); 64 | 65 | 66 | } 67 | 68 | } 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /testcase.php: -------------------------------------------------------------------------------- 1 | applyStorage('lms'); 16 | 17 | var_dump($storage_info); 18 | 19 | 20 | $group_name = 'lms'; 21 | //$file_path = 'M00/00/00/CgAABVFYZgmAQ_9nAKnrXobBHdI433.rar'; 22 | //$appender_file_path = 'M00/00/00/CgAABVFc8duEOo6HAAAAAD1cKVQ817.txt'; 23 | 24 | $storage = new FastDFS\Storage($storage_info['storage_addr'], $storage_info['storage_port']); 25 | 26 | var_dump( 27 | $storage->downloadToFile('lms', 'M00/00/00/qMB6HVLYiECAJ2KTACvD-UdfQeU480.pdf', __DIR__ . '/test.pdf') 28 | //$storage->uploadFile($storage_info['storage_index'], 'F:\\Downloads\\desktop.ini') 29 | //$storage->getFileInfo($group_name, $file_path), 30 | //$storage->deleteFile($group_name, $file_path), 31 | //$storage->setFileMetaData($group_name, $file_path, array( 32 | // 'time' => time() 33 | //), 2), 34 | //$storage->uploadSlaveFile('I:\\FastDFS_v4.06\\FastDFS\\HISTORY', $file_path, 'randdom', 'txt'), 35 | //$storage->getFileInfo($group_name, $file_path) 36 | //$storage->getFileMetaData($group_name, $file_path) 37 | //$storage->downloadFile($group_name, $file_path) 38 | //$storage->uploadAppenderFile($storage_info['storage_index'], 'I:\\FastDFS_v4.06\\FastDFS\\HISTORY', 'txt') 39 | //$storage->appendFile('TEST' . time() . PHP_EOL, $appender_file_path) 40 | //$storage->modifyFile('I:\\FastDFS_v4.06\\FastDFS\\INSTALL', $appender_file_path, 0) 41 | ); 42 | 43 | $time_end = microtime(TRUE); 44 | 45 | printf("[内存最终使用: %.2fMB]\r\n", memory_get_usage() /1024 /1024 ); 46 | printf("[内存最高使用: %.2fMB]\r\n", memory_get_peak_usage() /1024 /1024) ; 47 | printf("[页面执行时间: %.2f毫秒]\r\n", ($time_end - $time_start) * 1000 ); --------------------------------------------------------------------------------