OurJS


OurJS-我们的JS, 我们的技术-IT文摘; 专注JS相关领域;
我们热爱编程, 我们热爱技术;我们是高大上, 有品味的码农;

欢迎您订阅我们的技术周刊


我们会向您分享我们精心收集整理的,最新的行业资讯,技术动态,外文翻译,热点文章;
我们使用第三方邮件列表向您推送,我们不保存您的任何个人资料,注重您的隐私,您可以随时退订,

欢迎分享您的观点,经验,技巧,心得

让我们一起找寻程序员的快乐,探索技术, 发现IT人生的乐趣;


本网站使用缓存技术每次加载仅需很小流量, 可在手机中流畅浏览;
如果您发现任何BUG,请即时告知我们: ourjs(at)ourjs.com

Node.JS用Socket实现FTP Server服务器和Client客户端


分享到
分类 JS学习   关键字 Node.JS   发布 kris  1493294498882
注意 转载须保留原文链接,译文链接,作者译者等信息。  
FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协议把网页或程序传到Web服务器上。此外,由于FTP传输效率非常高,在网络上传输大的文件时,一般也采用该协议。 默认情况下FTP协议使用TCP端口中的 20和21这两个端口,其中20用于传输数据,21用于传输控制信息。但是,是否使用20作为传输数据的端口与FTP使用的传输模式有关,如果采用主动模式,那么数据传输端口就是20;如果采用被动模式,则具体最终使用哪个端口要服务器端和客户端协商决定。

通信过程

FTP协议其实就是主机和服务通过Socket进行固定格式的通信过程,当某客户端连接到FTP 服务器后,客户端发送指令:

<指令> [参数] <命令结束符:"\r\n"> 

服务会按以下格式返回:

<状态码> [参数或说明] <命令结束符:"\r\n"> 

例如以下是FileZilla FTP客户端与服务器通信的过程:

"命令"为客户端通过socket发送的消息,“响应”为服务器端的返回

响应: 220-FileZilla Server version 0.9.43 beta
响应: 220-written by Tim Kosse (tim.kosse@filezilla-project.org)
响应: 220 Please visit http://sourceforge.net/projects/filezilla/
命令: AUTH TLS
响应: 502 SSL/TLS authentication not allowed
命令: AUTH SSL
响应: 502 SSL/TLS authentication not allowed
命令: USER newghost
响应: 331 Password required for newghost
命令: PASS **************
响应: 230 Logged on

命令: SYST
响应: 215 UNIX emulated by FileZilla
命令: FEAT
响应: 211-Features:
响应:  MDTM
响应:  REST STREAM
响应:  SIZE
响应:  MLST type*;size*;modify*;
响应:  MLSD
响应:  UTF8
响应:  CLNT
响应:  MFMT
响应: 211 End
命令: PWD
响应: 257 "/" is current directory.
命令: TYPE I
响应: 200 Type set to I
命令: PASV
响应: 227 Entering Passive Mode (121,42,140,131,14,77)
命令: MLSD
响应: 150 Opening data channel for directory listing of "/"
响应: 226 Successfully transferred "/"

以下是FTP中的命令列表

