侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

FTP的主动被动模式,解决上传文件大小为0字节的问题

孔子说JAVA
2022-08-07 / 0 评论 / 0 点赞 / 70 阅读 / 5,940 字 / 正在检测是否收录...

FTP的中文名称是“文件传输协议”,是File Transfer Protocol三个英文单词的缩写。FTP协议是TCP/IP协议组中的协议之一,其传输效率非常高,在网络上传输大的文件时,经常采用该协议。不同于TCP服务的单路连接,FTP协议使用双向的多个连接,而且使用的端口很难预计。所以FTP有主动模式和被动模式两种上传方式,以应对服务器的不同配置及防火墙的拦截问题。

1、了解FTP

周知端口(well-known port)是2018年公布的计算机科学技术名词,出自《计算机科学技术名词 》第三版,如TCP或UDP使用的协议端口号。它被事先分配给一些具体的服务,是已经标准化了的众所周知的端口号。

1.1 FTP介绍

一个完整的FTP由FTP服务器和FTP客户端组成,客户端可以将服务器上的文件通过FTP协议下载到本地,也可以将本地数据通过FTP协议上传到服务器上。

FTP服务器

服务器端需要安装FTP服务软件,常用的有FileZilla Server、IIS、Serv-U、OSSFTP等等。

  1. 不同的FTP软件对FTP协议标准支持有所差别,从我的使用经验感觉FileZilla Server是对FTP协议支持最好的软件,它具有以下几个特点:体积小(2M左右)、免费开源、操作简单、功能完善(我们所需要的有功能它基本都支持)。
  2. IIS对FTP协议的支持也很不错。
  3. Serv-U有几个命令支持不是很好,这个软件也是收费软件,没感觉比FileZilla Server好多少。
  4. OSSFTP是阿里云对象存储提供的FTP Server,对FTP标准协议支持就更差了。

FTP客户端

FTP客户端软件就比较多了,常用的有FileZilla、FlashFXP、WinSCP、甚至在浏览器和windows资源管理器中输入FTP地址都可以当做FTP客户端来使用。我们的FtpCopy也属于FTP客户端。

1.2 FTP连接的特殊性

大多数的TCP服务是使用单个的连接,一般是客户向服务器的一个周知端口发起连接,然后使用这个连接进行通讯。但是,FTP协议却有所不同,它使用双向的多个连接,而且使用的端口很难预计。一般,FTP连接包括:

1)一个控制连接(control connection)

这个连接用于传递客户端的命令和服务器端对命令的响应。它使用服务器的21端口,生存期是整个FTP会话session时间。(就是从open ip[port]到close这段时间)

2)几个数据连接(data connection)

这些连接用于传输文件和其它数据,例如:目录列表等。这种连接在需要数据传输时建立,而一旦数据传输完毕就关闭,每次使用的端口也不一定相同。而且,数据连接既可能是客户端发起的,也可能是服务器端发起的。

2、FTP的主动模式和被动模式

FTP支持两种模式,一种方式叫做Standard主动方式,缺省时默认的方式,一种是 Passive 被动方式。 这两种模式发起连接的方向截然相反,主动模式是从服务器端向客户端发起连接,被动模式是客户端向服务器端发起连接。

2.1 FTP主动模式

主动模式是FTP的默认模式,也称为PORT模式。在主动模式下,客户端会开启N和N+1两个端口,N为客户端的命令端口,N+1为客户端的数据端口。FTP主动模式的连接过程如下:

  1. 第一步,客户端使用端口N连接FTP服务器的命令端口21,建立控制连接并告诉服务器我这边开启了数据端口N+1。
  2. 第二步,在控制连接建立成功后,服务器会使用数据端口20,主动连接客户端的N+1端口以建立数据连接。

在数据连接建立的过程中,服务器是主动去连接客户端的,所以称这种模式为主动模式。客户端的命令端口和数据端口在实际中并不严格是上文写的N和N+1的关系,两个端口比较接近而已。

image-1659662022375

