tcpServer分包踩坑记录

需求

收集各个linux机器的信息

技术方案

server侧:用golang起一个tcpServer
client侧:用php的fsockopen和tcpServer建立长连接,来实时传输数据

实现细节

golang tcpServer代码
func main() {

    packEof := []byte("#\r\n\r\n") //自动分包分隔符
    tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:8088")
    if err != nil {
        fmt.Println("tcp start error")
    }
    //侦听
    tcpListener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil {
        fmt.Println("listen error")
    }

    fmt.Println("start server successful......")
    //开始接收请求
    for {
        conn, err := tcpListener.Accept()
        if err != nil {
            fmt.Println("accept tcp client  err %s", conn.RemoteAddr().String())
        }
        defer conn.Close()

        util.ReadPackage(nil, conn, func(pack []byte) bool {
            conn.Write(pack)
            return true
        }, packEof)
    }
}


func ReadPackage(ctx context.Context, conn net.Conn, call ReadPackageCall, packEof ...interface{}) error {
    msgBuffer := make([]byte, 0)
    isEofModel := len(packEof) > 0
    eof := []byte{}

    switch packEof[0].(type) {
    case string:
        eof = []byte(packEof[0].(string))
    case []byte:
        eof = packEof[0].([]byte)
    }

    eofLen := len(eof)

    for {

        if ctx != nil {
            select {
            case <-ctx.Done():
                return errors.New("timeout")
            default:
            }
        }

        buffer := make([]byte, 256)
        n, err := conn.Read(buffer)
        if err != nil {
            return err
        }

        buffer = buffer[:n]
        if len(msgBuffer) > 0 {
            msgBuffer = append(msgBuffer, buffer...)
        } else {
            msgBuffer = buffer
        }

        if isEofModel {
            for {
                idx := bytes.Index(msgBuffer, eof)
                if idx == -1 {
                    break
                }

                doNext := call(msgBuffer[:idx])
                if !doNext {
                    return nil
                }

                idx += eofLen
                msgBuffer = msgBuffer[idx:]
            }
        } else {
            for {
                // 数据不足
                if len(msgBuffer) < 4 {
                    break
                }
                // 前4个字节是包长度信息
                packLen, err := strconv.Atoi(string(msgBuffer[:4]))
                if err != nil {
                    return err
                }

                l := packLen + 4

                // 数据不足
                if len(msgBuffer) < l {
                    break
                }

                doNext := call(msgBuffer[:l])
                if !doNext {
                    return nil
                }

                msgBuffer = msgBuffer[l:]
            }
        }
    }

    return nil
}

php client侧代码

<?php
/**
 * Class Task
 * @package Weipaitang\Framework\ServiceCenter
 */
class Task
{
    const SECTOR_SIZE = 512;

    /**
     * @var array
     */
    private $disk = [];

    /**
     * @return array
     */
    public function status()
    {
        $status = [];

        // linux system info
        if (PHP_OS == 'Linux') {
            $status = $this->getInfo();
        } else {
            sleep(1);
        }

        $status['load_avg'] = join(', ', array_map(function ($v) {
            return round($v, 2);
        }, sys_getloadavg()));

        return $status;
    }

    /**
     * @return array
     */
    public function getInfo()
    {
        $this->disk = $this->parsePartitions();

        $info = [];

        $prevIO  = $this->parseIOStats();
        $prevNet = $this->parseNet();

        sleep(1);

        $currIo  = $this->parseIOStats();
        $currNet = $this->parseNet();

        foreach ($this->disk as $disk) {
            $info['io'][$disk] = $this->calcIO($prevIO[$disk], $currIo[$disk]);
        }
        unset($prevIO, $currIo);

        foreach ($prevNet as $device => $net) {
            $receiveSpeed  = $currNet[$device]['receive'] - $net['receive'];
            $transmitSpeed = $currNet[$device]['transmit'] - $net['transmit'];

            $info['net'][$device] = [
                'receive'  => bcdiv($receiveSpeed * 8, 1048576, 2) . 'm/s',
                'transmit' => bcdiv($transmitSpeed * 8, 1048576, 2) . 'm/s'
            ];
        }
        unset($currNet, $prevNet);

        $info['cpu']    = $this->parseCpu(). '%';
        $info['memory'] = $this->parseMemory();
        $info['disk']   = $this->parseDisk();

        return $info;
    }

