簡體   English   中英

雙向流轉換器的設計模式

[英]Design pattern for bidirectional stream translator

我想將網絡協議實現對象設計為完全與套接字無關,並且僅充當雙向轉換器。 因此,協議對象應從“控制”端饋送對象或命令,並從“網絡”端發出字節,並從“網絡”端接受字節以轉換為對象/響應並從“控制”端發出。

我無法選擇優雅的設計模式在Node.js中執行此操作。 我希望它與Stream完全兼容,到目前為止,我最終還是采用了這種方法:

socket = getSocketSomehow();
proto = new Protocol();

socket.pipe(proto.aux);
proto.aux.pipe(socket);

proto.write({ foo: 'this', bar: ['is', 'command'] });
proto.once('data', function(response) {
    console.log('this is response: ' + response.quux);
});

proto是兩個交叉連接的雙工流的集合,其本身是stream.Duplex以及aux 傳入的網絡數據進入proto.aux ,然后被解析並作為對象從proto發出。 傳入的對象進入proto ,並組成字節,並從proto.aux發出。

有更好的方法來做同樣的事情嗎?

我以下面的方法結束了。 代碼示例包含在CoffeeScript中,以提高可讀性。


Bond類實現了Duplex流接口,但將兩個不相關的流綁定在一起,因此將讀寫操作代理到單獨的流。

'use strict'

{ EventEmitter } = require 'events'

class Bond extends EventEmitter
    proxyReadableMethod = (method) =>
        @::[method] = -> @_bondState.readable[method] arguments...

    proxyWritableMethod = (method) =>
        @::[method] = -> @_bondState.writable[method] arguments...

    proxyReadableMethod 'read'
    proxyReadableMethod 'setEncoding'
    proxyReadableMethod 'resume'
    proxyReadableMethod 'pause'
    proxyReadableMethod 'pipe'
    proxyReadableMethod 'unpipe'
    proxyReadableMethod 'unshift'
    proxyReadableMethod 'wrap'

    proxyWritableMethod 'write'
    proxyWritableMethod 'end'

    constructor: (readable, writable) ->
        super

        @_bondState = {}
        @_bondState.readable = readable
        @_bondState.writable = writable

        proxyEvent = (obj, event) =>
            obj.on event, => @emit event, arguments...

        proxyEvent readable, 'readable'
        proxyEvent readable, 'data'
        proxyEvent readable, 'end'
        proxyEvent readable, 'close'
        # proxyEvent readable, 'error'

        proxyEvent writable, 'drain'
        proxyEvent writable, 'finish'
        proxyEvent writable, 'pipe'
        proxyEvent writable, 'unpipe'
        # proxyEvent writable, 'error'

module.exports = Bond

Protocol聚合兩個內部TransformParserComposer Parseraux端獲取數據,並將其轉換為從ctl端輸出的數據,而Composer則相反。 auxctl都是解析器和作曲者的紐帶,但方向不同-因此aux只處理進出的“組合”數據,而ctl端發出並接受“分析”的數據。 我的設計決定是通過Protocol本身公開ctl ,而aux作為實例變量可見。

Protocol公開:

  • _parse_compose作為_transform類的方法
  • _parseEnd_composeEnd作為_flush的方法
  • parsedcomposedpush式方法
  • unparseuncomposeunshift的方法
'use strict'

Bond = require './bond'
BacklogTransform = require './backlog-transform'

