Swift2 でNSStreamを使ってソケット通信3(データ受信編)

前回の続き
受信処理は不正?なデータが入ってきたり、分割して送られてきたりするのでひと手間必要になります。
この辺りはサーバプログラムと合わせて調整が必要かと。
入力ストリームのNSStreamEvent.HasBytesAvailableが発生するタイミングで受信処理を実行します。







ソースコード


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
 各通信処理の挙動の挙動を継承先で定義する
 */
protocol ConnectionDelegate {
     
    /**
     * データの取得が完了した時の処理
     */
    func didReceivedResponseData(response: String)
     
}
 
// 以下は通信クラスの実装抜粋
 
// プロパティ
/** 受信データのキュー */
private var inputQueue = NSMutableData()
 
 
/** ストリームの状態が変化した時に呼ばれる 詳細省略 */
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
    if aStream === inputStream {
        // 入力ストリーム
        switch eventCode {
        case NSStreamEvent.HasBytesAvailable:
            print("input: HasBytesAvailable")
            // 入力ストリーム読み込み可能
            getResponse()
             
        default:
            break
        }
    }
}
 
/* responseを受け取る */
private func getResponse() {
  
 let BUFFER_MAX = 2048
     
    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 )
     
    // 受信データは必ず EOF で終わるという想定
    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(ERR_MSG)
            inputQueue = NSMutableData()
        }
    }
     
    if( allStream != nil && !allStream!.containsString("STR") ){
         
        if( allStream?.length == 0 ) {
            print("切断された?")
        }
        else {
            // データ不正
            print("不正なデータです。STRがありません")
            delegate.didReceivedResponseData(ERR_MSG)
        }
         
        inputQueue = NSMutableData()
    }
}


解説



サーバ側から送られるデータは先頭にSTR、末尾にEOFが入ってくる想定です(そういう風に実装しています)
切断されたタイミングで長さ0のデータが送られてくるようです。
データが複数回に別れることを想定して一度キューに溜め込んでデータの終わりが来たら委譲先に受信完了処理をしてもらうという実装になっています。
BUFFER_MAXを8など小さい値にするとよく挙動が把握できるかと。
要件に合わせて適宜調整してください。

次回はこれまでやってきたことをまとめます。


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

次の記事
まとめ

2016年9月24日土曜日