    /**
     * SystemInfo constructor.
     */
    public function parsePartitions()
    {
        $disk = [];

        $partitions = array_slice(array_filter(explode("\n", file_get_contents('/proc/partitions'))), 1);
        foreach ($partitions as &$partition) {
            if (is_numeric(substr($partition, -1, 1))) {
                continue;
            }

            $disk[] = array_slice(preg_split('/\s+/', $partition), -1)[0];
        }
        unset($partitions);

        return $disk;
    }

    /**
     * @return array
     */
    public function parseIOStats()
    {
        $microtime = microtime(true);

        $data = [];

        $diskstats = array_filter(explode("\n", file_get_contents('/proc/diskstats')));
        foreach ($diskstats as &$diskstat) {
            $diskstat = array_slice(preg_split('/\s+/', trim($diskstat)), 2);

            if (! in_array($diskstat[0], $this->disk)) {
                continue;
            }

            $data[$diskstat[0]] = [
                'r_ios'     => $diskstat[1],
                'r_sec'     => $diskstat[3],
                'r_ticks'   => $diskstat[4],
                'w_ios'     => $diskstat[5],
                'w_sec'     => $diskstat[7],
                'w_ticks'   => $diskstat[8],
                'tot_ticks' => $diskstat[10],
                'time'      => $microtime,
            ];
        }

        return $data;
    }

    /**
     * @param array $last
     * @param array $curr
     * @return array
     */
    public function calcIO(array $last, array $curr)
    {
        $stat = [];

        $diff = function ($field) use ($last, $curr) {
            return $curr[$field] - $last[$field];
        };

        $stat['rkB/s']  = $diff('r_sec') * self::SECTOR_SIZE / 1024;
        $stat['wkB/s']  = $diff('w_sec') * self::SECTOR_SIZE / 1024;

        $div = $diff('r_ios') + $diff('w_ios');
        if ($div > 0) {
            $stat['await'] = ($diff('r_ticks') + $diff('w_ticks')) / $div;
        } else {
            $stat['await'] = 0;
        }

        $stat['util'] = $diff('tot_ticks') / 10;

        array_map(function ($item) {
            return (float)number_format($item, 3);
        }, $stat);

        $retStr = "";
        if (!empty($stat)) {
            foreach ($stat as $_key => $_stat) {
                $retStr .= $_key . ":" . $_stat . " ";
            }
        }
        return $retStr;
    }

    /**
     * @return float
     */
    public function parseCpu()
    {
        return 100.0 - (float) trim(shell_exec("vmstat | tail -1 | awk '{print $15}'"));
    }

    /**
     * @return string
     */
    public function parseMemory()
    {
        $memory = file_get_contents('/proc/meminfo');

        preg_match('/MemTotal:\s+(\d+)/', $memory, $match);
        $total = bcdiv((int) $match[1], 1048576, 2);

        preg_match('/MemFree:\s+(\d+)/', $memory, $match);
        $free = bcdiv((int) $match[1], 1048576, 2);

        return ($total - $free) . 'G/'. $total. "G";
    }

    /**
     * @return array
     */
    public function parseNet()
    {
        $net = array_filter(explode("\n", file_get_contents('/proc/net/dev')));
        $net = array_map('trim', array_slice($net, 2));

        $info = [];
        foreach ($net as $item) {
            $item   = preg_split('/\s+/', $item);
            $device = str_replace(':', '', $item[0]);

            $info[$device] = [
                'receive'  => $item[1],
                'transmit' => $item[9]
            ];
        }

        return $info;
    }

    /**
     * @return array
     */
    public function parseDisk()
    {   
        $disk = trim(shell_exec('df -h|awk \'{print $1" "$2" "$3" "$4}\'|grep "/dev"'));
        return array_map(function ($item) {
            return explode('|', $item);
        }, explode("\n", $disk));
    }
}
$task = new Task();

$fp = null;
while(true) {
    if (is_null($fp)) {
       $fp = fsockopen("192.168.0.108", 8088, $errno, $errstr, 30); 
    }
    $content = json_encode($task->status()) . "#\r\n\r\n";
    fwrite($fp, $content);
    $result = fread($fp, 1024);
    echo "result:$result";
    sleep(3);
}
fclose($fp);

踩坑点

#如果后面用单引号,就当做真正的字符串处理了
$content = json_encode($task->status()) . '#\r\n\r\n';

抓包记录

这里写图片描述
这里写图片描述

总结

凡通信调试者,多抓包则能快速找到问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值