2016年12月13日 星期二

PHP socket,client 端傳送、接收資料範例

假設 server 端和 client 端,約定傳輸資料的前 4 個 bytes 為緊接著要傳輸資料的長度。
所以,client 端傳送的資料為 => 4 bytes 的數字(資料本體長度) + 資料本體
同樣,client 端接收到資料為 => 4 bytes 的數字(資料本體長度) + 資料本體
//client 傳送:前4個bytes為要傳送的資料本體長度,後面緊接著資料本體
//server 回應:前4個bytes為回應的資料本體長度,後面緊接著資料本體
$data = "這是要傳送的資料本體";
//socket
$url = "http://example.com:8888";
$url_part = parse_url($url);
$host = $url_part["host"];
$address = gethostbyname($host);
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("socket_create() failed:" . $err_msg);
}
$conn = socket_connect($socket, $address, $api_def->getSocketPort());
if (!$conn) {
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("socket_connect() failed:" . $err_msg);
}

//傳送資料
$bin_body = pack("a*", $data);
//$bin_body = $data;
$body_len = strlen($bin_body);
$bin_head = pack("N", $body_len); //unsigned long (always 32 bit, big endian byte order)=>數字轉成一個 4 bytes 的big endian byte order
$bin_data = $bin_head . $bin_body; //4bytes資料本體長度 + 資料本體
$res = socket_write($socket, $bin_data, strlen($bin_data)); //返回成功寫入的bytes數 or false
if (!$res) {
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("socket_write() failed:" . $err_msg);
}

//接收資料
$buf = "";
//先取得 4bytes 回應資料本體長度
if (false === ($bytes = socket_recv($socket, $buf, 4, MSG_WAITALL))) { //返回接收到的長度 or false。MSG_WAITALL:阻塞模式,若過程沒異常,一直等到讀到指定長度
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("讀取資料長度失敗 socket_recv() failed:" . $err_msg);
}
$len_data = unpack("Nlen", $buf); //N:unsigned long (always 32 bit, big endian byte order)=>解析頭 4 個bytes

//再用取得的資料本體長度,抓回應資料本體
if (false === ($bytes = socket_recv($socket, $buf, $len_data["len"], MSG_WAITALL))) { //返回接收到的長度 or false。MSG_WAITALL:阻塞模式,若過程沒異常,一直等到讀到指定長度
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("讀取回應資料本體失敗 socket_recv() failed:" . $err_msg);
}
socket_close($socket);
var_dump($buf);//回應的資料本體



參考:
PHP: 深入pack/unpack - 陈亦的个人页面 - 开源中国社区
[转帖]pack/unpack用法----心得筆記 - Perl Forum
PHP: pack - Manual
PHP: socket_recv - Manual
bytearray - String to byte array in php - Stack Overflow
java的int和byte數組的相互轉換_我們關註網
IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re - houlaizhe221的专栏 - 博客频道 - CSDN.NET


2016年12月12日 星期一

位元組順序

因為不同電腦,資料儲存的位元組順序可能不同,所以電腦間要用網路傳輸資料時,要注意位元組的順序,才不會傳輸後,因解讀順序不同,導致資料意義變了。

一個整數資料,依將高數位資料,放在儲存位址的前或後,可區分為 big-endian、little-endian 兩種儲存順序,而在網路傳輸 TCP/IP 協議是使用 big-endian。
  • big-endian(大端序、大尾序):
    多位的整數,高位數存放在儲存位址前面(高位數放在前面的低位址,低位數放在後面的高位址)
  • little-endian(小端序、小尾序):
    多位的整數,低位數(Least Significant Bit、LSB)存放在儲存位址前面(低位數放在前面的低位址,高位數放在後面的高位址)
  • network byte order(網絡位元組序、網絡序):一般為大端序。
例如:一個16進位 int ,0x12345678,
高位數~低位數依序為:12、34、56、78
共占儲存位址4個位元組:0x100~0x103
大端序在每個位元組存放的資料 0x100:12、0x101:34、0x102:56、0x103:78
小端序在每個位元組存放的資料 0x100:78、0x101:56、0x102:34、0x103:12


相關名詞:
  • 最高有效位(Most Significant Bit,msb):
    n位二進位數字中的n-1位。有符號二進位數,負數採用反碼或補碼形式,此時msb用來表示符號,msb為1表示負數,0表示正數。
  • MSB(Most Significant Byte):
    多字節序列中具有最大權重的字節。
  • 最低有效位(Least Significant Bit,lsb):
    一個二進位數字中的第0位(即最低位)。二進位數中的最小的單位,可以用來指示數字很小的變化。
  • LSB(Least Significant Byte):
    多字節序列中最小權重的字節。

使用 PHP 判斷系統是大端序或小端序
//方法1
function isBigEndian() {
    $bin = pack("L", 0x12345678);
    $hex = bin2hex($bin);
    if (ord(pack("H2", $hex)) === 0x78) {
        return FALSE;
    }

    return TRUE;
}
//方法2
function isLittleEndian() {
    $testint = 0x00FF;
    $p = pack('S', $testint);
    return $testint===current(unpack('v', $p));
}
//方法3
function isLittleEndian() {
    return unpack('S',"\x01\x00")[1] === 1;
}


參考:
位元組順序 - 維基百科,自由的百科全書
PHP: 深入pack/unpack - 陈亦的个人页面 - 开源中国社区
How to get the endianness type in PHP?


其他:C-style strings 在記憶體體中位置。
截錄 Big and Little Endian
「Here are some facts you should know about C-style strings and arrays.
1.C-style strings are stored in arrays of characters.
2.Each character requires one byte of memory, since characters are represented in ASCII (in the future, this could change, as Unicode becomes more popular).
3.In an array, the address of consecutive array elements increases. Thus, & arr[ i ] is less than & arr[ i + 1 ].
4.What's not as obvious is that if something is stored in increasing addresses in memory, it's going to be stored in increasing "addresses" in a file. When you write to a file, you usually specify an address in memory, and the number of bytes you wish to write to the file starting at that address.

So, let's imagine some C-style string in memory. You have the word "cat". Let's pretend 'c' is stored at address 1000. Then 'a' is stored at 1001. 't' is at 1002. The null character '\0' is at 1003.
Since C-style strings are arrays of characters, they follow the rules of characters.」



2016年12月5日 星期一

Linux vi 將內容從一個檔複製到另一個檔

假設要將 testA 檔案部分內容,複製到 testB 檔
$ vi testA
先在 testA 檔內複製需要的部分,
例如,用「yy」指令複製目前這一行(此時複製的資料會存在暫存區)
在不退出 vi 的情況下,直接去編輯 testB 檔,
如此在暫存區的複製資料才會留著。
:vi testB

:e testB

此動作會關閉 testA,然後開啟 testB,
所以假設 testA 檔有修改到,但沒儲存,會過不去 testB
若要放棄testA修改,直接編輯testB,指令可改成
:vi! testB

:e! testB

到 testB 檔後,即可直接用「p」指令貼上

若 testA 檔,有好幾個地方,要分別複製到 testB 檔
則在 testA 檔複製時,可指定要放的暫存區(a~z)
先按「"」「a」,表示要使用 a 暫存區
再按「yy」指令複製,則會將資料放在 a 暫存區
同理,
先按「"」「b」,表示要使用 b 暫存區
再按「yy」指令複製,則會將資料放在 b 暫存區
如此便可將不同部分,放在不同暫存區,一次帶到 testB 檔,
然後在 testB 檔時,切換目前使用的暫存區,再使用「p」指令貼上。


[相關:選取特定區域後複製、貼上]
先按「v」後,移動游標,即可反白選取所要的區域字段。
再來可選擇按「d」,剪下選取區域的內容,
或是按「y」,複製選取區域的內容。
最後再用「p」貼上。


參考:
Vi- 編輯與修改
vim - Copy and paste content from one file to another file in VI - Stack Overflow
Checko's Blog: VI 學習 : copy and paste


2016年11月24日 星期四

PHP new self 、new static 比較

class A {
    public static function get_self() {
        return new self();
    }

