9

我正在尝试编写一个针对 Filezilla 的 ftp 客户端,该客户端支持使用 node.js 的活动模式。我是 ftp 和 node.js 的新手。我想通过做这个练习我可以很好地理解 tcp socket 通信和 ftp 协议。另外,node-ftpjsftp似乎不支持主动模式,所以我认为这将是对 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合适,或者我应该以其他方式进行吗?

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

4

1 回答 1

1

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

  • 连接前导 ( USER/ PASS)
  • 为数据建立客户端本地套接字
  • 通知服务器该套接字 ( PORT)
  • 告诉服务器打开远程文件进行写入 ( STOR)
  • 从上面建立的数据套接字开始写入数据(socket.write()
  • 从客户端关闭流(socket.end())以结束文件传输
  • 告诉服务器您已完成 ( QUIT)
  • 清理客户端上所有打开的套接字和服务器

所以一旦你这样做了:

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);
}
于 2016-08-15T18:01:19.550 回答