详细描述:FTP客户端首先随机选择一个大于1024的端口p1,并通过此端口发送请求连接到FTP服务器的21号端口建立TCP连接,在FTP中这个连接叫做控制连接,连接成功建立后,FTP客户端会发送port命令,紧接着FTP客户端会监视自己的p1+1端口,FTP服务器接收到port命令会从自己的20号端口向FTP客户端的p1+1端口发起请求建立TCP连接,这个连接叫做数据连接,用来发送数据,数据传输完毕后数据连接随即关闭,控制连接保持开启。

主动模式的利弊:

  • 主动模式对FTP服务器的管理有利,因为FTP服务器只需要开启21端口的“准入”和20端口的“准出”即可。
  • 但这种模式对客户端的管理不利,因为FTP服务器20端口连接客户端的数据端口时,有可能被客户端的防火墙拦截掉。

解决客户端防火墙拦截问题:

如何解决客户端防火墙拦截“数据连接”的建立呢?

  • 1)如果防火墙开启了“拦截通知”,在使用FTP软件连接服务器时,防火墙会弹出下面这个提醒,只需要点击“允许访问”就可以建立连接了。
  • 2)如果防火墙没有开启“拦截通知”,则需要我们的应用程序添加到防火墙的“允许的程序”列表中。允许FlashFXP.exe和WinSCP以主动模式连接FTP服务器。

2.2 FTP被动模式

FTP主动模式指的是服务器的主动,简单理解就是服务器的数据端口20主动连接客户端的数据端口,从而建立数据连接,用来传输数据,这个数据连接的建立有可能被客户端防火墙拦截掉。为了解决这个问题就衍生出另外一种连接模式—被动模式。被动模式也称为PASV模式(passive的缩写)。FTP被动模式的连接过程如下:

  1. 第一步,客户端的命令端口N主动连接服务器命令端口21,并发送PASV命令,告诉服务器用“被动模式”,控制连接建立成功后,服务器开启一个数据端口P,通过PORT命令将P端口告诉客户端。服务器的数据端口P是随机的,不过P端口的范围是可以设置的。
  2. 第二步,客户端的数据端口N+1去连接服务器的数据端口P,建立数据连接。

在数据连接建立的过程中,服务器是被动的等待客户端来连接的,所以称这种模式为被动模式。客户端的命令端口和数据端口在实际中并不严格是上文写的N和N+1的关系,两个端口比较接近而已。

image-1659662032577

详细描述:在建立控制连接的时候和主动模式类似,但建立连接后发送的不是Port命令,而是Pasv命令。FTP服务器收到Pasv命令后,随机打开一个临时端口(也叫自由端口,端口号大于1023小于65535)并且通知客户端在这个端口上传送数据的请求,FTP客户端发送请求连接FTP服务器此端口,成功建立连接后FTP服务器将通过这个端口进行数据的传送数据传输完毕后数据连接随即关闭,控制连接保持开启。

被动模式的利弊:

  • 被动模式对FTP客户端的管理有利,因为客户端的命令端口和数据端口都是“准出”,windows防火墙对于“准出”一般是不拦截的,所以客户端不需要任何多余的配置就可以连接FTP服务器了。
  • 但这种模式对服务器端的管理不利。因为客户端数据端口连到FTP服务器的数据端口P时,很有可能被服务器端的防火墙阻塞掉。

解决服务器端防火墙拦截问题:

如何解决服务器端防火墙拦截“数据连接”的建立呢?

  • 为了解决P端口的“准入”不被服务器防火墙拦截,需要在服务器端设定P端口的范围,并在防火墙中开启这个范围端口的“准入”。如在FileZilla Server中指定被动模式的数据端口范围为6000-7000,然后在windows防火墙中配置6000-700端口允许“准入”。

2.2 各客户端默认连接方式

  1. Windows Cmd下默认为主动模式,更改Ftp为被动的方法:
ftp> quote PASV
  1. Linux Ftp命令下默认为被动模式,更改为主动的方法:
ftp> passive
  1. IE浏览器访问方式默认为被动模式,更改为主动的方法:

工具—Internet选项—高级—使用被动FTP(去掉勾选)。

  1. java代码设置主动或被动模式的方法:
// 设置为主动模式 
ftpClient.enterLocalActiveMode(); 