    public static function get_static() {
        return new static();
    }
}

class B extends A {
    public function testB(){
        return 'testB';
    }
}

echo get_class(A::get_self()); // A
echo get_class(A::get_static()); // A
echo get_class(B::get_self());  // A
echo get_class(B::get_static()); // B

#echo B::get_self()->testB();  // Fatal error:  Uncaught Error: Call to undefined method A::testB()
echo B::get_static()->testB(); // testB
new self() => 生成的物件為實際寫有這句 code 的 class
new static() =>生成的物件為呼叫使用這句 code 的 class


參考:
php - New self vs. new static - Stack Overflow
class - What does new self(); mean in PHP? - Stack Overflow


MySQL InnoDB AUTO_INCREMENT 自動縮小?

MySQL InnoDB 的 auto-increment 計數器數值只儲存在記憶體中,所以重新啟動 MySQL 後,原本的 auto-increment 值就消失了。在需要取得 auto-increment 時,才重新取目前最大值來初始化 auto-increment
SELECT MAX(ai_col) FROM table_name FOR UPDATE;

因為這個特性,
所以若原本 InnoDB 資料表有1~5筆資料
id:1、2、3、4、5 => auto-increment:6

刪除了第2筆、第4筆、第5筆,資料剩第1筆、第3筆
id:1、3 => auto-increment:6

重新啟動 MySQL 後,auto-increment 會變成 4
id:1、3 => auto-increment:4
(註:若為MyISAM,重啟後仍為 6)


其他:
取得 AUTO_INCREMENT 值
//方法1
SHOW TABLE STATUS LIKE "資料表";
//方法2
SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_name = "資料表" AND table_schema = "資料庫";
//方法3 DATABASE()目前資料庫
SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_name = "資料表" AND table_schema = DATABASE();



參考:
MySQL :: MySQL 5.6 Reference Manual :: 14.8.6 AUTO_INCREMENT Handling in InnoDB
sql - How to get the next auto-increment id in mysql - Stack Overflow


2016年10月26日 星期三

PHP foreach 使用 reference 修改值

PHP array 使用 foreach 時,可以直接將代表值的變數($v),指定為 reference(&$v)
如此,可方便直接對值修改:
foreach ($arr as &$v){
    $v = $v*10;
}
.....

但這樣使用時須注意,最後須記得將 reference 變數 unset,
避免 reference 變數仍指到陣列的元素,不注意時,可能造成非預期的修改。
$arr = array("A", "B", "C");
foreach ($arr as &$v){
}
var_dump($arr);
foreach ($arr as $key => $v) {
    echo "{$key} => {$v}\n";
    var_dump($arr);
}
輸出結果,可發現第2次的freach,最後一個元素一直改變,
因為 reference 變數 $v 還一直代表著陣列最後一個元素,所以一直被重新賦值。
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  &string(1) "C"
}

0 => A
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  &string(1) "A"
}
1 => B
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  &string(1) "B"
}
2 => B
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  &string(1) "B"
}



使用完馬上unset(&$v),可避免非預期的最後一個元素值一直改變
$arr = array("A", "B", "C");
foreach ($arr as &$v){
}
unset($v);
var_dump($arr);
foreach ($arr as $key => $v) {
    echo "{$key} => {$v}\n";
    var_dump($arr);
}
輸出結果
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}

0 => A
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
1 => B
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
2 => C
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}




參考:
PHP: foreach - Manual

PHP、Java 的 Des 加密(ECB mode)

PHP 和 Java 間用 Des 加密(ECB mode) 來傳輸資料,要確保兩邊用同樣的key加解密的結果一樣。

PHP code:
/**
 * DES 加解密(ECB mode)
 */
class DesCrypt {

    private $key;

    /**
     * DES 加解密(ECB mode)
     * @param string $key 加密的key
     */
    public function __construct($key) {
        $this->key = $key;
    }

    /**
     * 加密(ECB mode)
     * @param string $str 要加密的資料
     * @return string
     * @throws \Exception
     */
    public function encrypt($str) {
        //獲得加密算法的分組大小
        $size = mcrypt_get_block_size('des', 'ecb');
        //填充不足字節數,傳入數據長度必須是 n * 分組大小
        $str = $this->pkcs5Pad($str, $size);
        $key = $this->key;
        //打開算法和模組對應的模塊
        $td = mcrypt_module_open('des', '', 'ecb', '');
        $key_max_len = mcrypt_enc_get_key_size($td);
        if (strlen($key) > $key_max_len) {
            throw new \Exception("key超過最大長度:{$key_max_len}");
        }
        //建立初始向量
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        //初始化加密所需的緩衝區
        //在 ECB 模式下,初始向量會被忽略, 在 CFB,CBC,STREAM,nOFB 和 OFB 模式下,必須提供初始向量
        mcrypt_generic_init($td, $key, $iv);
        //加密數據
        $data = mcrypt_generic($td, $str);
        //清理指定加密模組緩衝區
        mcrypt_generic_deinit($td);
        //關閉加密模組
        mcrypt_module_close($td);
        //將加密結果用base64編碼
        $data = base64_encode($data);
        return $data;
    }

    /**
     * 解密(ECB mode)
     * @param string $str 要解密的密文
     * @return string
     * @throws \Exception
     */
    public function decrypt($str) {
        //將base64編碼結果解開
        $str = base64_decode($str);
        $key = $this->key;
        //獲得加密算法的分組大小
        $td = mcrypt_module_open('des', '', 'ecb', '');
        $key_max_len = mcrypt_enc_get_key_size($td);
        if (strlen($key) > $key_max_len) {
            throw new \Exception("key超過最大長度:{$key_max_len}");
        }
        //建立初始向量
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        //$ks = mcrypt_enc_get_key_size($td);
        //初始化加密所需的緩衝區
        mcrypt_generic_init($td, $key, $iv);
        //解密數據
        $decrypted = mdecrypt_generic($td, $str);
        //清理指定加密模組緩衝區
        mcrypt_generic_deinit($td);
        //關閉加密模組
        mcrypt_module_close($td);

        //去除 pkcs5Pad 的填充值
        $result = $this->pkcs5Unpad($decrypted);
        return $result;
    }

    /**
     * 填充不足字節數
     * 1.將填充量取chr當填充值
     * 2.剛好滿也再填充一組
     * @param string $text
     * @param int $blocksize
     * @return string
     */
    private function pkcs5Pad($text, $blocksize) {
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }

    /**
     * 去除 pkcs5Pad 的填充值
     * @param string $text
     * @return boolean
     * @return string
     */
    private function pkcs5Unpad($text) {
        $pad = ord($text{strlen($text) - 1});
        if ($pad > strlen($text)) {
            return false;
        }
        if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
            return false;
        }
        return substr($text, 0, -1 * $pad);
    }

}
$Des = new DesCrypt("12345678");
$encrypt_res = $Des->encrypt("test data"); //l9TN9Ln/71IvFRjlhD8PaQ==
$Des->decrypt($encrypt_res); //test data
註:PHP要確定有裝 php-mcrypt
$ yum install php-mcrypt



Java Code:
import javax.crypto.*;
import java.security.*;
import javax.crypto.spec.*;

/**
 * DES算法
 *
 * @author user
 */
public class DES {

