Socket服务端报 :“段错误 (核心已转储)” 附代码
发布于 9 年前 作者 FanTaSyLin 5842 次浏览 最后一次编辑是 8 年前 来自 问答

网上说是由于内存溢出导致的 可我通过打印 bufferWritePos 和 bufferReadPos 以及process.memoryUsage 没有发现指针越界呀 以下是服务端代码,主要目的是接收客户端传来的流数据,然后截取图像数据保存在文件中。客户端数据 包大小20647字节 发送间隔10ms, 运行一会就会报:“段错误 (核心已转储)”

/**
 * Created by fanlin on 7/28/15.
 */
'use strict'

var port = process.argv[2];
var net = require('net');
var fs = require('fs');
var path = require('path');
var util = require('util');
var log = require('../lib/log');
var dataBuffer = undefined; //数据缓冲区
var decoderList = [];
var fWriteStream = undefined;
var maxISKSize = 0;
var bufferWritePos = 0; //缓冲区写指针的位置
var bufferReadPos = 0; //缓冲区读指针的位置
var totalReceive = 0;
var tmpDataPath = '../temp/tmp.dat';

//启动脚本
start();

function start(){

    try{
        //初始化
        initServer();
        //创建服务
        createServer();
    }catch (err){
        console.log(err);
    }

}

function initServer(){
    //提前加载所有的decoder
    var pathname = path.resolve(__dirname, '../decoder');
    var files = fs.readdirSync(pathname.toString());
    files.forEach(function(file){
        var filename = pathname + '/' + file;
        var flg = fs.lstatSync(filename);
        if(!flg.isDirectory()){
            var item = require(filename);
            if(item.hasOwnProperty('infoType') && item.hasOwnProperty('iskSize')){
                decoderList[item['infoType']] = item;
                //找到数据的最大长度, 拆分数据时根据此数值创建数据缓冲区的大小
                if(maxISKSize < item['iskSize']){
                    maxISKSize = item['iskSize'];
                }
            }

        }
    });
}

function createServer(){
    var server = net.createServer(function(socket){
        console.log('socket connected');
        socket.write('connected');
        socket.setTimeout(15*1000);
        log.info("建立链接,Client Address:" + socket.remoteAddress);

        //创建数据缓冲区
        dataBuffer = new Buffer(maxISKSize*200);

        var pathname = path.resolve(__dirname, '../temp');
        tmpDataPath = pathname.toString() + '/' + 'tmp.dat';
        fs.open(tmpDataPath, 'w', function(err, fd){
            if(err) {
                log.warning(err.toString());
            }
            fWriteStream = fs.createWriteStream(tmpDataPath);
        });

        socket.on('data', function(data){
            //接收数据 并根据数据内容进行处理
            try {
                //console.log(data.length);

                dataBuffer.fill(data, bufferWritePos, bufferWritePos + data.length);
                bufferWritePos += data.length;
                dataSplite(data);
            }
            catch (e){
                console.log(e.message);
            }
        });

        socket.on('close', function(err){
            if(err){
                console.error(err);
                log.warning("Socket出现一次异常关闭,此链接已断开,Client Address:" + socket.remoteAddress + ". ERR:" + err.msg)
            }else{
                //console.error("Socket正常关闭。");
            }
            log.info("关闭链接,Client Address:" + socket.remoteAddress);
        });

        socket.on('error', function(err){
            console.error(err);
            try{
                log.error("Socket异常关闭,Client Address:" + socket.remoteAddress);
            }catch (err){
                log.error("Socket异常关闭,Client Address:" + socket.remoteAddress);
            }
        });

        socket.on('end', function(){
            //会话终止
            console.log('Receive Total: ' + totalReceive);
            fWriteStream.end();
            totalReceive = 0;
        });

        socket.on('timeout', function(){

        })
    });
    server.listen(port, function(){

    });
}

