【NodeJs】使用TCP套接字收發數據的簡單實例


因為TCP協議是流協議,在收發數據的時候會有粘包的問題。本例使用自定義的SPtcp封包協議對TCP數據再進行一次封裝,解決了粘包問題。

注:其性能仍有待優化。優化方向:使用TCP自帶的接收窗口緩存。

  • sptcp.js
/**
 * script: sptcp.js
 * description: 簡單封包協議SPtcp類
 * authors: alwu007@sina.cn
 * date: 2016-04-14
 */

var util = require('util');

function SPtcp(socket) {
    //解析所處的階段
    var _sp_parse_step = SPtcp.SP_PARSE_STEP.HEADER;
    //接收緩存
    var _sp_rcv_buf = new Buffer(0);
    //包頭
    var _sp_header = null;
    //包體
    var _sp_body = null;
    //套接字
    this.socket = socket;

    //解析整包方法
    function _spParseSPPacket(func){
        if (_sp_rcv_buf.length >= SPtcp.SP_HEADER_LENGTH) {
            //解析包頭
            _sp_header = {bodyLength: _sp_rcv_buf.readUInt16LE(0, true)};
            //裁剪接收緩存
            _sp_rcv_buf = _sp_rcv_buf.slice(SPtcp.SP_HEADER_LENGTH);
            //解析包體
            _sp_parse_step = SPtcp.SP_PARSE_STEP.BODY;
            _spParseBody(func);
        }
    };

    //解析包體方法
    function _spParseBody(func){
        if (_sp_rcv_buf.length >= _sp_header.bodyLength) {
            var packet = _sp_rcv_buf.toString('utf8', 0, _sp_header.bodyLength);
            util.log('['+socket.remoteAddress+']->['+socket.localAddress+'] receive: '+packet);
            //裁剪接收緩存
            _sp_rcv_buf = _sp_rcv_buf.slice(_sp_header.bodyLength);
            //處理消息
            try {
                var msg = JSON.parse(packet);
                func(msg);
            } catch(e) {
                util.log(e);
            }
            //清空包頭和包體
            _sp_header = null;
            _sp_body = null;
            //解析下一個包
            _sp_parse_step = SPtcp.SP_PARSE_STEP.HEADER;
            _spParseSPPacket(func);
        }
    };

    //接收數據
    this.spReceiveData = (data, func) => {
        if (!func) func = msg => undefined;
        //合並新舊數據
        _sp_rcv_buf = Buffer.concat([_sp_rcv_buf, data]);
        //解析處理數據
        if (_sp_parse_step == SPtcp.SP_PARSE_STEP.HEADER) {
            _spParseSPPacket(func);
        } else if (_sp_parse_step == SPtcp.SP_PARSE_STEP.BODY) {
            _spParseBody(func);
        }
    };

    //發送數據
    this.spSendData = msg => {
        var packet = JSON.stringify(msg);
        var body_buf = new Buffer(packet);
        var head_buf = new Buffer(SPtcp.SP_HEADER_LENGTH);
        head_buf.writeUInt16LE(body_buf.length);
        var snd_buf = Buffer.concat([head_buf, body_buf]);
        this.socket.write(snd_buf);
    };

    //銷毀方法
    this.spDestroy = () => {
        delete this.socket;
    };
}

//包頭長度,單位字節
SPtcp.SP_HEADER_LENGTH = 4;
//解析所處的階段
SPtcp.SP_PARSE_STEP = {
    HEADER: 0,  //解析包頭階段
    BODY: 1,    //解析包體階段
};

exports.SPtcp = SPtcp;
  •  spsvr.js
/**
 * script: spsvr.js
 * description: SPtcp服務器端
 * authors: alwu007@sina.cn
 * date: 2016-04-15
 */

var util = require('util');
var net = require('net');
var SPtcp = require('./sptcp').SPtcp;

var server = net.createServer(client => {
    util.log('client connected: ' + client.remoteAddress);
    //套接字繼承SPtcp
    SPtcp.call(client, client);
    //監聽data事件
    client.on('data', data => {
        client.spReceiveData(data, msg => {
            util.log('susl msg: ' + util.inspect(msg));
            client.spSendData(msg);
        });
    });
    //監聽結束事件
    client.on('end', () => {
        util.log('disconnected from client: ' + client.remoteAddress);
        client.spDestroy();
    });
    //監聽錯誤事件
    client.on('error', err => {
        util.log(err);
        client.end();
    });
});

var listen_options = {
    host: '172.16.200.26',
    port: 6200,
};
util.log('listen options: ' + util.inspect(listen_options));
server.listen(listen_options, () => {
    util.log('server bound');
});
  •  spcli.js
/**
 * script: spcli.js
 * description: SPtcp客戶端
 * authors: alwu007@sina.cn
 * date: 2016-04-15
 */

var util = require('util');
var net = require('net');
var SPtcp = require('./sptcp').SPtcp;

var connect_options = {
    host: '172.16.200.26',
    port: 6200,
    localPort: 6201,
};
util.log('connect options: ' + util.inspect(connect_options));
var client = net.connect(connect_options, ()=>{
    //套接字繼承SPtcp
    SPtcp.call(client, client);
    //監聽data事件
    client.on('data', data => {
        client.spReceiveData(data, msg => {
            util.log('susl msg: ' + util.inspect(msg));
        });
    });
    //監聽結束事件
    client.on('end', () => {
        util.log('disconnected from server: ' + client.remoteAddress);
        client.spDestroy();
    });
    //監聽錯誤事件
    client.on('error', err => {
        util.log(err);
        client.end();
    });
    //發送消息
    for (var i=0; i<10; i++) {
        var msg = {op:'test', msg:'hello, 草谷子!', times:i};
        client.spSendData(msg);
    }
    //關閉連接
    client.end();
});

優化方案1:接收緩存_sp_rcv_buf改為Buffer數組,並記錄數組元素的長度和_sp_rcv_length。這樣做的好處有兩點,一點是不用每次收到數據就執行一次concat方法分配一塊新的內存;一點是在執行concat方法時直接傳入長度參數,加快該方法的執行速度。——於2016-04-16

優化方案2:將類的方法定義在prototype原型對象上,這樣該類的所有實例就共用同一個方法副本,節約資源。——於2016-04-19


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM