zoukankan      html  css  js  c++  java
  • 【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

  • 相关阅读:
    JDBC事务管理
    JDBC常见操作
    Java集合之List接口
    Nginx+Keepalived+Lvs实现双机热备
    Nginx+Consul+Upsync实现动态负载均衡
    DNS域名解析概念
    WPF中实现两个窗口之间传值
    C# 重写(override)和覆盖(new)
    C# DateTime.Now函数
    WPF中在后台实现控件样式
  • 原文地址:https://www.cnblogs.com/alwu007/p/5395035.html
Copyright © 2011-2022 走看看