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

发布者 kris  发布时间 1493294498882
关键字 JS学习  Node.JS 
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客户端与服务器通信的过程:


响应: 220-FileZilla Server version 0.9.43 beta
响应: 220-written by Tim Kosse (
响应: 220 Please visit
响应: 502 SSL/TLS authentication not allowed
响应: 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
响应:  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 "/"


"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"


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."



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.')


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) {
    //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)

        var cmds  = line.split(' ')
        var cmd   = cmds[0].toUpperCase()
        var arg   = cmds.slice(1)

        var func  = COMMANDS[cmd]

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

    .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

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

FTP 服务器端模块




const FtpSvr    = require('ftp-srv')
const ftpServer = new FtpSvr('')

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 {

  .then(() => {



var FTPServer = require('ftpserver').FTPServer

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

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



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

FTP 客户端模块



项目地址 ,示例

var JSFtp = require("jsftp");

var Ftp = new JSFtp({
    host: "",
    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



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




var FTPS = require('ftps');

var ftps = new FTPS({
  host: '', // 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 : s in this case
  port: 22, // Optional
  // port is added to the end of the host, ex: s 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'some_directory').addFile(__dirname + '/test.txt').exec(console.log);



回复 (1)
微信扫码 立即评论

 热门文章 - 分享最多
  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. 周鸿祎:一些程序员没有商业意识,却又很自负,一看就知道不会创业

  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数据改写

  开源的 OurJS
OurJS开源博客已经迁移到 OnceOA 平台。