文章目录
- 前言
- 一、FTP是什么?
- 二、使用步骤
- 1 服务端
- 1.1 服务端的代码实现
- 2 客户端
- 2.1 客户端的代码实现
- 附件
前言
最近在公司的项目中,使用到了 局域网通信,不同的设备直接传递消息以及发送文件。普通文本消息的发送使用websocket 封装了一套可以使用,但是对于文件类型的消息,尤其是大文件的发送,使用websocket 发送字节数组的形式出现了很多的问题,所以就取消了使用websocket 自己封装发送的方式。采用局域网
一、FTP是什么?
FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。
其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协议把网页或程序传到Web服务器上。此外,由于FTP传输效率非常高,在网络上传输大的文件时,一般也采用该协议
二、使用步骤
1 服务端
引入库:
//ftp server
implementation files('libs/ftpserver-core-1.0.6.jar')
implementation files('libs/mina-core-2.0.16.jar')
implementation 'commons-net:commons-net:3.8.0'
1.1 服务端的代码实现
FtpServerlet:
import androidx.annotation.Keep
import org.apache.ftpserver.FtpServer
import java.io.File
import org.apache.ftpserver.listener.ListenerFactory
import org.apache.ftpserver.FtpServerFactory
import org.apache.ftpserver.ftplet.*
import org.apache.ftpserver.usermanager.SaltedPasswordEncryptor
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory
import org.apache.ftpserver.usermanager.impl.WritePermission
import org.apache.ftpserver.usermanager.impl.BaseUser
@Keep
class FtpServerlet(
private val sharedDirectory: String,
private val ftpPort: Int = 2121,
private val log: ((tag: String, content: String?) -> Unit)?=null
) :
DefaultFtplet() {
private val TAG = "FtpServerlet"
private var mFtpServer: FtpServer? = null
private val mUser = "myUser"
private val mPassword = "myPassword"
fun startFtp() {
if (null != mFtpServer && !mFtpServer!!.isStopped) {
return
}
val file = File(sharedDirectory)
if (!file.exists()) {
file.mkdirs()
}
val serverFactory = FtpServerFactory()
val listenerFactory = ListenerFactory()
// 设定端末番号
listenerFactory.port = ftpPort
// 通过PropertiesUserManagerFactory创建UserManager然后向配置文件添加用户
val userManagerFactory = PropertiesUserManagerFactory()
userManagerFactory.passwordEncryptor = SaltedPasswordEncryptor()
val userManager: UserManager = userManagerFactory.createUserManager()
val auths = mutableListOf<Authority>()
val auth: Authority = WritePermission()
auths.add(auth)
//添加用户
val user = BaseUser()
user.name = mUser
user.password = mPassword
user.homeDirectory = sharedDirectory
user.authorities = auths
userManager.save(user)
// 设定Ftplet
val ftpletMap = mutableMapOf<String, Ftplet>()
ftpletMap["Ftplet"] = this
serverFactory.userManager = userManager;
serverFactory.addListener("default", listenerFactory.createListener());
// serverFactory.ftplets = ftpletMap
mFtpServer = serverFactory.createServer();
mFtpServer?.start()
log?.invoke(TAG, "start ftp server , sharedDirectory = $sharedDirectory")
}
fun stopFtp() {
// FtpServer不存在和FtpServer正在运行中
if (null != mFtpServer && !mFtpServer!!.isStopped) {
mFtpServer?.stop()
log?.invoke(TAG, "stop ftp server")
}
}
override fun onAppendStart(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(
TAG,
"onAppendStart argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}"
)
return super.onAppendStart(session, request)
}
override fun onAppendEnd(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(
TAG,
"onAppendEnd argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}"
)
return super.onAppendEnd(session, request)
}
override fun onLogin(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(TAG, "onLogin argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}")
return super.onLogin(session, request)
}
override fun onConnect(session: FtpSession?): FtpletResult {
log?.invoke(TAG, "onConnect ")
return super.onConnect(session)
}
override fun onDisconnect(session: FtpSession?): FtpletResult {
log?.invoke(TAG, "onDisconnect ")
return super.onDisconnect(session)
}
override fun onUploadStart(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(
TAG,
"onUploadStart argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}"
)
return super.onUploadStart(session, request)
}
override fun onUploadEnd(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(
TAG,
"onUploadEnd argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}"
)
return super.onUploadEnd(session, request)
}
override fun onDownloadStart(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(
TAG,
"onDownloadStart argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}"
)
return super.onDownloadStart(session, request)
}
override fun onDownloadEnd(session: FtpSession?, request: FtpRequest?): FtpletResult {
log?.invoke(
TAG,
"onDownloadEnd argument = ${request?.argument} , requestLine = ${request?.requestLine} , command = ${request?.command}"
)
return super.onDownloadEnd(session, request)
}
}
2 客户端
引入库:
//ftp
implementation 'commons-net:commons-net:3.8.0'
implementation files('libs/ftpserver-core-1.0.6.jar')
2.1 客户端的代码实现
import android.net.Uri
import android.util.Log
import com.demo.client.model.ClientConfig
import kotlinx.coroutines.*
import org.apache.commons.net.ftp.FTPClient
import org.apache.commons.net.ftp.FTPReply
import java.io.*
import java.net.SocketException
class FtpClientManager {
companion object {
private const val TAG = "FtpClient"
private const val BUFFER_SIZE = 2 * 1024 * 1024
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
FtpClientManager()
}
}
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
var ilog: ((tag: String, content: String?) -> Unit)? = null
private var ftpClient: FTPClient? = null
private var config: ClientConfig? = null
fun connect2Server(config: ClientConfig, callback: ((result: Boolean) -> Unit)? = null) {
this.config = config
ilog?.invoke(TAG, config.toString())
val ftpUri = Uri.parse(config.ftpUrl)
ftpClient = FTPClient()
//设定连接超时时间
ftpClient?.connectTimeout = config.connectTimeout.toInt()
ftpClient?.setDataTimeout(config.connectTimeout.toInt())
ilog?.invoke(TAG, "ftpUri = ${ftpUri.host} , ${ftpUri.port}")
scope.launch {
try {
// 要连接的FTP服务器Url,Port
ftpClient?.connect(ftpUri.host, ftpUri.port)
// 看返回的值是不是230,如果是,表示登陆成功
val reply = ftpClient?.replyCode
ilog?.invoke(TAG, "reply = $reply")
if (!FTPReply.isPositiveCompletion(reply!!)) {
// 断开
ftpClient?.disconnect()
withContext(Dispatchers.Main) {
callback?.invoke(false)
}
return@launch
}
// 登陆FTP服务器
val login = ftpClient?.login(config.ftpUserName ?: "anonymous", config.ftpUserPwd)
ilog?.invoke(TAG, "login = $login")
if (!login!!) {
// 断开
ftpClient?.disconnect()
withContext(Dispatchers.Main) {
callback?.invoke(false)
}
return@launch
}
ftpClient?.setFileType(FTPClient.BINARY_FILE_TYPE)
ftpClient?.enterLocalPassiveMode()
withContext(Dispatchers.Main) {
callback?.invoke(true)
}
} catch (e: SocketException) {
e.printStackTrace()
withContext(Dispatchers.Main) {
callback?.invoke(false)
}
} catch (e: IOException) {
e.printStackTrace()
withContext(Dispatchers.Main) {
callback?.invoke(false)
}
}
}
}
fun disconnectFTP(): Boolean {
return try {
ftpClient?.disconnect()
true
} catch (e: IOException) {
e.printStackTrace()
false
}
}
/**
* 下载文件
*
* @param saveFilePath 要存放的文件的路径
* @param remoteFileName 远程FTP服务器上的那个文件的名字
* @param callback true为成功,false为失败
*/
fun downloadFile(saveFilePath: String, remoteFileName: String, callback: ((result: Boolean) -> Unit)? = null) {
if (null == ftpClient) {
callback?.invoke(false)
return
}
if (!ftpClient!!.isConnected) {
connect2Server(config!!) { result ->
if (result) {
realDownload(saveFilePath, remoteFileName, callback)
}
}
} else {
realDownload(saveFilePath, remoteFileName, callback)
}
}
private fun realDownload(saveFilePath: String, remoteFileName: String, callback: ((result: Boolean) -> Unit)? = null) {
scope.launch {
var out: BufferedOutputStream? = null
try {
val workPath = ftpClient?.printWorkingDirectory()
ilog?.invoke(TAG, "realDownload workPath = $workPath")
if ("/" == workPath) {
//工作目录切换,按需看自己是否需要切换
ftpClient?.changeWorkingDirectory("/resources/")
}
ftpClient?.bufferSize = BUFFER_SIZE
val files = ftpClient!!.listFiles(remoteFileName)
if (null == files || files.isEmpty()) {
throw FileNotFoundException("not found remoteFileName")
}
val fileInfo = files[0]
val localFile = File(saveFilePath)
out = BufferedOutputStream(FileOutputStream(localFile))
val downFlag = ftpClient!!.retrieveFile(fileInfo.name, out)
out.flush()
withContext(Dispatchers.Main) {
callback?.invoke(downFlag)
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
callback?.invoke(false)
}
} finally {
out?.close()
// 退出登陆FTP,关闭ftpCLient的连接
ftpClient?.logout()
ftpClient?.disconnect()
}
}
}
/**
* 上传文件
*
* @param uploadFilePath 要上传文件所在SDCard的路径
* @param callback true为成功,false为失败
*/
fun uploadFile(uploadFilePath: String, callback: ((result: Boolean) -> Unit)? = null) {
if (null == ftpClient) {
callback?.invoke(false)
return
}
if (!ftpClient!!.isConnected) {
connect2Server(config!!) { result ->
if (result) {
realUpload(uploadFilePath, callback)
}
}
} else {
realUpload(uploadFilePath, callback)
}
}
private fun realUpload(uploadFilePath: String, callback: ((result: Boolean) -> Unit)? = null) {
val uoloadFile = File(uploadFilePath)
if (!uoloadFile.exists()) {
callback?.invoke(false)
return
}
scope.launch {
val workPath = ftpClient?.printWorkingDirectory()
ilog?.invoke(TAG, "realUpload workPath = $workPath")
if ("/" == workPath) {
//工作目录切换,按需看自己是否需要切换
ftpClient?.changeWorkingDirectory("/resources/")
}
// 设置上传文件需要的一些基本信息
ftpClient?.bufferSize = BUFFER_SIZE
ftpClient?.controlEncoding = "UTF-8"
ftpClient?.enterLocalPassiveMode()
var fileInputStream: FileInputStream? = null
try {
fileInputStream = FileInputStream(uploadFilePath)
val uploadFlag = ftpClient!!.storeFile(uoloadFile.name, fileInputStream)
withContext(Dispatchers.Main) {
callback?.invoke(uploadFlag)
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
callback?.invoke(false)
}
} finally {
fileInputStream?.close()
// 退出登陆FTP,关闭ftpCLient的连接
ftpClient?.logout()
ftpClient?.disconnect()
}
}
}
}
附件
依赖库下载地址:
https://mina.apache.org/downloads-mina_2_0.html
https://mina.apache.org/ftpserver-project/old-downloads.html