{
"ABOR": "Abort an active file transfer.",
"ACCT": "Account information.",
"ADAT": "Authentication/Security Data",
"ALLO": "Allocate sufficient disk space to receive a file.",
"APPE": "Append.",
"AUTH": "Authentication/Security Mechanism",
"CCC": "Clear Command Channel",
"CDUP": "Change to Parent Directory.",
"CONF": "Confidentiality Protection Command",
"CWD": "Change working directory.",
"DELE": "Delete file.",
"ENC": "Privacy Protected Channel",
"EPRT": "Specifies an extended address and port to which the server should connect.",
"EPSV": "Enter extended passive mode.",
"FEAT": "Get the feature list implemented by the server.",
"HELP": "Returns usage documentation on a command if specified, else a general help document is returned.",
"HOST": "Identify desired virtual host on server, by name.",
"LANG": "Language Negotiation",
"LIST": "Returns information of a file or directory if specified, else information of the current working directory is returned.",
"LPRT": "Specifies a long address and port to which the server should connect.",
"LPSV": "Enter long passive mode.",
"MDTM": "Return the last-modified time of a specified file.",
"MFCT": "Modify the creation time of a file.",
"MFF": "Modify fact (the last modification time, creation time, UNIX group/owner/mode of a file).",
"MFMT": "Modify the last modification time of a file.",
"MIC": "Integrity Protected Command",
"MKD": "Make directory.",
"MLSD": "Lists the contents of a directory if a directory is named.",
"MLST": "Provides data about exactly the object named on its command line, and no others.",
"MODE": "Sets the transfer mode (Stream, Block, or Compressed).",
"NLST": "Returns a list of file names in a specified directory.",
"NOOP": "No operation (dummy packet; used mostly on keepalives).",
"OPTS": "Select options for a feature (for example OPTS UTF8 ON).",
"PASS": "Authentication password.",
"PASV": "Enter passive mode.",
"PBSZ": "Protection Buffer Size",
"PORT": "Specifies an address and port to which the server should connect.",
"PROT": "Data Channel Protection Level.",
"PWD": "Print working directory. Returns the current directory of the host.",
"QUIT": "Disconnect.",
"REIN": "Re initializes the connection.",
"REST": "Restart transfer from the specified point.",
"RETR": "Retrieve a copy of the file",
"RMD": "Remove a directory.",
"RNFR": "Rename from.",
"RNTO": "Rename to.",
"SITE": "Sends site specific commands to remote server (like SITE IDLE 60 or SITE UMASK 002). Inspect SITE HELP output for complete list of supported commands.",
"SIZE": "Return the size of a file.",
"SMNT": "Mount file structure.",
"SPSV": "Use single port passive mode (only one TCP port number for both control connections and passive-mode data connections)",
"STAT": "Returns the current status.",
"STOR": "Accept the data and to store the data as a file at the server site",
"STOU": "Store file uniquely.",
"STRU": "Set file transfer structure.",
"SYST": "Return system type.",
"TYPE": "Sets the transfer mode (ASCII/Binary).",
"USER": "Authentication username.",
"XCUP": "Change to the parent of the current working directory",
"XMKD": "Make a directory",
"XPWD": "Print the current working directory",
"XRCP": "",
"XRMD": "Remove the directory",
"XRSQ": "",
"XSEM": "Send, mail if cannot",
"XSEN": "Send to terminal"
}

以下是FTP中的返回状态码列表:

var REPLY_CODE = {
  "110": "Restart marker reply.",
  "120": "Service ready in nn minutes.",
  "125": "Data Connection already open; transfer starting.",
  "150": "File status okay; about to open data connection.",
  "200": "Command okay.",
  "202": "Command not implemented, superfluous at this site.",
  "211": "System status, or system help reply.",
  "212": "Directory status.",
  "213": "File status.",
  "214": "Help message.",
  "215": "NAME system type.",
  "220": "Service ready for new user.",
  "221": "Service closing control connection.",
  "225": "Data connection open; no transfer in progress.",
  "226": "Closing data connection.",
  "227": "Entering Passive Mode.",
  "230": "User logged in, proceed. This status code appears after the client sends the correct password. It indicates that the user has successfully logged on.",
  "250": "Requested file action okay, completed.",
  "257": "'\"'PATHNAME'\"' created.",
  "331": "User name okay, need password.",
  "332": "Need account for login.",
  "350": "Requested file action pending further information.",
  "421": "Error 421 Service not available, closing control connection.\n            Error 421 User limit reached\n            Error 421 You are not authorized to make the connection\n            Error 421 Max connections reached\n            Error 421 Max connections exceeded",
  "425": "Cannot open data connection.",
  "426": "Connection closed; transfer aborted.",
  "450": "Requested file action not taken.",
  "451": "Requested action aborted: local error in processing.",
  "452": "Requested action not taken. Insufficient storage space in system.",
  "500": "Syntax error, command unrecognized, command line too long.",
  "501": "Syntax error in parameters or arguments.",
  "502": "Command not implemented.",
  "503": "Bad sequence of commands.",
  "504": "Command not implemented for that parameter.",
  "530": "User not logged in.",
  "532": "Need account for storing files.",
  "550": "Requested action not taken. File unavailable, not found, not accessible",
  "552": "Requested file action aborted. Exceeded storage allocation.",
  "553": "Requested action not taken. File name not allowed.",
  "10054": "Connection reset by peer. The connection was forcibly closed by the remote host.",
  "10060": "Cannot connect to remote server.",
  "10061": "Cannot connect to remote server. The connection is actively refused by the server.",
  "10066": "Directory not empty.",
  "10068": "Too many users, server is full."
}

一个简易的FTP服务器

通过以上协议,我们可以编写一个简易的实现了登陆过程的FTP服务器,

var net = require('net')

var COMMANDS = {
  AUTH: function() {
    this.send('502 SSL/TLS authentication not allowed.')
  },
  USER: function(username) {
    this.session.username = username
    this.send('331 User name okay, need password.')
  },
  PASS: function(password) {
    var socket    = this
    var username  = socket.username

    if (username == 'newghost' && password == 'dachun') {
      socket.send(230, 'Logged on')
    } else {
      socket.send(450, 'Ensure that you typed the correct user name and password combination.')
    }
  },
  PWD: function(args) {
    this.send('257 "/" is current directory')
  },
  TYPE: function(args) {
    this.send('200 Type set to I')
  },
  EPSV: function(args) {
    this.send('229 Entering Extended Passive Mode (|||30324|).')
  },
  PASV: function(args) {
    this.send('227 Entering Passive Mode (112,124,126,185,165,12).')
  },
  MLSD: function(args) {
    this.send('226 Successfully transferred "/"')
  },
  LIST: function(args) {
    this.send('502 Command not implemented.')

    this.send('502')
  }
}


