以node.js v10.5.0 的net模块为例子,深刻理解粘包问题(4.x不行, 对Buffer.from等api不支持,解析Buffer错误)

--------------------------有问题的写法(故意发送很长的字符串,让客户端多次接收)---------------------------

1)Server.js(注意必须实现 "error"的监听, 不然客户端断开,服务端就宕机了, 当然也要实现 "close"事件监听)

var net = require("net")

var server = net.createServer(function (socket) {
    console.log("client connect");

    socket.on("error", function () {
        console.log("error");
    });

    socket.on("close", function () {
        console.log("client disconnect");

        socket.end();
    });

    socket.on("data", function (data) {
        socket.write(data);
    });

});

server.on("close", function () {
    console.log("client close");
});

server.listen({
    port: 6080,
    exclusive: true
});

console.log("server start!");

2)Client.js

var net = require("net")

var socket = net.connect({
    host: "127.0.0.1",
    port: 6080
});

socket.on("connect", function () {
    console.log("connect to server success");

    var str = "";
    for(var i = 0; i < 100000; i++){
        str += "0123456789";
    }


    socket.write(str);
});

socket.on("close", function () {
    console.log("disconnect");

    socket.end();
});

socket.on("data", function (data) {
    // console.log("recv from server:", data);

    // 解析为字符串
    var str = data.toString("utf8", 0, data.length);
    console.log(str.length)
});

socket.on("error", function () {
    console.log("error");
});

3)运行结果

可见客户端打印了多次说明分多次包才把服务器发来的数据包收完

--------------------------解决粘包问题的写法(故意发送很长的字符串,让客户端多次接收)---------------------------

1.TcpPkg.js

var TcpPkg = {
    readPkgSize: function (pkgData, offset) {
        if (offset > pkgData.length - 4) {
            return -1;
        }

        var len = pkgData.readUInt32LE(offset);
        return len;
    },


    packageData: function (data) {
        var buf = Buffer.allocUnsafe(4 + data.length);

        buf.writeUInt32LE(4 + data.length, 0);

        buf.fill(data, 4);

        return buf;
    }
};

module.exports = TcpPkg;

2.Server.js

var net = require("net")

var TcpPkg = require("./TcpPkg.js");

var server = net.createServer(function (socket) {
    console.log("client connect");

    socket.on("error", function () {
        console.log("error");
    });

    socket.on("close", function () {
        console.log("client disconnect");

        socket.end();
    });

    socket.on("data", function (data) {
        if (!Buffer.isBuffer(data)) {
            console.error("收到的不是Buffer");
            return;
        }

        console.log("temp data:", data);

        var lastPkg = socket.lastPkg;

        if (lastPkg != null) {
            var buf = Buffer.concat([lastPkg, data], lastPkg.length + data.length);
            lastPkg = buf;
        } else {
            lastPkg = data;
        }

        var offset = 0;
        var pkgLen = TcpPkg.readPkgSize(lastPkg, offset);
        if (pkgLen < 0) {
            socket.lastPkg = lastPkg;
            return;
        }

        while (offset + pkgLen <= lastPkg.length) {
            var cmdBuf = Buffer.allocUnsafe(pkgLen - 4);

            // 读取到一个完整的命令包放到cmdBuf
            lastPkg.copy(cmdBuf, 0, offset + 4, offset + pkgLen);

            // 编码加上包头后发送过去
            console.log("接收到数据的长度:", cmdBuf.length)
            var finalBuf = TcpPkg.packageData(cmdBuf);
            socket.write(finalBuf);

            offset += pkgLen;

            if (offset >= lastPkg.length) {
                break;
            }

            pkgLen = TcpPkg.readPkgSize(lastPkg, offset);
            if (pkgLen < 0) {
                break;
            }
        }

        if (offset >= lastPkg.length) {
            lastPkg = null;
        } else {
            var buf = Buffer.allocUnsafe(lastPkg.length - offset);
            lastPkg.copy(buf, 0, offset, lastPkg.length);
            lastPkg = buf;
        }

        socket.lastPkg = lastPkg;
    });

});

server.on("close", function () {
    console.log("client close");
});

server.listen({
    port: 6080,
    exclusive: true
});

console.log("server start!");

3.Client.js

var net = require("net");

var TcpPkg = require("./TcpPkg.js");

var socket = net.connect({
    host: "127.0.0.1",
    port: 6080
});

socket.on("connect", function () {
    console.log("connect to server success");

    var str = "";
    for (var i = 0; i < 1000; i++) {
        str += "0123456789";
    }

    var cmdBuf = Buffer.from(str);
    var finalData = TcpPkg.packageData(cmdBuf);
    socket.write(finalData);
});

socket.on("close", function () {
    console.log("disconnect");

    socket.end();
});

socket.on("data", function (data) {
    if (!Buffer.isBuffer(data)) {
        console.error("收到的不是Buffer");
        return;
    }

    var lastPkg = socket.lastPkg;

    if (lastPkg != null) {
        var buf = Buffer.concat([lastPkg, data], lastPkg.length + data.length);
        lastPkg = buf;
    } else {
        lastPkg = data;
    }

    var offset = 0;
    var pkgLen = TcpPkg.readPkgSize(lastPkg, offset);
    if (pkgLen < 0) {
        socket.lastPkg = lastPkg;
        return;
    }

    while (offset + pkgLen <= lastPkg.length) {
        var cmdBuf = Buffer.allocUnsafe(pkgLen - 4);

        // 读取到一个完整的命令包放到cmdBuf
        lastPkg.copy(cmdBuf, 0, offset + 4, offset + pkgLen);

        // 原封不动的发送过去
        console.log("客户端收到服务器的字节长度:", cmdBuf.length);
        var str = cmdBuf.toString("utf8", 0, cmdBuf.length);
        console.log(str);

        offset += pkgLen;

        if (offset >= lastPkg.length) {
            break;
        }

        pkgLen = TcpPkg.readPkgSize(lastPkg, offset);
        if (pkgLen < 0) {
            break;
        }
    }

    if (offset >= lastPkg.length) {
        lastPkg = null;
    } else {
        var buf = Buffer.allocUnsafe(lastPkg.length - offset);
        lastPkg.copy(buf, 0, offset, lastPkg.length);
        lastPkg = buf;
    }

    socket.lastPkg = lastPkg;
});

socket.on("error", function () {
    console.log("error");
});

4.效果

可见服务器依然分了2次才把客户端的包收完,但是没关系,被暂存了,所以最后服务器将收到的包合并为完整的包,一次性发送给客户端,客户端也采用暂存包的方式,最终收到完整包时一次性解析出来命令。

猜你喜欢

转载自blog.csdn.net/themagickeyjianan/article/details/107029937