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

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

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

  • sptcp.js
/**
 * 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
/**
 * 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
/**
 * 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();
});
时间: 2024-08-01 15:21:04

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

Unix网络编程之基本TCP套接字编程(上)

TCP客户/服务器实例 服务器程序 #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); //1 bzero(&servaddr, sizeof(servad

LINUX TCP套接字详细配置

提高服务器的负载能力,是一个永恒的话题.在一台服务器CPU和内存资源额定有限的情况下,最大的压榨服务器的性能,是最终的目的.要提高 Linux系统下的负载能力,可以先启用Apache的Worker模式,来提高单位时间内的并发量.但是即使这么做了,当网站发展起来之后,连接数过多 的问题就会日益明显.在节省成本的情况下,可以考虑修改Linux的内核TCP/IP参数,来最大的压榨服务器的性能.当然,如果通过修改内核参数也无法 解决的负载问题,也只能考虑升级服务器了,这是硬件所限,没有办法的事. Lin

TCP套接字端口复用SO_REUSEADDR

下面建立的套接字都是tcp套接字 1.进程创建监听套接字socket1,邦定一个指定端口,并接受了若干连接.那么进程创建另外一个套接口socket2,并试图邦定同一个端口时候,bind错误返回“Address already in use”(即使使用了SO_REUSEADDR). 2.进程创建监听套接字,邦定一个指定端口,并接受了若干连接,为每个连接创建子进程为连接服务.杀死监听套接字所在进程,然后重新启动.重新启动的进程调用bind重新建立监听套接字.这次邦定只有在bind前指定了SO_REU

《网络编程》基于 TCP 套接字编程的分析

本节围绕着基于 TCP 套接字编程实现的客户端和服务器进行分析,首先给出一个简单的客户端和服务器模式的基于 TCP 套接字的编程实现,然后针对实现过程中所出现的问题逐步解决.有关基于 TCP 套接字的编程过程可参考文章<基本 TCP 套接字编程>.该编程实现的功能如下: (1)客户端从标准输入读取文本,并发送给服务器: (2)服务器从网络输入读取该文本,并回射给客户端: (3)客户端从网络读取由服务器回射的文本,并通过标准输出回显到终端: 简单实现流图如下:注:画图过程通信双方是单独的箭头,只

【UNIX网络编程(二)】基本TCP套接字编程函数

基于TCP客户/服务器程序的套接字函数图如下: 执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型. #include <sys/socket.h> int socket(int family, int type, int protocol);/*返回值:若成功则为非负描述符,若出错则为-1*/ socket函数成功时返回一个小的非负整数值,它与文件描述符类似,把它称为套接字描述符,简称sockfd.family参数指明协议族,被称为协议域.type参数指

【UNIX网络编程(四)】TCP套接字编程详细分析

引言: 套接字编程其实跟进程间通信有一定的相似性,可能也正因为此,stevens这位大神才会将套接字编程与进程间的通信都归为"网络编程",并分别写成了两本书<UNP1><UNP2>.TCP套接字编程是套接字编程中非常重要的一种,仔细分析,其实它的原理并不复杂.现在就以一个例子来详细分析TCP套接字编程. 一.示例要求: 本节中试着编写一个完成的TCP客户/服务器程序示例,并对它进行深入的探讨.该示例会用到绝大多数的基本函数,未用到但比较重要的函数会在后面的补充上

《网络编程》基本 TCP 套接字编程

在进行套接字编程之前必须熟悉其地址结构,有关套接字的地址结构可参考文章<套接字编程简介>.基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符.客户端随后调用connect 函数,服务器端则调用 bind.listen 和accept 函数.套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字.下面针对套接字编程实现过程中所调用的函数进程分析.以下是基于 TCP 套接字编程的流程图: socket 函数 套接

Unix网络编程学习笔记之第4章 基于TCP套接字编程

1. socket函数 int socket(int family, int type,int protocol) 成返回一个套接字描述符.错误返回-1 其中family指定协议族,一般IPv4为AF_INET, IPv6为AF_INET6. 其中type指定套接字类型,字节流:SOCK_STREAM.   数据报:SOCK_DGRAM. 一般情况下通过family和type的组合都可以唯一确定一个套接字类型.所以一般我们就把protocol设为0就可以了. 有时在某些特殊情况下,family和

第4章 基本tcp套接字编程

4.1 各种套接字api(重要) 4.1.1 socket() 用于创建一个套接字描述符,这个描述符指明的是tcp还是udp,同时还有ipv4还是ipv6 #include <sys/socket.h>?int socket(int family, int type, int protocol);//成功返回描述符,错误-1 family主要是指明的协议族,AF_INET:ipv4.AF_INET6:ipv6 .AF_LOCAL:unix域协议.AF_ROUTE:路由套接字.AF_KEY秘钥套