2016-01-04 48 views
3

假設我是一個用objc/swift編寫的服務器。客戶端發送大量的數據,這實際上是一個很大的utf8編碼字符串。作爲服務器,我有我的NSInputStream觸發事件,說它有數據要讀取。我抓取數據並用它建立一個字符串。將流(utf8)數據轉換爲字符串的安全方法是什麼?

但是,如果我得到的下一塊數據落在utf8數據的不幸位置上,會發生什麼?就像一個複合字符一樣。如果你試圖向它添加一個不兼容的utf8塊,它似乎會混淆字符串。

什麼是合適的方法來處理這個問題?我想我可以將數據保存爲一個NSData,但是我沒有辦法知道數據何時完成接收(假設數據長度在標頭中的HTTP)。

感謝您的任何想法。

+0

你想顯示部分字符串嗎?當收到所有數據時,連接將始終關閉/結束 – Wain

+0

在這種情況下,我可能會立即從客戶端獲取多條消息。我需要將數據掃描爲字符串來分解它。 –

+0

唯一的方法是按字節檢查您的流字節。你可以看到,如果下一個unicode字符由一個,兩個,三個,四個,五個或六個字節表示,並將字符串附加到整個字符。所以需要一些中間緩衝區。 – user3441734

回答

6

您可能要在此處使用的工具是UTF8。它會爲你處理所有的州問題。請參閱How to cast decrypted UInt8 to String?瞭解一個可能適用的簡單示例。

從UTF-8數據構建字符串的主要問題不是組合字符,而是多字節字符。 「拉丁小寫字母A」+「組合GRAVE ACCENT」即使單獨解碼每個字符,也能正常工作。不工作的是收集你的第一個字節,解碼它,然後附加解碼的第二個字節。但是,UTF8類型將爲您處理此問題。你所需要做的就是將你的NSInputStream連接到GeneratorType

下面是我正在談論的一個基本的(並非完全生產就緒)示例。首先,我們需要一種將NSInputStream轉換爲生成器的方法。這可能是最難的部分:

final class StreamGenerator { 
    static let bufferSize = 1024 
    let stream: NSInputStream 
    var buffer = [UInt8](count: StreamGenerator.bufferSize, repeatedValue: 0) 
    var buffGen = IndexingGenerator<ArraySlice<UInt8>>([]) 

    init(stream: NSInputStream) { 
     self.stream = stream 
     stream.open() 
    } 
} 

extension StreamGenerator: GeneratorType { 
    func next() -> UInt8? { 
     // Check the stream status 
     switch stream.streamStatus { 
     case .NotOpen: 
      assertionFailure("Cannot read unopened stream") 
      return nil 
     case .Writing: 
      preconditionFailure("Impossible status") 
     case .AtEnd, .Closed, .Error: 
      return nil // FIXME: May want a closure to post errors 
     case .Opening, .Open, .Reading: 
      break 
     } 

     // First see if we can feed from our buffer 
     if let result = buffGen.next() { 
      return result 
     } 

     // Our buffer is empty. Block until there is at least one byte available 
     let count = stream.read(&buffer, maxLength: buffer.capacity) 

     if count <= 0 { // FIXME: Probably want a closure or something to handle error cases 
      stream.close() 
      return nil 
     } 

     buffGen = buffer.prefix(count).generate() 
     return buffGen.next() 
    } 
} 

呼叫至next()可以阻止在這裏,所以它不應該在主隊列中被調用,但除此之外,它是吐出字節標準的生成。 (這也是可能有很多小角落案件,我不處理,所以你想仔細考慮這件事,但它並不複雜)。

因此,創建一個UTF- 8解碼生成幾乎是微不足道的:

final class UnicodeScalarGenerator<ByteGenerator: GeneratorType where ByteGenerator.Element == UInt8> { 
    var byteGenerator: ByteGenerator 
    var utf8 = UTF8() 
    init(byteGenerator: ByteGenerator) { 
     self.byteGenerator = byteGenerator 
    } 
} 

extension UnicodeScalarGenerator: GeneratorType { 
    func next() -> UnicodeScalar? { 
     switch utf8.decode(&byteGenerator) { 
     case .Result(let scalar): return scalar 
     case .EmptyInput: return nil 
     case .Error: return nil // FIXME: Probably want a closure or something to handle error cases 
     } 
    } 
} 

你當然可以平凡變成一個CharacterGenerator代替(使用Character(_:UnicodeScalar))。

最後一個問題是,如果要合併所有組合標記,例如「拉丁小寫字母A」和「組合標記ACCENT」之後總會一起返回(而不是它們的兩個字符)。這實際上比聽起來有點棘手。首先,你需要生成字符串,而不是字符。然後你需要一個很好的方法來知道所有的組合字符是什麼。這當然是可以知道的,但是我在導出一個簡單的算法時遇到了一些麻煩。 Cocoa中沒有「結合MarkCharacterSet」。我仍在考慮這個問題。獲得「主要工作」的東西很容易,但我不確定如何構建它,以便它適用於所有的Unicode。

這裏有一個小示例程序來嘗試一下:

let textPath = NSBundle.mainBundle().pathForResource("text.txt", ofType: nil)! 
    let inputStream = NSInputStream(fileAtPath: textPath)! 
    inputStream.open() 

    dispatch_async(dispatch_get_global_queue(0, 0)) { 
     let streamGen = StreamGenerator(stream: inputStream) 
     let unicodeGen = UnicodeScalarGenerator(byteGenerator: streamGen) 
     var string = "" 
     for c in GeneratorSequence(unicodeGen) { 
      print(c) 
      string += String(c) 
     } 
     print(string) 
    } 

還有一點文如下:(即第二行是一些Zalgo encoded text,這是測試好的)

 
Here is some normalish álfa你好 text 
And some Zalgo i̝̲̲̗̹̼n͕͓̘v͇̠͈͕̻̹̫͡o̷͚͍̙͖ke̛̘̜̘͓̖̱̬ composed stuff 
And one more line with no newline 

我還沒有在真正的阻塞情況下做過任何測試,比如從網絡上讀取,但它應該基於NSInputStream如何工作(即它應該阻塞,直到至少有一個字節要讀取,但是應該只填充緩衝區中的可用內容)。

我做這一切的比賽GeneratorType,以便它插入其他的東西很容易,但錯誤處理可能更好地工作,如果你沒有使用GeneratorType,而是與next() throws -> Self.Element,而不是創建自己的協議。投擲會讓堆疊中的錯誤更容易傳播,但會使插入for...in循環變得更困難。

+0

UTF8()從發生器讀取(同步)直到用盡爲止。如果數據以塊的形式出現,這將如何工作? –

+0

@MartinR帶有調度隊列。您的'next()'方法可能會阻塞,直到有數據可用。我一直在建造一些像這樣的生成器來生成NP完整問題的解決方案。 'next()'可能需要幾小時才能返回,但沒關係。 –

+0

讓我們有一些數據..讓strb =「alfa你好」.utf8; var buff = strb.dropLast()。generate(); buff現在可以模擬該塊(它是該塊上的發生器)。同時應用utf.decode(&buff)我可以重建有效部分alfa你和next .Error。我如何檢查'上一個有效字節'的位置?我認爲,這是這種方法的麻煩。我看不出如何解決這個問題,以及如何保存我的數據的'尾巴'。 – user3441734

相關問題