    /**
     * 加密函數
     *
     * @param data 加密數據
     * @param key 密鑰
     * @return 返回加密後的數據
     */
    public static byte[] encrypt(byte[] data, byte[] key) {

        try {

            // DES算法要求有一個可信任的隨機數源
            SecureRandom sr = new SecureRandom();

            // 從原始密鑰數據創建DESKeySpec對象
            DESKeySpec dks = new DESKeySpec(key);

            // 創建一個密匙工廠,然後用它把DESKeySpec轉換成
            // 一個SecretKey對象
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretKey = keyFactory.generateSecret(dks);

            // using DES in ECB mode
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");

            // 用密匙初始化Cipher對象
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, sr);

            // 執行加密操作
            byte encryptedData[] = cipher.doFinal(data);

            return encryptedData;
        } catch (Exception e) {
            System.err.println("DES算法,加密數據出錯!");
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 解密函數
     *
     * @param data 解密數據
     * @param key 密鑰
     * @return 返回解密後的數據
     */
    public static byte[] decrypt(byte[] data, byte[] key) {
        try {
            // DES算法要求有一個可信任的隨機數源
            SecureRandom sr = new SecureRandom();

            // byte rawKeyData[] = /* 用某種方法獲取原始密匙數據 */;
            // 從原始密匙數據創建一個DESKeySpec對象
            DESKeySpec dks = new DESKeySpec(key);

            // 創建一個密匙工廠,然後用它把DESKeySpec對象轉換成
            // 一個SecretKey對象
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretKey = keyFactory.generateSecret(dks);

            // using DES in ECB mode
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");

            // 用密匙初始化Cipher對象
            cipher.init(Cipher.DECRYPT_MODE, secretKey, sr);

            // 正式執行解密操作
            byte decryptedData[] = cipher.doFinal(data);

            return decryptedData;
        } catch (Exception e) {
            System.err.println("DES算法,解密出錯。");
            e.printStackTrace();
        }

        return null;
    }
}
import javax.xml.bind.*;

public class desTest {

public static void main(String[] args) {
    String key = "12345678"; //設定加密 key
    String plain = "test data";//加密內容明文

    byte[] key_byte = key.getBytes();
    byte[] plain_byte = plain.getBytes();
    //ECB mode 加密
    byte[] data_ecb = DES.encrypt(plain_byte, key_byte);
    //加密結果經BASE64編碼
    String base64String = DatatypeConverter.printBase64Binary(data_ecb);
    System.out.println("密文經過BASE64編碼:" + base64String);

    try {
        byte[] b = DatatypeConverter.parseBase64Binary(base64String);
        System.out.println("解密後明文:" + new String(DES.decrypt(b, key_byte)));
    } catch (Exception ex) {
        System.out.println(ex.getMessage());
        System.out.println(ex.toString());
    }
}

}
//輸出
密文經過BASE64編碼:l9TN9Ln/71IvFRjlhD8PaQ==
解密後明文:test data



參考、節錄:
在JAVA中使用DES算法 - 张兴业 - 博客园
java - how to avoid warning for the Base 64? - Stack Overflow
[ Java 文章收集 ] Java如何進行Base64的編碼(Encode)與解碼(Decode)?
DES加密模式详解(zz) - Wayne - BlogJava
java php DES 加密解密 - bask之家 -- 思则远 - ITeye技术网站
php des 加密解密实例 - clh604的专栏 - 博客频道 - CSDN.NET (AES加密範例)
使用php mcrypt加密解密
Encryption operating modes: ECB vs CBC
PHP通用DES加密算法
javaDESEncrypt.rar DESEncrypt.java


2016年9月18日 星期日

CentOS 7 自動校時(使用chrony)

CentOS 7 提供了新的 chrony 校時方式。
chronyd 和 ntpd 的不同之處,可查看redhat說明:
Red_Hat_Enterprise_Linux-System_Administrators_Guide-7::15.1.1. Differences Between ntpd and chronyd

以下為簡易用法:
安裝 chrony
# yum install chrony
開機自動啟動 chronyd
# systemctl enable chronyd.service
啟動 chronyd
# systemctl start chronyd.service
查看 chronyd 運行狀態
#systemctl status chronyd.service
...
 8月 16 00:02:36 localhost.localdomain chronyd[3193]: System clock wrong by 147228.887649 seconds, adjustment started
 8月 17 16:56:25 localhost.localdomain chronyd[3193]: System clock was stepped by 147228.887649 seconds
可發現,因為初次啟動時,時間差太多,所以先執行了一次校正到正確的時間。
slew adjustment:慢慢調整到正確的時間(避免其他用到時間的程式有異常、產生時間不合理的資料)
step adjustment:一次調整到正確的時間


查看 chronyc 校時來源server
# chronyc sources
查看 chronyc 校時來源server狀態
# chronyc sourcestats
查看最後一次取得的校時資料
# chronyc tracking
取得的資訊中,其中比較會用到的兩項資料是。
Ref time (UTC):最後一次從校時來源(Reference ID)得到的時間資料
System time:目前系統時間跟校時來源時間的差異。

如果發現系統目前時間(System time)跟校時來源時間差很多(Ref time), 不想等 chrony 慢慢校時,想立刻校時成跟 Ref time 一樣,可執行
# chronyc -a makestep

前一個指令,也可以 root 身分進入 chronyc 互動指令模式,再操作,「-a」參數:自動使用 local keys 認證
# chronyc -a
chrony version 2.1.1
Copyright (C) 1997-2003, 2007, 2009-2015 Richard P. Curnow and others
chrony comes with ABSOLUTELY NO WARRANTY.  This is free software, and
you are welcome to redistribute it under certain conditions.  See the
GNU General Public License version 2 for details.

200 OK
chronyc> help #使用 help 查看可用指令

參考:
Red_Hat_Enterprise_Linux-System_Administrators_Guide-7::15.3. Using chrony
漂亮的笨蛋:NTP文档翻译:它是如何工作的?How does it work?
chronyd 使用

Linux systemctl 指令用法

列出可管理的單元類型
# systemctl -t help
使用說明
# systemctl --help
列出所有已安裝的單元
# systemctl list-unit-files
列出已安裝「php*」開頭的單元
# systemctl list-unit-files php*
將 php-fpm.service 設為開機自動啟動
# systemctl enable php-fpm.service
將 php-fpm.service 設為開機不自動啟動
# systemctl disable php-fpm.service
查看 php-fpm.service 目前是否設為開機自動啟動
# systemctl is-enabled php-fpm.service
啟動 php-fpm.service
# systemctl active php-fpm.service
停止 php-fpm.service
# systemctl stop php-fpm.service
查看 php-fpm.service 目前是否為啟動
# systemctl is-active php-fpm.service
查看 php-fpm.service 狀態
# systemctl status php-fpm.service


Linux bash tab 自動補齊指令可用的選項參數

Linux 系統指令操作時,按「tab」鍵,可自動補齊或提示可用的指令。
若希望其他後面要接的參數,也可自動補齊或提示。
可安裝 bash-completion
# yum install bash-completion
安裝後從新登入,即可。
若不想重新登入,可重新跑一次環境變數設定檔 /etc/profile
# source /etc/profile


Win7 升級到 Win10 後,遇到的網路問題

Win7 升級到 Win10 後,除了網路常常斷線,VirtualBox裡網路裡的介面卡也都找不到了。
  • 網路常常斷線的問題:
    重啟網路卡(Broadcom Netlink(TM) Gigabit Ethernet)就會正常,但發生太頻繁了。
    最後解決方式是到 Broadcom 官網,下載網路卡官方驅動程式安裝。
    http://zh-tw.broadcom.com/support/ethernet-nic-netxtreme-i-desktop-mobile
    選 NetLink®/NetXtreme® I Desktop/Mobile/Server (x64) 下載後,解壓縮。

    再到裝置管理員,選該網卡後,用手動更新的方式,強制更新為下載的驅動程式。

    強制更新為下載的驅動程式方式:
    「更新驅動程式軟體」->「瀏覽電腦中的驅動程式軟體」->「讓我從電腦上的裝置驅動程式清單挑選」->「從磁片安裝」->此時選剛剛下載解壓縮後的驅動程式資料夾->確定後,即會出現下載的驅動程式可選擇,再按下一步進行安裝。

    這樣之後,就再也沒有遇到網路莫名其妙斷線的問題。

    註:在win10年度更新(版本1607)出來後,安裝完年度更新,win10又把網卡驅動改回系統原本的,所以只好再做一次上面的步驟。
  • VirtualBox 4.2.16 網路不能用的問題
    先移除 VirtualBox 4.2.16,再安裝新版的 VirtualBox VirtualBox 5.0.24,
    移除前,我怕虛擬機會無預警移掉,所以先備份到其他地方(不過最後,發現移除程式不會移除虛擬機)。
    原本虛擬機檔案位置「C:\Users\[登入帳號]\VirtualBox VMs\」

