CocoaAsyncSocket实现FTP服务器
CocoaAsyncSocket实现FTP服务器
前言:
使用CocoaAsyncSocket实现本地FTP服务器,可用于发送消息,图片、视频传输(包含进度)。
这里我开始不知道怎么利用这个库,因此借鉴了别人的demo(AYAsyncSocketDemo),使用了
AYSocketServer,AYSocketManager,并实现了扩展,在这里感谢大佬的demo。
ps:这篇文章只是本人学习的一个记录,其中不对的地方或者可以改进的地方,您可以指出,期
待大佬指正。
Part 1:
导入CocoaAsyncSocket库,我这里是选择pod导入(实际上只需要GCDAsyncSocket的部分,
GCDAsyncUdpSocket是开发使用udp的时候用到的):
pod 'CocoaAsyncSocket'
附图:
文章图片
image Part 2 :
通过查看库,我发现CocoaAsyncSocket已经实现了socket的必要方法,以及结合了系统
CFStream(流)的方法实现,并且所有传输的delegate方法都是在分线程里,以tag作为重要标
识。
AYAsyncSocketDemo让我进一步了解到CocoaAsyncSocket传输过程中,可以用
GCDAsyncSocket.crlfData()作为传输的信息和内容的分界,AYAsyncSocketDemo中信息中
传入了内容的大小,方便我们读取定长的内容,为图片和视频的传输建立了基础。
服务端: 1、初始化:遵循协议GCDAsyncSocketDelegate,
var serverSocket: GCDAsyncSocket!
var clientSockets: [GCDAsyncSocket] = [GCDAsyncSocket]()
private var headerDic: [String : Any]? = nil
static let socketServer = AYSocketServer()override init() {
super.init()
serverSocket = GCDAsyncSocket()
serverSocket.delegate = self
serverSocket.delegateQueue = DispatchQueue.global()
}func startServer() -> Void {
do {//启动监听
try serverSocket.accept(onPort: LocalFTPConfigure.port)
ftp_print("服务器启动...")
} catch{
ftp_print("服务器启动, 错误:", error)
}
}
2、发送数据
func ay_sendData(data: Data, _ tag: Int, _ fileName: String, _ clientSocket: GCDAsyncSocket) {
var headerDic: [String : Any] = [String : Any]();
headerDic["size"] = data.count//内容大小
headerDic["tag"] = tag//发送要走的tag
headerDic["fileName"] = fileName//文件名
headerDic["userName"] = LocalFTPConfigure.myName//用户名,这里是我手机的名字var mData = https://www.it610.com/article/headerDic.ay_toJSONData()//header
mData.append(GCDAsyncSocket.crlfData()) // 数据分界
mData.append(data)//具体内容ftp_print("服务器发送, 数据:", data, "...header:", headerDic)
ftp_print("服务器发送, 是否是主线程:", RunLoop.current.isEqual(RunLoop.main))clientSocket.write(mData, withTimeout: -1, tag: tag)
}
3、代理方法
func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) {
ftp_print("服务器写入完成...", tag)
ftp_print("服务器写入完成,是否是主线程:", RunLoop.current.isEqual(RunLoop.main))
}func socket(_ sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) {
ftp_print("服务器有新的连接")
clientSockets.append(newSocket)
//注意:用这个方法,这里需要读到GCDAsyncSocket.crlfData(),才会走didRead方法
newSocket.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: LocalFTPConfigure.server_accept_tag)
}
//这里获取不到userName和fileName,只能以tag值做唯一标识
func socket(_ sock: GCDAsyncSocket, didReadPartialDataOfLength partialLength: UInt, tag: Int) {
let progress = sock.progress(ofReadReturningTag: nil, bytesDone: nil, total: nil)
ftp_print("服务器接收大小:", partialLength, "...tag:", tag, "...进度:", progress)
DispatchQueue.main.async {
if !progress.isNaN {
XJJPhotoModel.share.singleProgress(tag, progress, false)
}
}
}func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
// socket 为客户端链接 socket
var userName = ""
var fileName = ""if headerDic == nil {
headerDic = data.ay_JSONToAny() as? [String : Any]
//这里解析header
if headerDic == nil {
ftp_print("当前数据包头为空")
sock.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
}else {
if let length = headerDic?["size"] as? UInt {
userName = headerDic?["userName"] as? String ?? ""
fileName = headerDic?["fileName"] as? String ?? ""
//通过tableview的数据组,获取对应的tag值,方便获取进度
//这里就是把每个传输文件绑定一个特定的tag值
let _tag = XJJPhotoTable.findId(with: userName, fileName, false) ?? 0
ftp_print("服务器接收, 当前用户:", userName, "tag:", _tag)
sock.readData(toLength: length, withTimeout: -1, tag: _tag)
}
}
return;
}if let packetLength: UInt = headerDic?["size"] as? UInt {
if packetLength <= 0 || Int(packetLength) != data.count {
ftp_print("当前数据包错误")
sock.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
return
}
}fileName = headerDic?["fileName"] as? String ?? ""
userName = headerDic?["userName"] as? String ?? ""
ftp_print("服务器接收, header:", headerDic ?? "no header")
headerDic = nil
// 处理 data 数据
self.dataProcessing(data, fileName, sock)
DispatchQueue.main.async {
XJJPhotoModel.share.singleEndTransfer(tag, false)
}sock.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
}func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {}
客户端: 1、初始化:遵循GCDAsyncSocketDelegate
var sendProgress: ((Float) -> Void)?private var clientSocket: GCDAsyncSocket!
private var headerDic: [String : Any]? = nil
//本机ip为:127.0.0.1,如果要传给其他服务端,这里改下ip
var serverUrl: String = "127.0.0.1"static let socketManager = AYSocketManager()override init() {
super.init()
clientSocket = GCDAsyncSocket()
clientSocket.delegate = self
clientSocket.delegateQueue = DispatchQueue.global()
}/// 连接服务器
//这里的端口需要与对应服务端的端口一致,最好都设为一致的
func ay_socketConnect() -> Void {
guard !clientSocket.isConnected else {return}
do {
try clientSocket.connect(toHost: serverUrl, onPort: LocalFTPConfigure.port)
ftp_print("连接服务器...")
} catch {
ftp_print("连接服务器 错误:", error)
}
}/// 断开 socket 链接
func ay_disconnect() -> Void {
if clientSocket.isConnected {
clientSocket.disconnect()
}
}
2、发送数据(和服务端一样)
func ay_sendData(_ data: Data, _ tag: Int, _ fileName: String) -> Void {var headerDic: [String : Any] = [String : Any]()
headerDic["size"] = data.count
headerDic["tag"] = tag
headerDic["fileName"] = fileName
headerDic["userName"] = LocalFTPConfigure.myNamevar mData = https://www.it610.com/article/headerDic.ay_toJSONData()
mData.append(GCDAsyncSocket.crlfData()) // 分界
mData.append(data)ftp_print("客户端发送, 数据:", data, "...header:", headerDic)
ftp_print("客户端发送, 是否是主线程:", RunLoop.current.isEqual(RunLoop.main))clientSocket.write(mData, withTimeout: -1, tag: tag)
}
3、代理方法(和服务端差不多)
func socket(_ sock: GCDAsyncSocket, didWritePartialDataOfLength partialLength: UInt, tag: Int) {
let progress = clientSocket.progress(ofWriteReturningTag: nil, bytesDone: nil, total: nil)
ftp_print("客户端发送, 进度:", progress, "...大小:", partialLength, "...tag:", tag)
DispatchQueue.main.async {
if !progress.isNaN {
XJJPhotoModel.share.singleProgress(tag, progress, true)
}
}
}func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) {
ftp_print("客户端发送完成...", tag, "是否是主线程:", RunLoop.current.isEqual(RunLoop.main))
DispatchQueue.main.async {
XJJPhotoModel.share.singleEndTransfer(tag, true)
}
}func socket(_ sock: GCDAsyncSocket, didConnectTo url: URL) {}func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
ftp_print("连接服务器成功")
clientSocket.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: LocalFTPConfigure.client_connect_tag)
clientSocket.perform {[weak self] in
guard let sself = self else {return}
sself.clientSocket.enableBackgroundingOnSocket()
}
}func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {
ftp_print("服务器连接断开", err ?? "no error.")
}func socket(_ sock: GCDAsyncSocket, didReadPartialDataOfLength partialLength: UInt, tag: Int) {
let progress = sock.progress(ofReadReturningTag: nil, bytesDone: nil, total: nil)
ftp_print("客户端接收数据大小:", partialLength, "...tag:", tag, "...进度:", progress)
}func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {if headerDic == nil {
headerDic = data.ay_JSONToAny() as? [String : Any]if headerDic == nil {
ftp_print("当前数据包头为空")
clientSocket.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
}else {
if let length = headerDic?["size"] as? UInt {
let _tag: Int = (headerDic?["tag"] as? Int) ?? 0
clientSocket.readData(toLength: length, withTimeout: -1, tag: _tag)
}
}
return
}if let packetLength: UInt = headerDic?["size"] as? UInt {
if packetLength <= 0 || Int(packetLength) != data.count {
ftp_print("当前数据包错误")
clientSocket.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
return
}
}// 处理 data 数据
ftp_print("客户端接收, header:", headerDic ?? "no header")
let fileName: String? = headerDic?["fileName"] as? String
self.dataProcessing(data, fileName, sock)headerDic = nilclientSocket.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
}
总结:
【CocoaAsyncSocket实现FTP服务器】~这是我第一次发,总的来说还可以吧,如果有什么建议和意见,欢迎批评指出;这篇文章只是我的学习记录,只作为一个参考,希望对大家有用。
~这份代码是写在项目里的,没有demo;其实这差不多是全部了,就不做demo了
~数据处理部分(dataProcessing方法),每个人都有不同的处理方法和需要处理的数据,就不贴出来了。
~这里做两个端的多任务进度,我首先进行了发送和接收的区分,然后这两个部分都有独立的tag池(其实也就是tag集合的管理),创建进度条的时候,每个进度条都有一个ID,然后用ID作为对应文件的tag,单条更新的时候,以ID做识别,所以这部分很重要,我这里虽然能实现,但写的比较乱,就不好贴出来了
~其实也可以不用tag池,直接在didRead里面进行进度的读取,但我测试的时候发现有时候不走这里,也许是我哪里写错了。所以我放弃了,如果有知道的告诉下。
推荐阅读
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- MybatisPlus使用queryWrapper如何实现复杂查询
- python学习之|python学习之 实现QQ自动发送消息
- 孩子不是实现父母欲望的工具——林哈夫
- opencv|opencv C++模板匹配的简单实现
- Node.js中readline模块实现终端输入
- java中如何实现重建二叉树
- 人脸识别|【人脸识别系列】| 实现自动化妆
- paddle|动手从头实现LSTM
- pytorch|使用pytorch从头实现多层LSTM