提问者:小点点

Node.js的活动FTP客户端


我正在尝试针对Filezilla编写一个支持使用node. js的活动模式的ftp客户端。我是ftp和node.js的新手。我想通过做这个练习,我可以很好地理解tcp套接字通信和ftp协议。此外,node-ftp和jsftp似乎不支持活动模式,所以我认为这将是一个很好的(尽管很少使用)npm补充。

我有一些概念验证代码,至少有时有效,但不是所有时间。在它工作的情况下,客户端上传一个名为file的文件.txt带有文本“hi”。当它起作用时,我得到这个:

220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse@gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/

331 Password required for testuser

230 Logged on

listening
200 Port command successful

150 Opening data channel for file transfer.

server close
226 Transfer OK

half closed
closed

Process finished with exit code 0

当它不起作用时,我得到这个:

220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse@gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/

331 Password required for testuser

230 Logged on

listening
200 Port command successful

150 Opening data channel for file transfer.

server close
half closed
closed

Process finished with exit code 0

所以,我没有得到 226,我不确定为什么我得到的结果不一致。

原谅写得不好的代码。一旦我确信我理解了这应该如何工作,我就会重构。:

var net = require('net'),
    Socket = net.Socket;

var cmdSocket = new Socket();
cmdSocket.setEncoding('binary')

var server = undefined;
var port = 21;
var host = "localhost";
var user = "testuser";
var password = "Password1*"
var active = true;
var supplyUser = true;
var supplyPassword = true;
var supplyPassive = true;
var waitingForCommand = true;
var sendFile = true;

function onConnect(){

}

var str="";
function onData(chunk) {
    console.log(chunk.toString('binary'));

    //if ftp server return code = 220
    if(supplyUser){
        supplyUser = false;
        _send('USER ' + user, function(){

        });
    }else if(supplyPassword){
        supplyPassword = false;
        _send('PASS ' + password, function(){

        });
    }
    else if(supplyPassive){
        supplyPassive = false;
        if(active){
            server = net.createServer(function(socket){
                console.log('new connection');
                socket.setKeepAlive(true, 5000);

                socket.write('hi', function(){
                    console.log('write done');
                })

                 socket.on('connect', function(){
                    console.log('socket connect');
                });

                socket.on('data', function(d){
                    console.log('socket data: ' + d);
                });

                socket.on('error', function(err){
                    console.log('socket error: ' + err);
                });

                socket.on('end', function() {
                    console.log('socket end');
                });

                socket.on('drain', function(){
                    console.log('socket drain');

                });

                socket.on('timeout', function(){
                    console.log('socket timeout');

                });

                socket.on('close', function(){
                    console.log('socket close');

                });
            });

            server.on('error', function(e){
               console.log(e);
            });

            server.on('close', function(){
                console.log('server close');
            });

            server.listen(function(){
                console.log('listening');

                var address = server.address();
                var port = address.port;
                var p1 = Math.floor(port/256);
                var p2 = port % 256;

                _sendCommand('PORT 127,0,0,1,' + p1 + ',' + p2, function(){

                });
            });
        }else{
            _send('PASV', function(){

            });
        }
    }
    else if(sendFile){
        sendFile = false;

        _send('STOR file.txt', function(){

        });
    }
    else if(waitingForCommand){
        waitingForCommand = false;

        cmdSocket.end(null, function(){

        });

        if(server)server.close(function(){});
    }
}

function onEnd() {
    console.log('half closed');
}

function onClose(){
    console.log('closed');
}

cmdSocket.once('connect', onConnect);
cmdSocket.on('data', onData);
cmdSocket.on('end', onEnd);
cmdSocket.on('close', onClose);

cmdSocket.connect(port, host);

function _send(cmd, callback){
    cmdSocket.write(cmd + '\r\n', 'binary', callback);
}

此外,服务器是否合适,或者我应该以其他方式执行?

编辑:我更改了server.听中的回调以使用随机端口。这删除了我之前获得的425。但是,我仍然没有获得与文件传输一致的行为。


共1个答案

匿名用户

