CocoaAsyncSocket实现FTP服务器

CocoaAsyncSocket实现FTP服务器

前言:

  使用CocoaAsyncSocket实现本地FTP服务器,可用于发送消息,图片、视频传输(包含进度)。
  这里我开始不知道怎么利用这个库,因此借鉴了别人的demo(AYAsyncSocketDemo),使用了
AYSocketServer,AYSocketManager,并实现了扩展,在这里感谢大佬的demo。
  ps:这篇文章只是本人学习的一个记录,其中不对的地方或者可以改进的地方,您可以指出,期
待大佬指正。

Part 1:

  导入CocoaAsyncSocket库,我这里是选择pod导入(实际上只需要GCDAsyncSocket的部分,
GCDAsyncUdpSocket是开发使用udp的时候用到的):
        pod 'CocoaAsyncSocket'

附图:

CocoaAsyncSocket实现FTP服务器_第1张图片
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 = 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.myName
    
    var mData = 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 = nil
    
    clientSocket.readData(to: GCDAsyncSocket.crlfData(), withTimeout: -1, tag: tag)
}

总结:

~这是我第一次发,总的来说还可以吧,如果有什么建议和意见,欢迎批评指出;这篇文章只是我的学习记录,只作为一个参考,希望对大家有用。
~这份代码是写在项目里的,没有demo;其实这差不多是全部了,就不做demo了
~数据处理部分(dataProcessing方法),每个人都有不同的处理方法和需要处理的数据,就不贴出来了。
~这里做两个端的多任务进度,我首先进行了发送和接收的区分,然后这两个部分都有独立的tag池(其实也就是tag集合的管理),创建进度条的时候,每个进度条都有一个ID,然后用ID作为对应文件的tag,单条更新的时候,以ID做识别,所以这部分很重要,我这里虽然能实现,但写的比较乱,就不好贴出来了
~其实也可以不用tag池,直接在didRead里面进行进度的读取,但我测试的时候发现有时候不走这里,也许是我哪里写错了。所以我放弃了,如果有知道的告诉下。

你可能感兴趣的:(CocoaAsyncSocket实现FTP服务器)