    新版 VirtualBox VirtualBox 5.0.24 安裝完,開啟後,原本的虛擬機都還在,重新選虛擬機網路裡的介面卡後儲存。

    虛擬機開機後,因虛擬機網卡有附加到 VirtualBox Host-Only Ethernet Adapter,結果發現一個錯誤訊息,導致無法開機。
    Failed to open/create the internal network 'HostInterfaceNetworking-VirtualBox Host-Only Ethernet Adapter #2' (VERR_INTNET_FLT_IF_NOT_FOUND).
    Failed to attach the network LUN (VERR_INTNET_FLT_IF_NOT_FOUND).
    
    結果碼: 
    E_FAIL (0x80004005)
    元件: 
    ConsoleWrap
    介面: 
    IConsole {872da645-4a9b-1727-bee2-5585105b9eed}
    


    解決方法:
    參考 virtualbox - Failed to open/create the internal network Vagrant on Windows10 - Stack Overflow 說明。
    到「控制台\網路和網際網路\網路連線」,打開「VirtualBox Host-Only Ethernet Adapter」網卡內容設定,勾選「VirtualBox NDIS6 Bridged Networking driver」後,重新啟動「VirtualBox Host-Only Ethernet Adapter」網卡。

    [其他相關]
    如果使用橋接介面卡,但之後電腦實體網路卡更換了,也會出現類似的錯誤訊息:
    Failed to open/create the internal network 'HostInterfaceNetworking-Broadcom NetLink (TM) Gigabit Ethernet' (VERR_INTNET_FLT_IF_NOT_FOUND).
    Failed to attach the network LUN (VERR_INTNET_FLT_IF_NOT_FOUND).
    
    
    結果碼: 
    E_FAIL (0x80004005)
    元件: 
    ConsoleWrap
    介面: 
    IConsole {872da645-4a9b-1727-bee2-5585105b9eed}
    
    此時,解決方式是將虛擬機的網路設定,將橋接介面卡重新設定為新的實體網路卡。


    虛擬機開機後,若附加到 「VirtualBox Host-Only Ethernet Adapter 的這張網卡用使用 DHCP 抓不到 IP。
    參考 Genymotion, Fix Error "Could Not Obtain An IP Address" - YouTube  說明
    到選單:「檔案」->「喜好設定」->「網路」。填入 VirtualBox Host-Only Ethernet Adapter 的 DHCP 設定。







    註:在win10年度更新(版本1607)出來後,安裝完年度更新,網路連線裡的「VirtualBox Host-Only Ethernet Adapter」不見了,所以有用到「VirtualBox Host-Only Ethernet Adapter」的虛擬機不能開機了。
    解決辦法:到「檔案」->「喜好設定」->「網路」新增一個新的「VirtualBox Host-Only Ethernet Adapter」

    新增後,再填入之前的網路設定值。

2016年8月12日 星期五

CentOS 7 忘記密碼

CentOS 7 忘記root密碼,重設密碼方式如下
  1. 在 grub 開機選單出現時,按「e」,編輯開機選項
    找到「linux16」開頭這一行,
    例如:
    ...(略)...
    linux16 /vmlinuz-3.10.0-327.22.2.e17.x86_64 root=/dev/mapper/centos-root ro ...(略)...
    ...(略)...
    
    這一行到「ro」這個單詞位置,將「ro」改成「rw init=/sysroot/bin/sh」
    ...(略)...
    linux16 /vmlinuz-3.10.0-327.22.2.e17.x86_64 root=/dev/mapper/centos-root rw init=/sysroot/bin/sh ...(略)...
    ...(略)
    
  2. 接下來按「Ctrl+x」開機進入命令列環境,
    這個命令列環境不是原本真正的系統,原本系統在 /sysroot,所以先 chroot 到 /sysroot
    # chroot /sysroot
    
    修改root密碼
    # passwd root
    
  3. 最後,在系統根目錄建一個 .autorelabel 檔案,
    開機時,系統偵測到根目錄有這個檔,便會 relabel 系統的 SELinux 相關設定,
    否則若受 SELinux 影響,還是會沒辦法登入。
    # touch /.autorelabel
    


參考:
How To Reset Your Forgotten Root Password On CentOS 7 Servers – Liberian Geek
v7 變更密碼
45.2.2. Relabeling a File System
centos - What does the .autorelabel file do in Linux? - Server Fault


其他資料:http://dywang.csie.cyut.edu.tw/dywang/rhel7/node5.html
  • CentOS 7 開機不分層級,而是分成 4 個 target
    Target 說明
    graphical.target 多人模式,支援圖形及文字兩種方式登入,等效 v6 的層級 3 及 5。
    multi-user.target 多人模式,只支援文字登入方式,等效 v6 的層級 3。
    rescue.target sulogin (Single-user login),單人模式,等效 v6 的層級 1。
    emergency.target sulogin ,單人模式,完成 initramfs 載入且系統根目錄 / 掛載成唯讀,等效 v6 開機掛載不成功時的維修模式。



rsync 執行時出現 remote command not found

使用 rsync,要將遠端資料,複製到本地端時出現以下錯誤
rsync: connection unexpectedly closed (0 bytes received so far) [Receiver]
rsync error: remote command not found (code 127) at io.c(226) [Receiver=3.1.2]
原因是,本地端和遠端都要裝 rsync,
如果只有下指令的本地端有裝 rsync,遠端沒裝 rsync,便會出現上列錯誤訊息。

此時在遠端也安裝 rsync 即可
#yum install rsync


參考:
解决bash: rsync: command not found-web集结号


2016年8月8日 星期一

PHP 出現 mod_fcgid: read timeout from pipe 錯誤

PHP 使用 mod_fcgid 執行,畫面出現 Internal Server Error,
error log 錯誤訊息如下:
[warn] [client 127.0.0.1] mod_fcgid: read timeout from pipe
[error] [client 127.0.0.1] Premature end of script headers: index.php
可知是 PHP 執行時間超過系統設定。

若 PHP 執行時間是預期中的,則可將 mod_fcgid 的Timeout 時間設長一點
<IfModule mod_fcgid.c>
    .......
    #FcgidIOTimeout                  120
    #FcgidIdleTimeout                120
    #將時間改長
    FcgidIOTimeout                  1200
    FcgidIdleTimeout                1200
    ......
</IfModule>


參考:
php - fread timeout with 'mod_fcgid: read timeout from pipe' - Stack Overflow

 

PHP 將物件傳到 function 時,有用 & 和沒用 & 的差異

PHP 在 function 間傳遞物件時,沒用 & (pass by identifier)和 有用 & (pass by reference),兩者用起來雖然差不多,但嘗試將負責傳遞的物件變數,整個重新指定為其他東西時,會有不同的結果。

[ 沒用 & (使用識別符號傳遞) ]
[ pass by identifier(a reference to a reference) ]
class test{
    public $v = 123;
}

function change1($a){
    $a->v = 999;
}
function change2($a){
    $a = 222; //不能改變 $a = new test() 這個物件
}

$a = new test();
var_dump($a);

change1($a);
var_dump($a);

change2($a);
var_dump($a);
結果:
object(test)#1 (1) {
  ["v"]=>
  int(123)
}
object(test)#1 (1) {
  ["v"]=>
  int(999)
}
object(test)#1 (1) { //不能改變 $a = new test() 這個物件
  ["v"]=>
  int(999)
}



[ 用 & (使用參考傳遞) ]
[ pass by reference ]
class test{
    public $v = 123;
}

function change1(&$a){
    $a->v = 999;
}
function change2(&$a){ //可以改變 $a = new test() 這個物件
    $a = 222;
}

$a = new test();
var_dump($a);

change1($a);
var_dump($a);

change2($a);
var_dump($a);
結果:
object(test)#1 (1) {
  ["v"]=>
  int(123)
}
object(test)#1 (1) {
  ["v"]=>
  int(999)
}
int(222)  //可以改變 $a = new test() 這個物件


參考:
PHP: Passing by Reference - Manual
PHP: Objects and references - Manual
variables - How do you pass objects by reference in PHP 5? - Stack Overflow

