需求
收集各个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';
抓包记录
总结
凡通信调试者,多抓包则能快速找到问题