class Protocol extends Bond
    constructor: (options) ->
        @_protocolState = {}
        @_protocolState.options = options
        parser = @_protocolState.parser = new ParserTransform @
        composer = @_protocolState.composer = new ComposerTransform @

        parser.__name = 'parser'
        composer.__name = 'composer'

        proxyEvent = (source, event) =>
            source.on event, =>
                @emit event, arguments...

        proxyParserEvent = (event) =>
            proxyEvent @_protocolState.parser, event

        proxyComposerEvent = (event) =>
            proxyEvent @_protocolState.composer, event

        proxyParserEvent 'error'
        proxyComposerEvent 'error'

        super @_protocolState.parser, @_protocolState.composer
        @aux = @_protocolState.aux = new Bond @_protocolState.composer, @_protocolState.parser
        # @_protocolState.main = @main = new Bond @_protocolState.parser, @_protocolState.composer

    parsed: (chunk, encoding) ->
        @_protocolState.parser.push chunk, encoding

    composed: (chunk, encoding) ->
        @_protocolState.composer.push chunk, encoding

    unparse: (chunk, encoding) ->
        @_protocolState.parser.unshift chunk, encoding

    uncompose: (chunk, encoding) ->
        @_protocolState.composer.unshift chunk, encoding

    #
    _parse: (chunk, encoding, callback) ->
        throw new TypeError 'not implemented'

    _compose: (chunk, encoding, callback) ->
        throw new TypeError 'not implemented'

    _parseEnd: (callback) ->
        callback()

    _composeEnd: (callback) ->
        callback()

class ParserTransform extends BacklogTransform
    constructor: (@protocol) ->
        options = @protocol._protocolState.options
        super options, options.auxObjectMode, options.mainObjectMode

    __transform: (chunk, encoding, callback) ->
        @protocol._parse chunk, encoding, callback

    __flush: (callback) ->
        @protocol._parseEnd callback

class ComposerTransform extends BacklogTransform
    constructor: (@protocol) ->
        options = @protocol._protocolState.options
        super options, options.mainObjectMode, options.auxObjectMode

    __transform: (chunk, encoding, callback) ->
        @protocol._compose chunk, encoding, callback

    __flush: (callback) ->
        @protocol._composeEnd callback

module.exports = Protocol

BacklogTransform是實用程序類,它通過在_transform期間的某個地方調用unshift方法,將Transform流擴展為具有將未轉換的塊移回隊列的_transform ,因此未移位的數據將出現在下一個_transform_transform添加到新的塊中。 不幸的是,實現並不像我希望的那樣理想。

'use strict'

async = require 'async'
stream = require 'stream'

class BacklogTransform extends stream.Transform
    constructor: (options, writableObjectMode, readableObjectMode) ->
        options ?= {}

        super options
        @_writableState.objectMode = writableObjectMode ? options.writableObjectMode
        @_readableState.objectMode = readableObjectMode ? options.readableObjectMode
        @_backlogTransformState = {}
        @_backlogTransformState.backlog = []

    unshift: (chunk, encoding = null) ->
        if @_writableState.decodeStrings
            chunk = new Buffer chunk, encoding ? @_writableState.defaultEncoding

        @_backlogTransformState.backlog.unshift { chunk, encoding }

    _flushBacklog: (callback) ->
        backlog = @_backlogTransformState.backlog

        if backlog.length
            if @_writableState.objectMode
                async.forever(
                    (next) =>
                        return next {} if not backlog.length

                        { chunk, encoding } = backlog.shift()
                        @__transform chunk, encoding, (err) ->
                            return next { err } if err?

                            next null

                    ({ err }) ->
                        return callback err if err?

                        callback()
                )
            else
                chunks = (chunk for { chunk, encoding } in backlog)

                if @_writableState.decodeStrings
                    encoding = 'buffer'
                    chunk = Buffer.concat chunks
                else
                    encoding = backlog[0].encoding
                    for item in backlog[1..]
                        if encoding != item.encoding
                            encoding = null
                            break

                    chunk = chunks.join ''

                @_backlogTransformState.backlog = []
                @__transform chunk, encoding, callback
        else
            callback()

    _transform: (chunk, encoding, callback) ->
        backlog = @_backlogTransformState.backlog

        if backlog.length
            backlog.push { chunk, encoding }

            @_flushBacklog callback
        else
            @__transform chunk, encoding, callback

    _flush: (callback) ->
        @_flushBacklog =>
            @__flush callback

    __transform: (chunk, encoding, callback) ->
        throw new TypeError 'not implemented'

    __flush: (callback) ->
        callback()

module.exports = BacklogTransform

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM