【NodeJs】使用TCP套接字收发数据的简单实例

原文地址:https://www.cnblogs.com/alwu007/p/5395035.html

因为TCP协议是流协议,在收发数据的时候会有粘包的问题。本例使用自定义的SPtcp封包协议对TCP数据再进行一次封装,解决了粘包问题。

注:其性能仍有待优化。优化方向:使用TCP自带的接收窗口缓存。

  • sptcp.js

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

/**

 * script: sptcp.js

 * description: 简单封包协议SPtcp类

 * authors: [email protected]

 * 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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

/**

 * script: spsvr.js

 * description: SPtcp服务器端

 * authors: [email protected]

 * 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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

/**

 * script: spcli.js

 * description: SPtcp客户端

 * authors: [email protected]

 * 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

你可能感兴趣的:(Node.js)