var sendHandler = function(type, message) {
  var socket  = this
  var command

  if (arguments.length < 2) {
    if (REPLY_CODE[type]) {
      command = REPLY_CODE[type]
    } else {
      command = type.toString()
    }
  } else {
    command = type + ' ' + message
  }

  console.log('S:', command)

  socket.write(command + '\r\n')
}

var ftpServer = net.createServer(function(socket) {
  socket.session  = {}
  socket.send     = sendHandler

  socket.send(220, 'Welcome to OnceDoc FTP Server')

  var onCommand = function(buffer) {
    //receives.push(data)
    //var buffer  = Buffer.concat(receives).toString()
    //receives = []
    var buffers = buffer.toString()
    var lines   = buffers.split('\r\n')

    for (var i = 0, l = lines.length; i < l; i++) {
      var line = lines[i]
      if (line) {
        console.log('C:', line)

        //lines.push(raw[i])
        var cmds  = line.split(' ')
        var cmd   = cmds[0].toUpperCase()
        var arg   = cmds.slice(1)

        var func  = COMMANDS[cmd]

        func
          ? func.apply(socket, arg)
          : socket.send(502)
      }
    }
  }

  socket
    .on('data', onCommand)
    .on('end',  function() {
      console.log('end', arguments)
    })
    .on('close', function () {
      console.log('close', arguments)
    })
    .on('timeout', function () {
      console.log('timeout', arguments)
    })
    .on('error', function (err) {
      console.log('error', arguments)
    })

}).on('error', function(err) {
  // handle errors here
  console.error(err)
})

//
ftpServer.listen({ port: 21 }, function() {
  console.log('opened server on', ftpServer.address())
})

FTP 服务器端模块

目前用Node.JS实现的服务端模块还不多,大多不是很成熟,如

ftp-srv

项目主页 https://github.com/stewarttylerr/ftp-srv

const FtpSvr    = require('ftp-srv')
const ftpServer = new FtpSvr('ftp://127.0.0.1:21')

ftpServer.on('login', function (data, resolve, reject) {
  var connection  = data.connection
  var username    = data.username
  var password    = data.password

  if (data.username == 'anonymous') {
    resolve({ root: 'D:\\github\\oncedoc\\onceoa' })
  } else {
    reject()
  }
})

ftpServer
  .listen()
  .then(() => {
    console.log('ready')
  })

ftpServer

项目主页 https://github.com/stewarttylerr/ftpserver

var FTPServer = require('ftpserver').FTPServer

var ftpServer = new FTPServer({
  host: '127.0.0.1',
  port: 21,
  pasvStart: null,
  pasvEnd: null,
  timeout: 30000,
  disabledCommands: [],
  anonymous: false,
  logLevel: 10,
  greeting: null,
  override: {
    fs: null,
    authentication: null
  }
})

ftpServer.listen().then(() => {

})

nodeftpd

nodeftpd是一套可以独立支行的node.js ftp服务器,相对较为成熟 https://github.com/brandonwamboldt/nodeftpd

FTP 客户端模块

目前基于node.js的FTP客户端非常成熟,开源项目也比较多

JSFTP

项目地址 https://github.com/sergi/jsftp ,示例

var JSFtp = require("jsftp");

var Ftp = new JSFtp({
    host: "myserver.com",
    port: 3331, // defaults to 21
    user: "user", // defaults to "anonymous"
    pass: "1234" // defaults to "@anonymous"
});

Ftp.raw("mkd", "/new_dir", function(err, data) {
    if (err) return console.error(err);

    console.log(data.text); // Show the FTP response text to the user
    console.log(data.code); // Show the FTP response code to the user
});

node-ftp

项目地址 https://github.com/mscdex/node-ftp,示例

var c = new Client();
c.on('ready', function() {
  c.list(function(err, list) {
    if (err) throw err;
    console.dir(list);
    c.end();
  });
});
// connect to localhost:21 as anonymous
c.connect();

node-ftps

项目地址 https://github.com/Atinux/node-ftps

这个项目是对lftp的node.js封装

var FTPS = require('ftps');

