Swift2 でNSStreamを使ってソケット通信1(接続まで)

ライブラリでsocket.ioとかStarscreamなんかもあるんだけどフォーマットが決まっているのでサーバ側の実装はこれっていう指定があると対応できないんですよね








ソケット接続


func connect() {
    print("ソケット接続")
        
    var inputStream : NSInputStream!
    var outputStream : NSOutputStream!
    
    var readStream : Unmanaged?
    var writeStream : Unmanaged?
        
    let serverAddress = "127.0.0.1"
    let serverPort = "53489" // 適当
    // ソケット作成
    CFStreamCreatePairWithSocketToHost(
     kCFAllocatorDefault, 
     serverAddress, 
     serverPort, 
     &readStream, 
     &writeStream)
    
    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()
        
}

この辺りは用途で大きく変わらないかと。
受信用と送信用にそれぞれ作成していてinputStreamが受信用、outputStreamが送信用になります。通常はクラスのメンバとして保持します。
serverAddress、port番号はサーバ側で指定された値を使用します。
また実装によっては委譲先(delegate)を変えることがあるかも。





ソケット切断


/** 接続の切断処理を行う */
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)
    
}


順番を守らなかったりすると予期せぬエラーが発生する可能性があります。



ソケットのハンドルイベント


/** ストリームの状態が変化した時に呼ばれる */
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("データ送信可能")
            
            isConnected = true
            
        case NSStreamEvent.EndEncountered:
            print("output: EndEncountered")
            disConnect()

        default:
            break
        }
    }
}



委譲先のクラスで定義します。
NSStreamDelegateを継承すること。
上記ソースは受信用ストリームと送信用ストリームで処理を分けています。

以下はイベントの詳細です

ErrorOccurred

何かしらエラーが発生した時に呼ばれます(見たことないけど)
再接続処理が必要かもしれません。

OpenCompleted

通信可能になった状態です。

HasBytesAvailable

受信専用イベント。
何かしら受信した時に呼ばれます。
一回のレスポンスで複数回データが分割されて受信することがあるので対応が必要になります。

HasSpaceAvailable

データが送信可能になった時に呼ばれます。
ただし実際送信可能かどうか調べるときは送信時にoutputStream.hasSpaceAvailable プロパティを参照するようにした方が確実に動作します。

EndEncountered

主にサーバ側から通信が遮断された時に呼ばれます。
こちらも切断処理を行い場合によっては再接続処理を行います

ちょっと長くなったのでここまで

続きは以下
データ送信
データ受信
まとめ

2016年9月21日水曜日