Swift2 でNSStreamを使ってソケット通信4(まとめ)

接続、受信、送信をソースコード付きで解説
諸事情によりソースは直接貼り付け

前回の記事
ソケット接続
データ送信
データ受信







ソケット通信クラス



//
//  Connection.swift
//
//  Created by ninomae makoto on 2016/09/21.
//  Copyright © 2016年 ninomae makoto. All rights reserved.
//

import Foundation

/**
 各通信処理の挙動の挙動を継承先で定義する
 */
protocol ConnectionDelegate {
    
    /**
     * データの取得が完了した時の処理
     */
    func didReceivedResponseData(response: String)
    
}

///
/// ソケット通信クラス
///
class Connection : NSObject, NSStreamDelegate {
    
    /// シングルトン
    static let sharedManager = Connection()
    private override init() {
    }
    
    // 接続先
    serverAddress: CFString = "127.0.0.1"
    let serverPort: UInt32 = 66666
    
    /// 受信用
    private var inputStream : NSInputStream!
    /// 送信用
    private var outputStream : NSOutputStream!
    
    /// 接続状態
    var isConnected = false
    
    /** 受信データのキュー */
    private var inputQueue = NSMutableData()
    
    /** 一度に受信するバッファーサイズ */
    let BUFFER_MAX = 2048
    
    /** 受信データの委譲先 */
    var delegate: ConnectionDelegate!  = nil
    /** 汎用エラー */
    static let ERR_MSG = "STR通信エラー\n再度やり直してくださいEOF"
    
    /** 接続 */
    func connect() {
        print("ソケット接続")
        var readStream : Unmanaged<cfreadstream>?
        var writeStream : Unmanaged<cfwritestream>?
        
        // ソケット作成
        CFStreamCreatePairWithSocketToHost(
                kCFAllocatorDefault, 
                Settings.serverAddress(), 
                serverPort, 
                &readStream, 
                &writeStream)
        
        if( inputStream != nil ) {
            // 接続中の場合は切断
            inputStream.delegate = nil
            inputStream.close()
            inputStream.removeFromRunLoop(
                NSRunLoop.currentRunLoop(), 
                forMode: NSDefaultRunLoopMode)

        }
        
        if( outputStream != nil ) {
            // 接続中の場合は切断
            outputStream.delegate = nil
            outputStream.close()
            outputStream.removeFromRunLoop(
                NSRunLoop.currentRunLoop(), 
                forMode: NSDefaultRunLoopMode)
        }
        
        inputStream = readStream!.takeRetainedValue() as NSInputStream
        outputStream = writeStream!.takeRetainedValue() as NSOutputStream
        
        // ストリームイベントの委譲先
        inputStream.delegate = self
        outputStream.delegate = self
        
        inputStream.scheduleInRunLoop(
                NSRunLoop.currentRunLoop(), 
                forMode: NSDefaultRunLoopMode)
        outputStream.scheduleInRunLoop(
                NSRunLoop.currentRunLoop(), 
                forMode: NSDefaultRunLoopMode)
        
        inputStream.open()
        outputStream.open()
        
    }
    
    /** 接続の切断処理を行う */
    func disConnect() {
        
        print("ソケット切断")
        inputStream.delegate = nil
        outputStream.delegate = nil
        
        inputStream.close()
        outputStream.close()
        
        inputStream.removeFromRunLoop(
                NSRunLoop.currentRunLoop(), 
                forMode: NSDefaultRunLoopMode)
        outputStream.removeFromRunLoop(
                NSRunLoop.currentRunLoop(), 
                forMode: NSDefaultRunLoopMode)
        
        isConnected = false
    }
    
    /** ストリームの状態が変化した時に呼ばれる */
    func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
        
        if aStream === inputStream {
            // 入力ストリーム
            switch eventCode {
            case NSStreamEvent.ErrorOccurred:
                print("input: ErrorOccurred: \(aStream.streamError?.description)")
            case NSStreamEvent.OpenCompleted:
                print("input: OpenCompleted")
            case NSStreamEvent.HasBytesAvailable:
                print("input: HasBytesAvailable")
                // 入力ストリーム読み込み可能
                
                getResponse()
                
            case NSStreamEvent.EndEncountered:
                print("input: EndEncountered")
                // サーバから切断された?
                disConnect()
            default:
                break
            }
        }
        else if aStream === outputStream {
            // 出力ストリーム
            switch eventCode {
            case NSStreamEvent.ErrorOccurred:
                print("output: ErrorOccurred: \(aStream.streamError?.description)")
            case NSStreamEvent.OpenCompleted:
                print("output: OpenCompleted")
            case NSStreamEvent.HasSpaceAvailable:
                print("output: HasSpaceAvailable")
                print("データ送信可能")
                
                // Here you can write() to `outputStream`
                isConnected = true
                
            case NSStreamEvent.EndEncountered:
                print("output: EndEncountered")
                disConnect()

            default:
                break
            }
        }
    }
    /** requestを投げる
     */
    func sendRequest(msg: String) {
        
        if !isConnected {
            connect()
        }
        
        // エンコード
        let request = msg.dataUsingEncoding(
                NSShiftJISStringEncoding, 
                allowLossyConversion: false)

        let requestBytes = UnsafePointer<UInt8>(request!.bytes)
        let requestLength = request!.length
        
        var timeout = 5 * 100000 // wait 5 seconds before giving up
        //NSOperationQueue().addOperationWithBlock { [weak self] in
            while !self.outputStream.hasSpaceAvailable {
                usleep(1000) // wait until the socket is ready
                timeout -= 100
                if timeout < 0 {
                    print("time out")
                    self.delegate.didReceivedResponseData(Connection.ERR_MSG)
                    return
                } else if self.outputStream.streamError != nil {
                    
                    print("disconnect Stream")
                    self.delegate.didReceivedResponseData(Connection.ERR_MSG)
                    return // disconnectStream will be called.
                }
            }
            print("write")
            self.outputStream.write(requestBytes, maxLength: requestLength)
        //}
        
    }
    
    /* responseを受け取る */
    private func getResponse() {
        
        var buffer = UnsafeMutablePointer<UInt8>(NSMutableData(capacity: BUFFER_MAX)!.bytes)
        var length = self.inputStream!.read(buffer, maxLength: BUFFER_MAX)
        
        if length == -1 {
            print("length:-1")
            return
        }
        // ストリームデータを文字列に変更
        let streamText = NSString(
                data: NSData(bytes: buffer, length: length), 
                encoding: NSShiftJISStringEncoding )
        
        print("length:" + length.description)
        
        // データが断片化する可能性があるのでキューにためておく
        inputQueue.appendData(NSData(bytes: buffer, length: length))
        
        let work = inputQueue
        
        buffer = UnsafeMutablePointer<UInt8>(work.bytes)
        length = work.length

        let allStream = NSString(
                 data: NSData(bytes: buffer, length: length), 
                 encoding: NSShiftJISStringEncoding )
        
        if (allStream != nil && allStream!.containsString("EOF") ) {
            print("データ受信完了")
            
            let data: String = allStream! as String
            if( data.containsString("STR") ) {
                
                // データ受信完了後に委譲先に処理を依頼
                if( delegate == nil ) {
                    print("委譲先を設定してください")
                }
                delegate.didReceivedResponseData(data)
                inputQueue = NSMutableData()
            }
            else {
                // データ不正
                print("不正なデータです。EOFはありますがSTRがありません")
                delegate.didReceivedResponseData(Connection.ERR_MSG)
                inputQueue = NSMutableData()
            }
        }
        
        
        if( allStream != nil && !allStream!.containsString("STR") ){
            
            if( allStream?.length == 0 ) {
                print("切断された?")
            }
            else {
                // データ不正
                print("不正なデータです。STRがありません")
                delegate.didReceivedResponseData(Connection.ERR_MSG)
            }
            
            inputQueue = NSMutableData()
        }
    }
}



シングルトンで実装して1インスタンスの未起動するように。
画面毎にインスタンスを作成して接続してとやると上手くいかなかった。
接続処理は既に接続していた場合はStreamをまっさらに。
データ送信処理は今は同期的に処理しているけどコメントを外してNSOperationQueue().addOperationWithBlockを追加すると非同期になります(上手く動作しないかも)
データ受信処理は受け取ったデータをキューにためてデータの末尾まで来たら委譲先のdidReceivedResponseDataを呼ぶという実装にしています。




ソケット通信クラス使用例



/// 受信データを処理する場合ConnectionDelegateを継承して実装すること
class useConnection : ConnectionDelegate {
 
 let connect = Connection.sharedManager
 
 ///
 /// 初期化処理
 ///
    init() {
        connect.delegate = self
    }
 
 ///
 /// サーバに"test"を送信してみる
 ///
 func sendTest() {
  connect.sendRequest("test")
 }
 
 
    /**
     * データの取得が完了した時の処理
     */
    func didReceivedResponseData(response: String) {
        print("受信データ確認:" + response)
    }
    
}




2016年9月25日日曜日