2016年7月31日 星期日

PHP ini_set 設定 error_log 後無效

PHP 的 error_log 設定值,可修改範圍是 PHP_INI_ALL,所以可以使用 ini_set() 設定,
若設定後無效,可查看 php 設定檔,
是不是已經使用 php_admin_value 設定了 error_log 的值,
因為在設定檔中使用 php_admin_value、php_admin_flag 的方式指定設定值,
之後再使用 .htaccess 或 ini_set() ,也不能覆蓋 php_admin_value、php_admin_flag 設定的值。
php_admin_value、php_admin_flag 的效力比 .htaccess 、 ini_set() 大。

參考:
PHP: 运行时配置 - Manual
PHP: 配置可被设定范围 - Manual
PHP: How to change configuration settings - Manual


PHP FPM 錯誤記錄不會寫到 error log ?

PHP FPM 預設會將  worker 的 stdout 和 stderr 丟到 /dev/null,
而不會寫到 error log (/var/log/php-fpm/error.log)
所以執行時,像 PHP 的語法錯誤,在 error log 便看不到。

若開發時,要在 error log 觀察這類錯誤,可在設定檔中將 catch_workers_output 設為 yes
$ vi /etc/php-fpm.d/www.conf
找到 catch_workers_output 設定,改為 yes
; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Default Value: no
catch_workers_output = yes
重新讀取設定
$  systemctl reload php-fpm.service


參考:
nginx php-fpm 输出php错误日志  –  架构 - nginx.cn
PHP-FPM doesn't write to error log - Stack Overflow

2016年7月9日 星期六

Git push 上去後,再修改 commit 說明訊息

Git commit 後,才剛 push 到遠端,且還沒有其他人下載或更動過。
可用以下方式修改提交的訊息。

修改最近一次提交的訊息
$ git commit --amend
跳出修改畫面,修改後儲存(:wq)


使用 --force-with-lease 參數重新 push
$ git push --force-with-lease <遠端儲存庫別名> <分支名稱>
例如
$ git push --force-with-lease origin master
使用 --force-with-lease,而不用 --force 的原因
是 --force-with-lease 可避免執行時,若有其他人在對遠端儲存庫存取,對資料造成損毀。
(但舊的版本似乎沒 --force-with-lease)


如果已有其他人抓取了未修改前的資料,
那其他人則必須,抓修改後的資料後,然後 reset hard 讓本地端變成修改後的資料,
但此時本地端若有修改,將會不見。
git fetch origin
git reset --hard origin/master # 本地端的修改會不見



參考:
Changing git commit message after push (given that no one pulled from remote) - Stack Overflow
Git - git-push Documentation

HTML form 只有一個 text input 時,在 input 上按 enter 會自動送出表單

HTML 的 form 裡面有 submit 按鈕時,則在裡面其他表單元素按 enter 時,
此時會觸發 submit 按鈕的 onclick 事件、form 的 onsubmit 事件,送出表單。
 (註:若是用 ...form.submit() 方法送出表單,則不會觸發 form 的 onsubmit 事件)
<form method="post" action="test.php" onsubmit="alert('onsubmit');">
AA:<input type="text" name="aa">
BB:<input type="text" name="bb">
<input type="radio" name="cc">CC
<input type="submit" value="送出" onclick="alert('onclick');">
</form>


上例,若是將 type="submit" 按鈕移除,或是換成一般按鈕 type="button",則在表單元素按 enter 也無法送出表單。
但是當 form 裡面的 text input 只有一個時,在 text input 上按 enter 時,也會觸發 form 的 onsubmit 事件,自動送出表單
<form method="post" action="test.php">
AA:<input type="text" name="aa">
<input type="radio" name="cc">CC
</form>


解決方法1:藏一個隱藏的text input (但若form裡面還有個submit按鈕,此方法就不管用了)
<form method="post" action="test.php">
AA:<input type="text" name="aa">
<input type="text" style="display: none">
<input type="radio" name="cc">CC
</form>


解決方法2:在 text input enter 時 return false
<form method="post" action="test.php">
AA:<input type="text" name="aa" onkeypress="if (event.keyCode == 13) {return false;}">
<input type="radio" name="cc">CC
</form>



參考:
javascript - Why do some forms auto-submit on clicking the enter button and others do not? - Stack Overflow
javascript - Why does forms with single input field submit upon pressing enter key in input - Stack Overflow
HTML DOM submit() 方法

2016年6月4日 星期六

Nginx if、and、網域名稱、GET 變數判斷

Nginx 取得網域名稱:$host (例如:example.com)
Nginx 取得 GET 變數:$arg_變數名稱 (例如:$arg_aa,表示 aa 這個 GET 變數)

