当传递缓冲区传递给write时,nodejs套接字的行为很奇怪

时间:2017-01-12 18:38:07

标签: node.js sockets buffer

我正在设置一个简单的测试,用于在套接字上发送二进制数据。

在发送端,我只是编码传递给调用的参数的数量,然后对于每个参数,我编码该参数的长度。这为我提供了一种快速简便的方法来测试发送不同的二进制数据:

var client = new net.Socket()
client.connect(9999, host, function() {
    console.log("Connected")
    // convert argument list into length delimited parameters
    var i,args = process.argv.length-3
    var buf = new Buffer(4)
    buf.writeInt32LE(args,0)
    client.write(buf)
    console.log("sending "+args+" args")
    for (i=3;i<args+3;i++) {
        console.log("["+(i-3)+"] "+process.argv[i].length)
        buf.writeInt32LE(process.argv[i].length)
        client.write(buf)
    }
    client.end()    
})

在接收方面,我增加了参数的数量,然后对于每个参数,我提取该参数的长度,并打印整个事物。我使用一个小状态机来跟踪我在阅读中的位置。

var server = net.createServer(function(x) {
    console.log("connected")
    var state=0,args,argn
    x.on("readable",function() {
        switch (state) {
        case 0:
            var n = x.read(4)
            console.log("[0]"); console.dir(n)
            if (n === null) return
            args=n.readInt32LE(0)
            console.log(args+" arguments")
            state = 1 ; argn = 0
            break
        case 1:
            var n = x.read(4)
            console.log("[1]"); console.dir(n)
            if (n === null) return
            console.log("argument "+argn+" has "+n.readInt32LE(0)+" bytes")
            if (++argn > args) {
                console.log("end of arguments")
                state = 0
            }
            break
        default:
            console.log("state="+state+", now what?")
    }
})
x.on("close",function() { console.log("closed") })
x.on("end",function() { console.log("ended") })

server.listen({port:9999})

我看到的问题是我似乎永远不会收到最后两个参数长度(32位整数)。我已经尝试在发送端发送一个或两个额外的整数,只是为了看它是否有所作为,但事实并非如此。我根本没有看到曾经多次调用的可读回调。

例如,如果在客户端我调用:

node tcptest.js tx "Hello world" 12345 AAA 9

客户端的输出是:

sending
Connected
sending 4 args
[0] 10
[1] 5
[2] 3
[3] 1
Connection closed

但在服务器端,我看到的只有:

receiving
connected
[0]
Buffer [ 4, 0, 0, 0 ]
4 arguments
[1]
Buffer [ 10, 0, 0, 0 ]
argument 0 has 10 bytes
[1]
Buffer [ 5, 0, 0, 0 ]
argument 1 has 5 bytes
socket ended
socket closed

我尝试不重复使用buf对象并且它已经工作了一段时间,但后来开始间歇性地失败了。

...
for (i=3;i<args+3;i++) {
    buf = new Buffer(4)
    console.log("["+(i-3)+"] "+process.argv[i].length)
...

当它再次开始工作一段时间后,我想也许有一些没有记录的东西,不允许缓冲区被重复用于传递写入,但现在我不知道。如果我根本不使用缓冲区,例如如果我将stdin传递给套接字,那么它工作正常,但是如果我尝试构造像这样的二进制数据并不总是得到接收。

我可能做错了什么?

更新: 如果我在服务器端调用read()而不是指定字节数,那么我会看到收到的所有数据!我怀疑发生的事情是,如果我读取的字节数少于可用的字节数,我就不会得到额外的回调。

1 个答案:

答案 0 :(得分:0)

“可读”事件仅针对收到的每个附加数据块触发一次。如果没有读取所有可用数据,上面的代码假定它会在返回时再次触发,但事实并非如此。

以下代码通过将状态机包装在一个循环中来解决问题,该循环继续提取数据,直到它无法获取下一个预期的块:

x.on("readable",function() {
    while (true) {
        switch (state) {
        case 0:
            var n = x.read(4)
            console.log("[0]"); console.dir(n)
            if (n === null) return
            args=n.readInt32LE(0)
            console.log(args+" arguments")
            state = 1 ; argn = 0
            break
        case 1:
            var n = x.read(4)
            console.log("[1]"); console.dir(n)
            if (n === null) return
            console.log("argument "+argn+" has "+n.readInt32LE(0)+" bytes")
            if (++argn > args) {
                console.log("end of arguments")
                state = 0
            }
            break
        default:
            console.log("state="+state+", now what?")
            return
    }
}