function dataSplite(data){

    /*
     * 首先开辟一个buffer 大小为所有数据帧长的最大值
     * 将数据放入buffer中。
     * 根据首2个字节确定 decoderType
     * 通过decoderType 确定数据帧长 (如果是定长数据帧可直接确定,如果是变长帧则需要通过第3、4两个字节确定实际帧长)
     * 1.定长帧处理方式:
     *      判断当前数据长度,
     *      如果 = 期望帧长 则直接处理
     *      如果 < 期望帧长 则填入buffer等待后续数据
     *      如果 > 期望帧长 则截取出期望帧长的数据,然后将剩余数据填入buffer等待后续数据
     * 2.变长帧处理方式:
     *      暂不需要
     */

    var total = bufferWritePos;
    bufferReadPos = 0;
    while(bufferReadPos < total){
        var infoType = 2;//data.readUInt16LE(0);
        var iskSize = getISKSize(infoType);
        var isVarLenght = (iskSize == -1) ? true : false;
        if(isVarLenght){
            //如果是变长isk 则进行一下处理
            //TODO:暂不考虑
        }else{
            //
        }

        /*
         * 如果 iskSize 等于 0 则表示 infoType 为非法
         * 应丢弃缓冲区中的所有数据
         */
        if(iskSize == 0){
            bufferWritePos = 0;
            return;
        }
        if(bufferReadPos + iskSize <= total){
            //分割出一个完整的isk数据,进行后续处理
            var iskData = new Buffer(iskSize);
            dataBuffer.copy(iskData, 0, bufferReadPos, bufferReadPos + iskSize);
            dataProcess(iskData, decoderList[infoType]);
            //读指针偏移
            bufferReadPos += iskSize;
            if(bufferReadPos == total){
                bufferWritePos = 0;
            }
        }else{
            /*
             * 目前的缓冲区中的数据无法组成一个完整的isk消息
             * 将当前缓冲区中的数据全部移动到缓冲区的前端,以等待后续数据
             */
            bufferWritePos = total - bufferReadPos;
            if(bufferReadPos > 0) {
                dataBuffer.copy(dataBuffer, 0, bufferReadPos, total);
            }
            break;
        }
    }
}

function dataProcess(data, decoderObj){

    //测试代码段
    totalReceive += data.length;
    console.log("wp : " + bufferWritePos +
        "; rp : " +  bufferReadPos +   
        "; mem: " + util.inspect(process.memoryUsage()));

    if(decoderObj.hasOwnProperty('execute')){
        var imageCode = decoderObj.execute(data);
        fWriteStream.write(data);
        //saveImageCode(imageCode, decoderObj);
    }
}

function saveImageCode(code, decoderObj){
    var route;
    /*
    if(decoderObj.hasOwnProperty('decoderName')){

        route = '../temp/' + decoderObj.decoderName + '.dat';
    }else{
        route = '../temp/tmp.dat';
    }
     */
}

function getISKSize(infoType) {
    if (decoderList[infoType] == undefined) {
        return 0;
    } else {
        return decoderList[infoType].iskSize;
    }
}
4 回复

段错误不仅仅是指针越界错误,访问非法指针(访问不存在的内存),栈溢出都有可能出现。还有一些莫名其妙的程序跑飞掉(多数因为一些不起眼的错误)都会引起段错误。 关键是定位,如果是linux可以用valgrid,用debug版本来调试定位。 如果确定在这个代码范围之内的话,可以注释排除,定位后再找出错原因。

        //创建数据缓冲区
        dataBuffer = new Buffer(maxISKSize*200);

这一句可能性比较大,var maxISKSize = 0;,new Buffer(0)应该是分配到了一个合法的地址,但是这个地址并没有可访问的内存。 具体见源代码src/smalloc.cc440行,malloc(0)返回的不一定是nullptr,可能是一个合法的指针,具体要看malloc的实现,如果是合法的指针,这个指针其实没有任何可访问的内存空间,所以你后续对内存的访问都是非法的。内存分配成功了,但是只是分配了内存头(储存内存链表信息),但是实际供用户使用的内存空间为0。

@coordcn 能推荐一个比较好用的内存泄露调试工具么? valgrind 网上查不到如何调试js代码。

@FanTaSyLin valgrind是为c或c++服务的,内存泄漏检测只是它的一个功能,我主要用它来检测非法内存访问,js不知道用什么来调试这个问题,主要是自己小心吧。你碰到的这个问题如果的确像我说的,那这不是js的问题,而是c的问题。

内存泄漏检测工具可以问站长或其他高手,这个我不了解。我个人认为主要还是要依靠良好的编码习惯,清楚哪些代码可能会出现内存泄漏,自己编码的时候时刻注意。

段错误是最恶心的错误,很多时候都是莫名其妙的,我前段时间在一个段错误上耗了半个月时间,到最后才弄明白的,我在用lua + libuv实现一个伪同步的tcpserver,其中用到uv_timer_t,我原来以为timer不是handle,不需要像uv_tcp_t一样显式的uv_close,没有经过close的timer其实是在一个链表上的,直接释放某个timer的内存会导致链表前后的timer试图访问一个不能访问的内存,段错误就发生了。我之前一直以为是lua自动内存回收造成的,以为lua对内存管理有问题,其实是自己没有好好理解libuv的timer造成的。

回到顶部