|   | php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | 
| 
  [2005-04-15 13:59 UTC] felix at zango dot com
 Description:
------------
I am experiencing very slow file transfers when using ssh2_scp_send or php wrappers with sftp, usually around 30KB/sec on an internal network.  I am able to open a SSH connection, use SFTP / SCP without any problems using the PHP wrappers to mkdir, rmdir, unlink.
I was wondering if you knew a way to increase the buffer size or simply make it transfer faster.
FYI: I included part of stack trace of the PHP script trying to send a file:
[?]
send(3, "E-D:\246\346\25M\16\276\307\325\363\216\31*\334~\210\213"..., 1524, MSG_NOSIGNAL) = 1524
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfff9370, 1, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfff9370, 8, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
nanosleep({0, 250000000}, NULL)         = 0
recv(3, "3\365\21\311Ig\343\0", 8, MSG_NOSIGNAL) = 8
recv(3, "o\256\301NM\30\2175", 8, MSG_NOSIGNAL) = 8
recv(3, "\273\354}\265\275\271%a\225\310\tS\234\340\n\311M\32\242"..., 20, MSG_NOSIGNAL) = 20
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfff9370, 1, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfff9380, 1, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDONLY)           = 0
getpid()                                = 10218
getpid()                                = 10218
send(3, "\254\367\246\25\234ho\262\344?z\212\205\"2\246Z\311\247"..., 36, MSG_NOSIGNAL) = 36
munmap(0xb7a6c000, 356352)              = 0
fcntl64(3, F_SETFL, O_RDONLY)           = 0
getpid()                                = 10218
getpid()                                = 10218
send(3, "\24s\306\313\227\356\17V\35=P\216\352\350\344\272\2\327"..., 52, MSG_NOSIGNAL) = 52
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfffb3b0, 1, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfffb3b0, 8, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
nanosleep({0, 250000000}, NULL)         = 0
recv(3, "\354EJl{\211}\343", 8, MSG_NOSIGNAL) = 8
recv(3, "\270\206Dl\253\202m\n", 8, MSG_NOSIGNAL) = 8
recv(3, "\4\316\34o!\351\333\357\204\216\250v\327g\262\231\37\23"..., 20, MSG_NOSIGNAL) = 20
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfffb3b0, 1, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
recv(3, 0xbfffb3c0, 1, MSG_NOSIGNAL)    = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDONLY)           = 0
getpid()                                = 10218
getpid()                                = 10218
send(3, "4\374KL\360\20e\205\314j-\2\25293r/r\367\367\253\325f\375"..., 36, MSG_NOSIGNAL) = 36
close(4)                                = 0
gettimeofday({1113586396, 438326}, NULL) = 0
lseek(1, 127, SEEK_SET)                 = -1 ESPIPE (Illegal seek)
write(1, "< done in 11sec (29.9516 KB/sec)"..., 38) = 38
msgrcv(
[?]
Reproduce code:
---------------
<?
define('SFTP_ADDR', '10.0.0.2');
$start_time = microtime(true);
$connection = ssh2_connect(SFTP_ADDR, 22);
$sftp = ssh2_sftp($connection);
$src_file = '/var/www/data.src';
$dst_sftp_file = 'ssh2.sftp://' . $sftp . '/www/data.dst';
$dst_scp_file = '/www/data.dst';
// using SFTP wrapper
copy($src_file, $dst_sftp_file);
// using SCP
ssh2_scp_send($connection, $src_file, $dst_scp_file);
print 'Total Time: ' . microtime(true) - $start_time;
?>
Expected result:
----------------
Expected: FAST file transfer, over 3MB/sec
Actual result:
--------------
Result: SLOW file transfer, around 30KB/sec
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits             | |||||||||||||||||||||||||||
|  Copyright © 2001-2025 The PHP Group All rights reserved. | Last updated: Fri Oct 31 11:00:01 2025 UTC | 
I also tested using ssh2_exec to simply do a file tansfer using cat > filename, however I am limited to 128k of transfer per call (so multiple cat >> filename are necessary after closing & opening new streams after 128k are written) With this method I achieved 45KB/sec connected on 127.0.0.1 compared to around 30KB/sec when using SFTP or SCP. Is there a way to increase the size of the write buffers or just do some magic to make it faster? BTW, my reproduce code above is bugged, here is something that actually works: #!/usr/local/php5/bin/php <?php $src_file = 'test/data.src'; $dst_file = 'mirrored/data.dst'; // get filesize $filesize = filesize($src_file); // establish connection to FTP over SSH $connection = ssh2_connect('127.0.0.1', 22); ssh2_auth_password($connection, 'user', 'password'); $time = microtime(true); // ** SFTP ** /* $sftp = ssh2_sftp($connection); print "Connected to FTP over SSH\n"; copy($src_file, 'ssh2.sftp://' . $sftp . $dst_file); */ // ** SSH + cat ** /* $fp_in = fopen($src_file, 'r'); $stream = ssh2_exec($connection, 'cat > ' . $dst_file); $written_bytes = 0; while ($written_bytes < $filesize) { $written_bytes += stream_copy_to_stream($fp_in, $stream, 131072); if ($written_bytes < $filesize) { fclose($stream); $stream = ssh2_exec($connection, 'cat >> ' . $dst_file); } } fclose($fp_in); fclose($stream); */ $time = microtime(true) - $time; print 'Time: ' . round($time, 2) . ' :: ' . round($filesize / 1024 / $time, 2) . "KB/sec\n"; ?>I was wondering why I saw so many nanosleep({0, 250000000}, NULL) = 0 in my stack trace. After some investigating, I found this is caused by this: libssh2/src/packet.c: libssh2_blocking_read() ... ret = recv(session->socket_fd, buf + bytes_read, count - bytes_read, LIBSSH2_SOCKET_RECV_FLAGS(session)); ... this line very often return -1 because of socket error: EAGAIN (normal non blocking msg) for test purposes, I modified the function to use select() calls and the speed increased from 30k/sec to 150k/sec. Basically I avoid all the 0.25 sec sleep that occures everytime a EAGAIN occures. If you add this up for a normal buffer length of 8k, it means a maximum of 32kb/sec if each read returns -1 once. Here is the new beginning I modified: /* {{{ libssh2_blocking_read * Force a blocking read, regardless of socket settings */ static int libssh2_blocking_read(LIBSSH2_SESSION *session, unsigned char *buf, size_t count) { size_t bytes_read = 0; int polls = 0; /* added socket select code */ struct timeval timeout; timeout.tv_sec = 30; timeout.tv_usec = 0; int socket_count; while (bytes_read < count) { int ret; /* added socket select code */ fd_set read_socket; FD_ZERO(&read_socket); FD_SET(session->socket_fd, &read_socket); socket_count = select(session->socket_fd+1, &read_socket, NULL, NULL, &timeout); if (socket_count <= 0) { return -1; } That's that. I realize that your socket handling is not quite optimal and I suspect a similar problem also occures when establishing a connection. In my debugging, I found thousands of recv returning EAGAIN before the connection was succesfully completed. I was wondering if your streams support the set_buffer function as this would also help reduce the problem. I hope this helps you complete your debugging and cleans the way for a stable package!if you dont want to use fclose($stream), you can patch it! for closing SFTP subsystems, Publickey Subsystems and Tunnels; use ssh2_subsys_close() open ssh2.c file. 1) Add following lines before " PHP_FUNCTION(ssh2_fingerprint) " and after " /* }}} */ " /* {{{ proto stream ssh2_subsys_close(stream channel) * Close the SFTP subsystem/Publickey Subsystem or a Listener and return a true on success, false on error */ PHP_FUNCTION(ssh2_subsys_close) { zval *zsubsys; /* check & parse parameters */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zsubsys) == FAILURE) { RETURN_FALSE; } /* decrease the refcount and call destructor if needed */ if (zend_list_delete(Z_LVAL_P(zsubsys)) == FAILURE) { RETURN_FALSE; } /* destruct pointer */ zval_ptr_dtor(&zsubsys); RETURN_TRUE; } /* }}} */ 2) Declare the ssh2_subsys_close() function again by adding a line after the existing PHP_FE() line. find following line : PHP_FE(ssh2_connect, NULL) after add: PHP_FE(ssh2_subsys_close, NULL) ---- for closing channels (ssh2_exec, ssh2_shell) use ssh2_channel_close() 1) Add following lines to the end of "ssh2_fopen_wrappers.c" file /* ****************** * Channel Closer * ****************** */ /* {{{ proto stream ssh2_channel_close(stream channel) * Close a SSH2 Channel */ PHP_FUNCTION(ssh2_channel_close) { php_stream *channel; zval *zchannel; /* check & parse parameters */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zchannel) == FAILURE) { RETURN_FALSE; } php_stream_from_zval(channel, &zchannel); /* close php stream */ php_stream_close(channel); /* destruct pointer */ zval_ptr_dtor(&zchannel); RETURN_TRUE; } /* }}} */ 2) Declare the ssh2_channel_close() function again by adding a line after the existing PHP_FE() line. find following line (in ssh2.c) : PHP_FE(ssh2_fetch_stream, NULL) after add: PHP_FE(ssh2_channel_close, NULL) find following line (in php_ssh2.h) : PHP_FUNCTION(ssh2_fetch_stream); after add: PHP_FUNCTION(ssh2_channel_close);