主动模式 FTP 传输的流程大致如下:

    < li >连接前同步码(< code >用户/通过) < li >为数据建立客户端本地套接字 < li >通知服务器该套接字(< code >端口) < li >告诉服务器打开远程文件进行写入(< code>STOR) < li >开始从上面建立的数据套接字写入数据(< code > socket . write()) < li >从客户端关闭流(< code>socket.end())以结束文件传输 < li >告诉服务器您已经完成(< code >退出) < li >清理客户端上任何打开的套接字和服务器

所以一旦你完成了这个:

else if(sendFile){
    sendFile = false;

    _send('STOR file.txt', function(){

    });
}

服务器将以150响应,表示它已连接到您建立的数据套接字并准备好接收数据。

此时更容易推理执行的一项改进是更改控制流以对解析的响应代码而不是预定义的布尔值进行操作。

function onData(chunk) {
  console.log(chunk);
  var code = chunk.substring(0,3);

  if(code == '220'){

而不是:

function onData(chunk) {
  console.log(chunk.toString('binary'));

  //if ftp server return code = 220
  if(supplyUser){

然后,您可以添加用于发送数据的部分:

//ready for data
else if (code == '150') {
  dataSocket.write('some wonderful file contents\r\n', function(){});
  dataSocket.end(null, function(){});
}

还有一点需要清理:

//transfer finished
else if ( code == '226') {
  _send('QUIT', function(){ console.log("Saying Goodbye");});
}

//session end
else if ( code == '221') {
  cmdSocket.end(null, function(){});
  if(!!server){ server.close(); }
}

显然,如果您发送多个文件等,事情会变得更加复杂,但这应该会让您的概念验证更加可靠:

var net = require('net');
  Socket = net.Socket;

var cmdSocket = new Socket();
cmdSocket.setEncoding('binary')

var server = undefined;
var dataSocket = undefined;
var port = 21;
var host = "localhost";
var user = "username";
var password = "password"
var active = true;

function onConnect(){
}

var str="";
function onData(chunk) {
  console.log(chunk.toString('binary'));
  var code = chunk.substring(0,3);
  //if ftp server return code = 220
  if(code == '220'){
      _send('USER ' + user, function(){
      });
  }else if(code == '331'){
      _send('PASS ' + password, function(){
      });
  }
  else if(code == '230'){
      if(active){
          server = net.createServer(function(socket){
              dataSocket = socket;
              console.log('new connection');
              socket.setKeepAlive(true, 5000);

              socket.on('connect', function(){
                  console.log('socket connect');
              });

              socket.on('data', function(d){
                  console.log('socket data: ' + d);
              });

              socket.on('error', function(err){
                  console.log('socket error: ' + err);
              });

              socket.on('end', function() {
                  console.log('socket end');
              });

              socket.on('drain', function(){
                  console.log('socket drain');
              });

              socket.on('timeout', function(){
                  console.log('socket timeout');
              });

              socket.on('close', function(){
                  console.log('socket close');
              });
          });

          server.on('error', function(e){
             console.log(e);
          });

          server.on('close', function(){
              console.log('server close');
          });

          server.listen(function(){
              console.log('listening');

              var address = server.address();
              var port = address.port;
              var p1 = Math.floor(port/256);
              var p2 = port % 256;

              _send('PORT 127,0,0,1,' + p1 + ',' + p2, function(){

              });
          });
      }else{
          _send('PASV', function(){

          });
      }
  }
  else if(code == '200'){
      _send('STOR file.txt', function(){

      });
  }
  //ready for data
  else if (code == '150') {
    dataSocket.write('some wonderful file contents\r\n', function(){});
    dataSocket.end(null, function(){});
  }

  //transfer finished
  else if ( code == '226') {
    _send('QUIT', function(){ console.log("Saying Goodbye");});
  }

  //session end
  else if ( code == '221') {
    cmdSocket.end(null, function(){});
    if(!!server){ server.close(); }
  }
}

function onEnd() {
  console.log('half closed');
}

function onClose(){
  console.log('closed');
}

cmdSocket.once('connect', onConnect);
cmdSocket.on('data', onData);
cmdSocket.on('end', onEnd);
cmdSocket.on('close', onClose);

cmdSocket.connect(port, host);

function _send(cmd, callback){
  cmdSocket.write(cmd + '\r\n', 'binary', callback);
}