// 设置为被动模式
ftpClient.enterLocalPassiveMode();

3、代码示例

解决上传文件大小为0字节的问题。

3.1 主动模式

ftp默认为主动模式,也可以通过代码明确设置:ftpClient.enterLocalActiveMode();

public static boolean uploadFile(String host, int port, 
		String username, String password,String basePath,
        String filePath, String filename, InputStream input) {
		boolean result = false;
		FTPClient ftp = new FTPClient();
		try {
			int reply;
			ftp.connect(host, port);// 连接FTP服务器
			// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
			ftp.login(username, password);// 登录
			reply = ftp.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftp.disconnect();
				return result;
			}
		
			//切换到上传目录
			if (!ftp.changeWorkingDirectory(basePath+filePath)) {
				//如果目录不存在创建目录
				String[] dirs = filePath.split("/");
				String tempPath = basePath;
				for (String dir : dirs) {
					if (null == dir || "".equals(dir)) continue;
					tempPath += "/" + dir;
					if (!ftp.changeWorkingDirectory(tempPath)) {
						if (!ftp.makeDirectory(tempPath)) {
							return result;
						} else {
							ftp.changeWorkingDirectory(tempPath);
						}
					}
				}
			}
			//设置上传文件的类型为二进制类型
			ftp.setFileType(FTP.BINARY_FILE_TYPE);
			//上传文件
			if (!ftp.storeFile(filename, input)) {
				return result;
			}
			input.close();
			ftp.logout();
			result = true;
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (ftp.isConnected()) {
				try {
					ftp.disconnect();
				} catch (IOException ioe) {
				}
			}
		}
		return result;
}

问题:通过FTPClient上传图片时发现,虽然无错误出现但是上传到服务器端的图片大小为0。

问题排查:打上断点debug,当运行到下面时,返回了false。

if (!ftp.storeFile(filename, input)) {
    return result;
}

原因分析

因为很多防火墙在设置的时候都是不允许接受外部发起的连接的,所以许多位于防火墙后或内网的FTP客户端不支持主动模式,因为服务器无法穿过防火墙或者无法连接到NAT后的客户端。

解决方案

找到了原因,是FTP客户端所在机器上开启了防火墙,FTP服务器请求客户机的端口被隔离阻止。

  • 关闭防火墙或允许访问,将我们的应用程序添加到防火墙的“允许的程序”列表中,如允许FlashFXP.exe和WinSCP以主动模式连接FTP服务器。
  • 在代码中将ftp模式设置为Passive模式(被动模式),加上如下代码即可:ftp.enterLocalPassiveMode();

3.2 被动模式

关键代码:ftp.enterLocalPassiveMode();

public static boolean uploadFile(String host, int port, 
		String username, String password,String basePath,
        String filePath, String filename, InputStream input) {
		boolean result = false;
		FTPClient ftp = new FTPClient();
		try {
			int reply;
			ftp.connect(host, port);// 连接FTP服务器
			// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
			ftp.login(username, password);// 登录
			reply = ftp.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftp.disconnect();
				return result;
			}
			//将客户端设置为被动模式
			ftp.enterLocalPassiveMode();
			//切换到上传目录
			if (!ftp.changeWorkingDirectory(basePath+filePath)) {
				//如果目录不存在创建目录
				String[] dirs = filePath.split("/");
				String tempPath = basePath;
				for (String dir : dirs) {
					if (null == dir || "".equals(dir)) continue;
					tempPath += "/" + dir;
					if (!ftp.changeWorkingDirectory(tempPath)) {
						if (!ftp.makeDirectory(tempPath)) {
							return result;
						} else {
							ftp.changeWorkingDirectory(tempPath);
						}
					}
				}
			}
			//设置上传文件的类型为二进制类型
			ftp.setFileType(FTP.BINARY_FILE_TYPE);
			//上传文件
			if (!ftp.storeFile(filename, input)) {
				return result;
			}
			input.close();
			ftp.logout();
			result = true;
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (ftp.isConnected()) {
				try {
					ftp.disconnect();
				} catch (IOException ioe) {
				}
			}
		}
		return result;
}
0

评论区