Nginx 比較運算子(=、!=),前後要空格
不然會出現 unknown "host123" variable 或 invalid condition "$host!=" 錯誤。
if 跟前小刮號 ( 也要空格,不然會出現 unknown directive "if($host" 錯誤。
例如:
if ($host = '123'){
}
if ($host != '123'){
}


GET 變數判斷
#表示GET有一個aa變數值等於 abc
if ($arg_aa = 'abc'){
}
#表示一整串的GET數值,有任一處出現 abc(不一定是值)
#例如 aa=123&aabcc=456 (aabcc 中間有出現 abc)
if ($args ~ 'abc'){
}
#表示一整串的GET數值,沒有任一處出現 abc
if ($args !~ 'abc'){
}


Nginx 沒 AND,所以要另其他方式達成,
例如用兩個 if 達到 AND 效果:
set $isOK 0;
if ($host = 'example.com'){
    set $isOK 1;
}
if ($arg_aa != 'abc'){
    set $isOK "${isGoOut}1";
}
if ($isOK = 11){
    return 301 http://xx.example.org;
}



參考:
Nginx unknown directive "if($domain" - Stack Overflow
NGINX的奇淫技巧 —— 2. IF AND 和 OR - A.R.G.U.S. Sec Team - SegmentFault
nginx hack for multiple conditions · GitHub
Making nginx check parameter in url - Stack Overflow
nginx built-in variables (nginx 内置的变量) - 申思维的技术站点
Module ngx_http_rewrite_module


Linux 記憶體使用量 used、free、buffers、cached

用 top 指令查看記憶體使用狀況時,
有時會發現 used 使用的越來越多, free 的記憶體越來越來少,但系統服務似乎沒變慢,
反而是重開機後,free 的記憶體變多了,系統服務卻變慢了,
再跑一段時間之後,free 的記憶體變少,系統服務又變快了。
這種情況,可以再看 buffers、cached 的量,
可能是記憶體被 buffers、cached 使用掉,
但在 buffers、cached 的記憶體,只是系統為了增加性能而使用,
若有應用程式須要時,系統便會讓出來,所以才會有使用一段時間,反而變快的感覺。
top - 21:03:07 up 172 days, 19:36,  1 user,  load average: 0.01, 0.04, 0.00
Tasks: 417 total,   1 running, 416 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  65925940k total, 65635248k used,   290692k free,   292772k buffers
Swap: 33030140k total,     7848k used, 33022292k free, 55499140k cached


用 free 指令查看記憶體使用狀況時,
可發現有三行資料
第1行:Mem
第2行:-/+ buffers/cache
第3行:Swap
             total       used       free     shared    buffers     cached
Mem:      65925940   65635092     290848          0     292772   55499140
-/+ buffers/cache:    9843180   56082760
Swap:     33030140       7848   33022292
第1行資料,在 top 指令中也有,
在第2行資料,可發現
第2行(free)(56082760) = 第1行(free)(290848) + 第1行(buffers)(292772) + 第1行(cached)(55499140)
可見第2行(-/+ buffers/cache),所指的 free 也包含 buffers/cache,也就是系統還可再利用的記憶體大小。


參考:
Linux 的記憶體快取(Cache Memory)功能:Linux 系統把記憶體用光了? - G. T. Wang
每天一个linux命令(45):free 命令 - peida - 博客园
linux - Meaning of the buffers/cache line in the output of free - Server Fault


2016年5月19日 星期四

Git mv 改檔名或移動檔案後,查詢之前的歷史記錄

例如:
git mv test.php mydir/test.php
git commit
git log mydir/test.php
這樣會看不到以前 test.php 以前修改的歷程記錄,

此時可加 --follow 參數,就會一併列出以前的歷史記錄。
git log --follow mydir/test.php



參考:
Is it possible to move/rename files in git and maintain their history? - Stack Overflow


PHP curl、curl 命令列,登入 HTTP basic authentication (HTTP Basic Auth)

[PHP curl]
使用 curl_setopt() 的 CURLOPT_USERPWD 傳輸帳密進行認證。
$ch = curl_init();
.....
curl_setopt($ch, CURLOPT_USERPWD, "userA:pwtest"); // 格式=>"帳號:密碼"
.....
$data = curl_exec($ch);
curl_close($ch);

[curl 命令列]
#參數=> -u "帳號:密碼"
curl -s -S -G --data "aa=123&bb=456" -u userA:pwtest http://example.com


參考:
rest - How do I make a request using HTTP basic authentication with PHP curl? - Stack Overflow
PHP: curl_setopt - Manual


Nginx 使用 Basic Auth (HTTP Basic Authentication)

  1. 先使用 htpasswd 建立帳號密碼檔
    (下例建立帳號名稱為 userA)
    $ htpasswd -c /home/.htpasswd userA
    
  2. 修改 nginx 網站設定檔
    location / {
        #auth_basic 訊息字串 或 off
        auth_basic "Basic Auth";
        auth_basic_user_file "/home/.htpasswd";
    }
    
  3. 重新讀取設定檔
    $ systemctl reload nginx.service



參考:
CentOS 7 : Nginx : Enable Basic Auth : Server World
Module ngx_http_auth_basic_module
nginx配置location总结及rewrite规则写法 | Sean's Notes


其他:
不能直接使用 if 判斷來決定要不要執行 auth_basic "Basic Auth" 這一行。
NGINX - auth basic if $arg contains 'admin' - Stack Overflow
drupal - Nginx config: how to use auth_basic authentication if ssl_client_certificate none provided? - Stack Overflow

2016年5月9日 星期一

JavaScript 控制 Audio 物件停止播放

HTML5 audio 物件,沒有 stop() 停止播放的方法。
折衷的解決方式,可先暫停播放,再將目前播放位置 currentTime 設回起點。
效果便跟停止播放一樣。

範例:
<script type="text/javascript">
var snd = new Audio("bell.wav");
snd.loop = true; //設定循環播放

//停止
function myStop(){
    snd.pause();
    snd.currentTime = 0;
}
</script>
<input type="button" value="播放" onclick="snd.play()">
<br>
<input type="button" value="暫停" onclick="snd.pause()">
<br>
<input type="button" value="停止" onclick="myStop()">


參考:
HTML5 Audio stop function - Stack Overflow
<audio> - HTML | MDN.
使用 JavaScript 控制 Audio 物件 (Windows)
audio element | audio object (Internet Explorer)


補充:

2016年5月3日 星期二

PHP 判斷是否在 CLI (Command Line) 命令列模式下執行

function is_cli() {
    if (php_sapi_name() === 'cli') {
        return true;
    }
    if (defined("STDIN")) {
        return true;
    }
    if (!isset($_SERVER["REMOTE_ADDR"]) || empty($_SERVER["REMOTE_ADDR"])) {
        return true;
    }
    return false;
}



參考:
PHP: I/O streams - Manual
PHP: php_sapi_name - Manual
PHP CLI Tutorial
http://stackoverflow.com/a/25967493
老師說:學術研究的路是孤單的: PHP的STDIN(標準輸入)、STDOUT(標準輸出)及STDERR(標準錯誤輸出)

PHP empty() 出現 Fatal error: Can't use function return value in write context 錯誤訊息


因為 PHP 5.4 以前,empty() 只能處理變數。
若直接在  empty() 放函式,會出現 Fatal error: Can't use function return value in write context 錯誤
function aa(){
   return 123;
}

empty(aa()); // Fatal error: Can't use function return value in write context 

$a = aa();
empty($a); // false 


官網說明:
http://php.net/manual/en/function.empty.php
Prior to PHP 5.5, empty() only supports variables; anything else will result in a parse error. In other words, the following will not work: empty(trim($name)). Instead, use trim($name) == false.


PHP class Property 和 method 名稱相同

PHP 的 class,屬性和方法名稱可以相同。
至於使用時,是存取屬性或調用方法,則視寫法區別,例如 $obj->bar 或 $obj->bar()

官網說明:
Class properties and methods live in separate "namespaces", so it is possible to have a property and a method with the same name. Reffering to both a property and a method has the same notation, and whether a property will be accessed or a method will be called, solely depends on the context, i.e. whether the usage is a variable access or a function call.


參考:
php - member variable and member function have the same name - Stack Overflow
PHP: The Basics - Manual (Classes and Objects)


2016年3月24日 星期四

電腦自動喚醒

有時候電腦睡眠後,不明原因自動被喚醒開機。
此時可由「控制台」->「事件檢視器」->「系統」,查到相關資訊。
或如以下範例,從指令查的上次從睡眠喚醒的資料(下例可知是經由網卡喚醒)
C:\Windows\system32>powercfg /lastwake
 - 1
 [0]
   - 1
   [0]
    :
    : 
    :
    : Broadcom NetLink (TM) Gigabit Ethernet
    : Broadcom
(如果不想此裝置喚醒電腦,可到「裝置管理員」,在該項設備的「電源設定」裡面即可設定。)


列出目前設定為將系統從任何睡眠狀態喚醒的裝置
C:\Windows\system32>powercfg /devicequery wake_armed
Broadcom 802.11n Broadcom NetLink (TM) Gigabit Ethernet


查詢系統可用的睡眠種類
C:\Windows\system32>powercfg /availablesleepstates
此系統有以下幾種睡眠狀態: 待命 ( S1 S3 ) 休眠 混合式睡眠
此系統缺乏以下幾種睡眠狀態:
待命 (S2)
        系統韌體不支援此待命狀態。


使用中的喚醒計時器
C:\Windows\system32>powercfg /waketimers


參考:
Powercfg 命令列選項


Git 流程

名詞:
Working Directory(Working Tree、workspace)
Index(Staging Area、Staged files、Directory cache)
local repository
remote repository

流程:
<-------------------------- client -----------------------------> <--- git server -->
 Working Directory  |  Index(Staging Area)  |  local repository  |  remote repository
         (add)--------------->
                           (commit)---------------->
                                                (push)------------------->
                                                
            <---------------------------------(merge) <-------(fetch)-----
                   merge origin/master into master     update origin/master
                         
            <------------------------------------------------(pull)------
            
            <-----------(checkout revision)---------




2016年3月16日 星期三

Linux 查詢硬碟 S.M.A.R.T. 健康狀態

查硬碟資訊
$ smartctl -i /dev/sda
注意訊息最後面,硬碟是否有支援 SMART,且是否有開啟。
.....
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

若硬碟是否有支援 SMART,但沒有開啟。
.....
SMART support is: Available - device has SMART capability.
SMART support is: Disabled
可使用以下指令開啟
$ smartctl -s on /dev/sda

查看硬碟健康狀態
$ smartctl -H /dev/sda

查看硬碟健康狀態(列出細項)
$ smartctl -A /dev/sda

讓硬碟進行一次自我檢測(此指令下完會在背景執行)
$ smartctl -t short /dev/sda
想知道自我檢測執行進度,可查看硬碟完整資訊
$ smartctl -a /dev/sda
.....
SMART Self-test log structure revision number 1
Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
# 1  Short offline       Self-test routine in progress 80%      3568         -
# 2  Short offline       Completed without error       00%      3568         -
這一段,是歷史檢測結果(新的在上面),上例表示,有過兩次檢測, 前一次檢測結果正常(Completed without error), 目前這一次尚在檢測,進度還剩 80%


參考:
[Linux] smartctl 使用smartmontools監控硬碟的健康狀態 | 70's Blog
鳥哥的 Linux 私房菜 -- 第二十章、基礎系統設定與備份策略



PHP 讀取 Access mdb 檔

PHP 可透過 ODBC 存取 Access 資料庫檔。

環境 ODBC for Access MDB 設定:
1. Windows
在 windows 上,在 php.ini 開啟 php_pdo_odbc.dll 擴展
extension=php_pdo_odbc.dll

2. Linux
在 Linux 上,則要另外安裝 ODBC 和 mdbtools
[ mdbtools 說明 ]
mdbtools 是目前 Linux 上操作 Access MDB 免費方案的解決方法,
但我測試時的版本,只能執行簡單的SQL語法,一些 SQL 語法在 windows 上正常,
但在 linux 透過 mdbtools 都無法執行(PHP PDO 執行後 false,直接使用指令透過 ODBC 操作,則出現 Couldn't parse SQL 的錯誤)。

安裝 php-odbc
$ yum install php-odbc
安裝 MDBTools
$ yum install mdbtools
安裝 mdbtools-devel ,安裝這個才有 libmdbodbc.so
$ yum install mdbtools-devel

查看 /etc/odbcinst.ini 內容,看一下設定的格式
$ less /etc/odbcinst.ini
# Example driver definitions
# Driver from the postgresql-odbc package
# Setup from the unixODBC package
[PostgreSQL]
Description     = ODBC for PostgreSQL
Driver          = /usr/lib/psqlodbcw.so
Setup           = /usr/lib/libodbcpsqlS.so
Driver64        = /usr/lib64/psqlodbcw.so
Setup64         = /usr/lib64/libodbcpsqlS.so
FileUsage       = 1

# Driver from the mysql-connector-odbc package
# Setup from the unixODBC package
[MySQL]
Description     = ODBC for MySQL
Driver          = /usr/lib/libmyodbc5.so
Setup           = /usr/lib/libodbcmyS.so
Driver64        = /usr/lib64/libmyodbc5.so
Setup64         = /usr/lib64/libodbcmyS.so
FileUsage       = 1
可知要有 ODBC for MDB 的話, lib、lib64 目錄,應該會有 libmdbodbc.so 之類的檔案
$ ls -al /usr/lib/libmdb*
ls: 無法存取 /usr/lib/libmdb*: 沒有此一檔案或目錄
$ ls -al /usr/lib64/libmdb*
-rwxr-xr-x 1 root root 38240  2月 20  2015 /usr/lib64/libmdbodbc.so
-rwxr-xr-x 1 root root 42536  2月 20  2015 /usr/lib64/libmdbodbcW.so
lrwxrwxrwx 1 root root    15  2月  5 17:14 /usr/lib64/libmdb.so -> libmdb.so.2.0.1
lrwxrwxrwx 1 root root    15  2月  5 16:35 /usr/lib64/libmdb.so.2 -> libmdb.so.2.0.1
-rwxr-xr-x 1 root root 90104  2月 20  2015 /usr/lib64/libmdb.so.2.0.1
lrwxrwxrwx 1 root root    18  2月  5 17:14 /usr/lib64/libmdbsql.so -> libmdbsql.so.2.0.0
lrwxrwxrwx 1 root root    18  2月  5 16:35 /usr/lib64/libmdbsql.so.2 -> libmdbsql.so.2.0.0
-rwxr-xr-x 1 root root 41144  2月 20  2015 /usr/lib64/libmdbsql.so.2.0.0
可發現 usr/lib64/ 底下有 libmdbodbc.so 的檔案link
在 odbcinst.ini 裡面,多加一組 MDBTools MDB ODBC Driver(名稱可自訂) 設定
$ vi /etc/odbcinst.ini
[MyMDBDriver]
Description     = MDBTools MDB ODBC Driver
Driver          = /usr/lib/libmdbodbc.so
Setup           = /usr/lib/libmdbodbc.so
Driver64        = /usr/lib64/libmdbodbc.so
Setup64         = /usr/lib64/libmdbodbc.so
FileUsage       = 1
UsageCount  = 1


PHP 程式範例:
$uname = explode(" ", php_uname());
$os = $uname[0];
switch ($os) {
    case 'Windows':
        $mdb_file = 'D:\test.mdb';
        $driver = '{Microsoft Access Driver (*.mdb)}';
        break;
    case 'Linux':
        $mdb_file = '/home/test.mdb';
        $driver = 'MyMDBDriver'; //在 /etc/odbcinst.ini 裡的設定名稱
        break;
    default:
        exit("Don't know about this OS");
}
$dataSourceName = "odbc:Driver=$driver;DBQ=$mdb_file;";
$connection = new PDO($dataSourceName);
$sql = "SELECT * FROM test";
$stmt = $connection->query($sql);
$data = $stmt->fetchAll(\PDO::FETCH_ASSOC);


參考:
Access a mdb database from a php web site
How to handle MS Access MDB files in Linux with PHP5 PDO and ODBC · GitHub
PHP: PDO_ODBC DSN - Manual



其他:
1. 若在 /etc/odbc.ini 增加以下設定
$ vi /etc/odbc.ini
[MDBtest]
Description = The Source of My test mdb
Driver      = MyMDBDriver
ServerName  = localhost
Database    = /home/test.mdb
則可這樣進入 SQL 指令介面,進入後,可直接用 SQL 指令操作 /home/test.mdb 資料庫
$ isql -v MDBtest
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL>

2. 編譯安裝 mdbtools:
GitHub - brianb/mdbtools: MDB Tools - Read Access databases on *nix
Install mdbtools on Centos 7 – Dale Scott
tar zxvf mdbtools-0.6pre1.tar.gz
cd mdbtools-0.6pre1/
autoreconf -i -f
./configure --with-unixodbc=/usr/local
make
make install

3. Access mdb 時間欄位:
SQL語法中條件值中有日期時間格式的值﹐需要在日期時間值的左右加上#
$sql = "SELECT * FROM test WHERE aa >#{2016-03-15}#";
參考:請問mdf與mdb的SQL語法有何差別? - Delphi K.Top 討論區


iframe 執行父視窗裡面的 JavaScript

  1. 先將父視窗裡要被 iframe 執行的 JS 放在一個 function
    var aa = function(){
        alert('test');
    };
    
  2. 然後在 iframe 裡面呼叫父視窗的 function 時,即可執行放在父視窗 function 內的程式碼
    parent.aa();
    


2016年3月3日 星期四

CentOS 7 安裝遠端桌面

在 Linux 若須要遠端操作圖形視窗介面時,可以安裝 xrdp 和 VNC server,
安裝完,用 windows 內建的遠端桌面即可連線操作。

若沒桌面環境,先安裝桌面環境
$ yum groupinstall "GNOME Desktop Environment"

安裝 epel REPO
$ rpm -Uvh http://download.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

安裝 xrdp 和 VNC server
$ yum install xrdp tigervnc-server

設定開機自動啟動 xrdp
$ systemctl enable xrdp.service

啟動 xrdp
$ systemctl start xrdp.service

查看 xrdp 使用的 port
$ netstat -antup | grep xrdp
或
$ ss -nlp | grep xrdp
(port預設應該是3389)

若須修改 port,可編輯設定檔
$ vi /etc/xrdp/xrdp.ini

查看防火牆是否有開啟
$ firewall-cmd --state

查看防火牆開放的 port
$ firewall-cmd --list-all --zone=public

開放 3389 port
$ firewall-cmd --permanent --zone=public --add-port=3389/tcp
$ firewall-cmd --reload


其他問題:


參考:
CentOS Linux 7 xRDP (Linux RDP遠端桌面)
Linux 上的遠端桌面中繼程式: xrdp (v0.4.2)


CentOS 7 掛載 windows 網路芳鄰資料夾(CIFS)

Linux CentOS 掛載 windows 分享的資料夾。
先安裝相關套件
$ yum install samba-client samba-common cifs-utils

首先在 windows 開啟一個共用資料夾,並設帳號、密碼。

方法一:寫在 fstab 檔案,開機時掛載
$ mkdir /mnt/win
$ vi /etc/fstab
//192.168.56.1/testdir /mnt/win cifs rw,suid,dir_mode=0777,file_mode=0666,username=帳號,password=密碼 0 0
若想自己利用 mount /mnt/win 這個指令來手動掛載它時,可加入 noauto 這個選項


方法二:autofs偵測有用到時,自動掛載(推薦用此方法) 
若系統沒 autofs,先安裝 autofs
yum install autofs

接下來,預計將資料掛載在 /automnt/win 底下。
先新增一個設定檔,
例如 /etc/auto.mymnt,裡面寫上掛載內容
$ vi /etc/auto.mymnt
win -fstype=cifs,rw,suid,dir_mode=0777,file_mode=0666,username=帳號,password=密碼 ://192.168.56.1/testdir

auto.master加上一行設定(/automnt 資料夾 autofs 會自動建立)
$ vi /etc/auto.master
/automnt /etc/auto.mymnt

設定開機自動啟動
$ systemctl enable autofs.service

重新啟動 autofs
$ systemctl restart autofs

當嘗試存取自動掛載目錄,autofs 就會自動掛載,例如進入目錄
$ cd /automnt/win


參考:
鳥哥的 Linux 私房菜:自動掛載 autofs 的使用
CentOS Wiki:如何掛載遠端 Windows 共享資源

2016年1月18日 星期一

MySQL GROUP_CONCAT()

在兩個關聯資料表 ,一對多對應的情況使用 join 時,常會用 GROUP BY 取得不重覆的資料,此時若想取得重覆列某個欄位所有的值,可使用 GROUP_CONCAT() 函式

例如:
bb 資料表,商品列表
b_idb_name
1綠茶
2麵包

uu 資料表,訂購資料
u_idb_id
12
11
32
41
51


要一次列出個別商品訂購的 u_id,可使用 GROUP BY 配合 GROUP_CONCAT()
SELECT bb.* , GROUP_CONCAT( uu.u_id ) 
FROM bb
LEFT JOIN uu ON ( uu.b_id = bb.b_id ) 
GROUP BY bb.b_id
結果:
b_idb_nameGROUP_CONCAT( uu.u_id )
1綠茶1,4,5
2麵包1,3
GROUP_CONCAT( uu.u_id ) 取得的值或需要再篩選,也可以再使用 HAVING 配合 FIND_IN_SET 過濾資料。


使用 PHP 網路開機

在區域網路中,可以傳送 Magic Packet 給主機的網路卡,Magic Packet 內容開頭有6個 "FF",再接 16 個網路卡 MAC,當主機版有開啟支援 Wake on LAN 功能,網路卡接收到符合條件的 Magic Packet,就能開啟電腦。(每個主機版關於 Wake on LAN 的名稱不盡相同,例如 PME Event Wake Up、Power On By PCI Devices...)
另一個影響能否網路開機的設定,在作業系統裡面裝置管理員,網卡裝置內容裡的電源管理,需勾選「允許這個裝置喚醒電腦」。

假設網路卡 MAC 位址為:01-02-03-04-05-06
Magic Packet 內容則為:
FFFFFFFFFFFF010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506010203040506
然後以 UDP 通訊協定傳送給電腦的 port 7、或 port 9

所以實現網路開機的方式,就是在區網內想辦法傳送給目標電腦網卡 Magic Packet,如果用來傳送 Magic Packet 的設備(Server),平常也有對外連線,也可達成在外網開機的效果。

以下 PHP 傳送 Magic Packet 範例來源
WOL: Wake-on-LAN Tutorial with Bonus PHP Script
僅稍微修改後如下:
class WakeOnLan {

    /**
     * 喚醒電腦
     * @param type $addr ,目標IP 或 廣播位址(格式 01-02-03-04-05-06 或 01:02:03:04:05:06)
     * @param type $mac ,MAC 位址
     * @param type $port , 7 or 9
     * @return boolean
     */
    public function wake($addr, $mac, $port) {
        $mac = str_replace("-", ":", $mac);
        $addr_byte = explode(':', $mac);
        $hw_addr = '';
        for ($a = 0; $a < 6; $a++) {
            $hw_addr .= chr(hexdec($addr_byte[$a]));
        }
        // 開頭六個 "FF"
        $msg = chr(255) . chr(255) . chr(255) . chr(255) . chr(255) . chr(255);
        // 16個MAC
        for ($a = 1; $a <= 16; $a++) {
            $msg .= $hw_addr;
        }
        // 開一個 UDP 的 socket
        // AF_INET:IP4
        // SOCK_DGRAM:The UDP protocol is based on this socket type
        // SOL_UDP:使用 UDP 通訊協定
        $skt = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        $err = array();
        if ($skt === false) {
            $err[] = "Error creating socket!";
            $err[] = "Error code is '" . socket_last_error($skt) . "' - " . socket_strerror(socket_last_error($skt));
            throw new Exception(implode("\n", $err));
        } else {
            // 設定使用broadcast廣播訊息
            // $opt_ret = socket_set_option($skt, 1, 6, TRUE);
            $opt_ret = socket_set_option($skt, SOL_SOCKET, SO_BROADCAST, true);
            if ($opt_ret === false) {
                $err[] = "setsockopt() failed, error: " . socket_strerror(socket_last_error($skt));
                throw new Exception(implode("\n", $err));
            }

            if (socket_sendto($skt, $msg, strlen($msg), 0, $addr, $port)) {
                //Magic Packet sent successfully
                $res = trim(socket_strerror(socket_last_error($skt)));
                socket_close($skt);
                return $res;
            } else {
                $err[] = "Magic packet failed!";
                throw new Exception(implode("\n", $err));
            }
        }
    }

}

$WOL = new WakeOnLan();
try {
    // 使用 port 7
    $res = $WOL->wake('192.168.0.255', "01-02-03-04-05-06", 7);
    // 也可使用 port 9
    //$res = $WOL->wake('192.168.0.255', "01-02-03-04-05-06", 9);
    var_dump($res);
} catch (Exception $ex) {
    var_dump($ex->getMessage());
}

  • 將 Magic Packet 指定傳給 broadcast address(例如:192.168.0.255),可傳送給區網所有電腦,MAC位址符合的才會開機。
  • 原範例 socket_set_option($skt, 1, 6, TRUE),1、6 改成用常數取代,因不同環境 SOL_SOCKET、SO_BROADCAST 的常數直似乎不同。
    查看所有 soket 常數 http://php.net/manual/en/sockets.constants.php#108291
    $a = get_defined_constants(TRUE);
    foreach ($a['sockets'] as $constant => $value) {
        printf("%-25s %d
    ", $constant, $value);
    }

網路喚醒 - 維基百科,自由的百科全書
設定Windows 8網路喚醒(Wake On LAN) - 黑暗執行緒
PHP: socket_create - Manual
PHP: socket_set_option - Manual
PHP: socket_get_option - Manual
arduino - PHP : Send an UDP broadcast message , and wait the response - Stack Overflow
小信豬的原始部落: [TCP/IP Illustrated] Broadcasting & Multicasting

2016年1月7日 星期四

Git 修改檔名大小寫

環境:win7 + NetBeans 8.0.2

更改檔名大小寫後,直接在 NetBeans 裡 commit,會因為大小寫不分,沒辦法達到預期效果。

解決方法:
改用指令的方式操作,例如 abc.php 改成 Abc.php
$ git mv -f abc.php Abc.php
使用 git mv 指令修改檔名,不管 core.ignorecase 設定值如何。
commit 後,原本檔案的歷史修改 log 都還會在。
加上 -f 的原因是,在 windows 系統不分大小寫,不加的話會出現目地檔案已存在。

接下來 commit 時也要在指令下執行。在 NetBeans 裡也無法正確執行。
$ git commit


其他注意事項:
如果不經由 git mv 指令修改檔名,而是直接對檔案修改,
在 core.ignorecase 設為大小寫敏感的環境下,可偵測到檔案異動了,
但效果是新增了一個檔案,因此該檔案的歷史記錄會不見。
$ git config core.ignorecase false


參考:
How do I commit case-sensitive only filename changes in Git? - Stack Overflow