var ftps = new FTPS({
  host: 'domain.com', // required
  username: 'Test', // Optional. Use empty username for anonymous access.
  password: 'Test', // Required if username is not empty, except when requiresPassword: false
  protocol: 'sftp', // Optional, values : 'ftp', 'sftp', 'ftps', ... default: 'ftp'
  // protocol is added on beginning of host, ex : sftp://domain.com in this case
  port: 22, // Optional
  // port is added to the end of the host, ex: sftp://domain.com:22 in this case
  escape: true, // optional, used for escaping shell characters (space, $, etc.), default: true
  retries: 2, // Optional, defaults to 1 (1 = no retries, 0 = unlimited retries)
  timeout: 10, // Optional, Time before failing a connection attempt. Defaults to 10
  retryInterval: 5, // Optional, Time in seconds between attempts. Defaults to 5
  retryMultiplier: 1, // Optional, Multiplier by which retryInterval is multiplied each time new attempt fails. Defaults to 1
  requiresPassword: true, // Optional, defaults to true
  autoConfirm: true, // Optional, is used to auto confirm ssl questions on sftp or fish protocols, defaults to false
  cwd: '', // Optional, defaults to the directory from where the script is executed
  additionalLftpCommands: '', // Additional commands to pass to lftp, splitted by ';'
  requireSSHKey:  true, //  Optional, defaults to false, This option for SFTP Protocol with ssh key authentication
  sshKeyPath: '/home1/phrasee/id_dsa' // Required if requireSSHKey: true , defaults to empty string, This option for SFTP Protocol with ssh key authentication
});

// Do some amazing things
ftps.cd('some_directory').addFile(__dirname + '/test.txt').exec(console.log);
社区评论 ( Beta版 )
OnceDoc 您自己的企业内容管理系统——文档、流程、知识库、报表、网盘All In One

访问404页面,寻找丢失儿童
 热门文章 - 分享最多
  1. JavaScript使用ES6的Class面向对象继承时 this is not defined 解决方法
  2. Java已快过时?斯坦福大学将JavaScript作为计算机科学入门课
  3. JavaScript条形码生成和扫码识别(Barcode scan)开源库
  4. Docker改名Moby:急于商业化陷入品牌更名乱象
  5. TCP/UDP协议比较:在Node.JS中UDP服务器和客户端通信示例
  6. OnceVI前后端分离的数据可视化报表工具简介
  7. Node.JS通过原型和类继承EventEmitter,实现收发事件的几种方法
  8. 2016 年崛起的 JS 项目
  9. 如何基于SVG矢量图制作一个可填写信息的可视化表单-OnceVI
  10. 周鸿祎:一些程序员没有商业意识,却又很自负,一看就知道不会创业
  11. AirJD-简单好用的免费建站工具

 相关阅读 - JS学习
  1. Node.JS更改Windows注册表regedit的几种方法
  2. Debian下设置Linux Shell脚本开机自动启动Node.JS进程
  3. Node.JS通过原型和类继承EventEmitter,实现收发事件的几种方法
  4. 可视化Web报表OnceVI中用户输入表单验证与提交
  5. Web报表OnceVI如何制作条形码与打印二维码(Barcode/Qrcode)
  6. 如何基于SVG矢量图制作一个可填写信息的可视化表单-OnceVI
  7. OnceVI报表制作入门—如何将用户的json数据可视化成名片展示
  8. OnceDB支持全文搜索和关系查询的Redis内存数据库:驱动安装及使用教程
  9. 在OnceIO(Node.JS)中用Redis储存Session
  10. OnceIO的模块拦截与注入:模板文件路由重定向与Model数据改写

 关键字 - Node.JS
  1. node.js中fs.stat里的atime,mtime,ctime,birthtime在debian下的更新规则与区别
  2. NodeJS的DNS使用:域名解析,MX记录查询,SPF反垃圾邮件测试
  3. 在nodejs中使用Redis缓存和查询数据及Session持久化(Express)
  4. 是什么让Node.js比Java更快?为什么NodeJS这么快?
  5. 用纯Node.JS弹出Windows系统消息提示框(MessageBox)
  6. 利用Windows注册表将你的Node.JS程序添加到右健菜单直接运行
  7. Node.js中的通用基础设计模式
  8. Node.JS中如何判断递归嵌套的所有回调函数已经执行完毕,以读取目录下所有文件为例:计数比Promise方式快将近一倍
  9. Node.js网页抓取:一个最简单的http请求客户端示例(request client)
  10. 6款基于Node.JS的开源内容管理和静态网站生成系统

 欢迎订阅 - 技术周刊

我们热爱编程, 我们热爱技术; 我们是高端, 大气, 上档次, 有品味, 时刻需要和国际接轨的码农; 欢迎您订阅我们的技术周刊; 您只需要在右上角输入您的邮箱即可; 我们注重您的隐私,您可以随时退订.
加入我们吧! 让我们一起找寻码农的快乐,探索技术, 发现IT人生的乐趣;


 关注我们

我们的微信公众号: ourjs-com
打开微信扫一扫即可关注我们:
IT文摘-程序员(码农)技术周刊

ourjs官方微信号