2019年3月26日 星期二

c++ 善用 smart pointer 在物件建構法中傳回不同類型的物件並統一函式介面的作法

每個物件通常有不同的函式介面, 想要將兩種不同物件的方法統一成相同介面, 可以利用物件建構法將它封裝起來, 用這新類型物件去實例化想要的物件, 達成傳回多型物件(polymorphic class)的目的:
#include  <stdio.h>
struct base    { // 基礎類型

  virtual void work( ){ }//擬統一的函式介面

  virtual  ~base(){ } // 避免繼承者,當在解構式中想刪除指標物件時,編譯器會報出錯務訊息

}; // 先建構基礎物件抽象類型, 統一函式介面


struct A: base { // 繼承 base 物件, 將物件用物件 A 封裝起來
    void work( ) override { printf("A\n");    } // 將方法封裝並實現函式介面
};


struct B: base {// 繼承 base 物件, 將另一物件用物件 B 封裝起來
    void work( ) override { printf("B\n");    } // 同樣將方法封裝並實現函式
};


struct C {     // 一個全新多型類型(polymorphic class)
    base *ptr;// 基礎物件的指標, 可以封裝物件 A 或 B
    ~C( )      { delete ptr; }
    C(int a) {  // 根據輸入值實例化不同物件, 奇數傳回 A, 偶數傳回 B, 等同於傳回不同物件
        if (a%2) ptr = (base *) new A();  // 實例化物件 A 指標
        else       ptr = (base *) new B();  // 實例化物件 B 指標
    }
    void work( ){ ptr->work( ); }// 將多型物件成員用指標連結函式介面,方便使用
};
int main(){
   for(int i=0; i < 10; i++)         C(i).work( );
}
如果害怕指標的用法(像是造成 memory leak or double free), 可以用 smart pointer 將它封裝起來, 讓系統自動處理, 就不用擔心記憶體釋放的問題. c++ 所提供的 std::shared_ptr < > 就是 smart pointer 其中之一用來封裝類型指標的標準樣板函式(STL), 但有一點要注意的是, 用 smart pointer 封裝類型時, 該類型的建構及解構方法必須放在 public: 區, 讓 STL 可以正常呼叫, 否則會出現一堆錯誤訊息. 又或者很不喜歡用指標箭頭的方法, 還可以用左值(&alias) 換化成正常物件, 將方法從指標箭頭轉成正常物件的使用方式.
#include  <stdio.h>
#include  <memory> // smart pointer 要使用 memory 函式庫
typedef std::shared_ptr <base> baseptr; // 封裝成 smart pointer, 讓系統自動管理
struct base    {  // 基礎類型

   virtual void work( ){}
   virtual  ~base(){ } // 避免繼承者若在解構式中想刪除指標物件時會報出錯務訊息

}; // 先建構基礎物件的抽象類型, 統一函式介面


struct A: base { // 繼承 base 類型, 可以將物件用類型 A 封裝起來
    void work( ) override { printf("A\n");  } // 將方法封裝並覆蓋虛擬函式
};


struct B: base {// 繼承 base 類型 , 將另一物件用類型 B 封裝起來
    void work( ) override { printf("B\n");    } // 同樣將方法封裝並覆蓋虛擬函式
};


struct C {  // 一個全新多型類型(polymorphic class)
    baseptr ptr;// 宣告 ptr 成為 smart pointer 可以封裝物件 A 或 B 類型的指標    

    // 無需解構式  ~C( ){ }
    C(int a) {  // 根據輸入值實例化不同物件, 奇數傳回 A, 偶數傳回 B, 等同傳回不同物件
        if (a%2) ptr = baseptr(new A( ));  // 實例化物件 A 指標, 同時轉換成 smart pointer
        else        ptr = baseptr(new B( ));  // 實例化物件 B 指標, 同時轉換成 smart pointer
    }

};


int main(){ // 呼叫時, 先用物件直取多型成員, 接著就能用指標 -> 呼叫函式介面
   for(int i=0; i < 10; i++)  C(i).ptr->work( );
}

2019年3月24日 星期日

linux mint 升級 openssl

Openssl:
1. 至官網下載原始碼 https://github.com/openssl/openssl
2. 解壓縮, 並進入目錄
3. 執行 ./config
4. 執行 make
5. 執行 sudo make install
預設程式庫會被安裝至 /usr/local/lib, 導致 openssl 找不到 libssl.so.3 及 libcrypto.so.3 等動態程式庫, 無法執行, 只要將符號連結過去就可以解決問題:
    sudo ln -sf /usr/local/lib/libcrypto.so.3  /usr/lib
    sudo ln -sf /usr/local/lib/libssl.so.3         /usr/lib

Libressl:
1. 這是另一個源出於 Oenssl 的分支, 程式碼是相容的, 號稱刪除一些不必要的程式碼, 讓它更精減, 目標也要讓程式更安全可靠,  編譯的方式大同小異, 參考文章: http://linuxg.net/how-to-install-libressl-2-1-6-on-linux-systems , 可到官網下載源碼:  https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.9.0.tar.gz
2. 解壓縮, 並進入目錄
3. 執行 ./configure
4. 執行 make
5. 執行 sudo make install
6. 執行 sudo ldconfig
預設程式庫也是安裝至 /usr/local/lib, 如果導致 openssl 找不到 libssl.so.45 及 libcrypto.so.45 等動態程式庫, 只要將符號連結過去就可以解決問題:
    sudo ln -sf /usr/local/lib/libcrypto.so.45.0.1  /usr/lib/libcrypto.so.45
    sudo ln -sf /usr/local/lib/libssl.so.47.0.1         /usr/lib/libssl.so.45

編譯程式時要加上選項參數  -L/usr/local/lib -lssl -lcrypto, 例如:
g++   https.c -L/usr/local/lib -lssl -lcrypto  -pthread -o https
或是編寫一個簡單的 Makefile
#Makefile to link with ssl, crypto, pthead library
https:
    g++  https.c -L/usr/local/lib -lssl -lcrypto -pthread -o$@
clean:
    rm  -f  https
之後只要下一個命令, 不用再打那麼多字
make

Boringssl:
1. 同樣源自於 Openssl 的另一分支 , 由 google 負責維護的 tls/ssl 程式庫, 首先要安裝 cmake 及 golang 兩個必要開發工具:
     sudo apt-get install cmake golang
2. 到官網用 git 下載源碼, 複製整個 boringssl 目錄
     cd $HOME/Downloads
     git clone https://boringssl.googlesource.com/boringssl
3. 進入目錄, 建個子目錄 build, 進入子目錄 build,  cmake .. ,  make
     cd $HOME/Downloads/boringss
     mkdir build
     cd build
     cmake ..
     make
4. 例如要編譯 c 聯結 Boringssl 程式庫, 可以進到工作目錄, 把編譯好的程式庫複製過來作聯結:
     cd $HOME/work/c/https
     cp $HOME/Downloads/boringssl/build/ssl/libssl.a   .
     cp $HOME/Downloads/boringssl/build/crypto/libcrypto.a   .
     g++   https.c    -L.   -lssl   -lcrypto   -pthread
   

c++ 寫一個簡單的 https server

參考文章: https://wiki.openssl.org/index.php/Simple_TLS_Server
先安裝好 openssl 開發檔及準備自我認証的憑證
sudo apt-get install libssl–dev
openssl req -x509 -nodes -days 30 -newkey rsa:2048 -keyout mykey.pem -out mycert.pem
編輯 https  server 主程式:
// server.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
int main()

    SSL_library_init(); 
    SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
    SSL_CTX_set_ecdh_auto(ctx, 1);
    SSL_CTX_use_certificate_file(ctx, "mycert.pem", SSL_FILETYPE_PEM);
    SSL_CTX_use_PrivateKey_file(ctx,  "mykey.pem", SSL_FILETYPE_PEM);
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    int server = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5000);
    addr.sin_addr.s_addr = INADDR_ANY;
    int  ok = 1;
    setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &ok, sizeof(ok));
    bind(server, (struct sockaddr*)&addr, sizeof(addr));
    listen(server, 10);  
    while(1) {
        int client = accept(server, (struct sockaddr*)&addr, &len);
        printf("Accept client IP= %s : port=%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        SSL * ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client);
        SSL_accept(ssl);
        char buf[1024];
        int bytes = SSL_read(ssl, buf, sizeof(buf));
        if (bytes > 0) { buf[bytes]=0; printf("Client: %s", buf); }
        char msg[] = "<html><head><meta charset='UTF-8'></head><Body>歡迎光臨</Body></html >";
        buf[0]=0;
        sprintf(buf            ,"HTTP/1.0 200 OK\n");
        sprintf(buf+strlen(buf),"Server: Welcome\n");
        sprintf(buf+strlen(buf),"Content-Type: text/html\n");
        sprintf(buf+strlen(buf),"Content-Length: %11ld\n",strlen(msg));
        sprintf(buf+strlen(buf),"Connection: close\n\n");  
        sprintf(buf+strlen(buf),"%s",msg);
        SSL_write(ssl, buf, strlen(buf));
        SSL_free(ssl);
        close(client);  
    };
    close(server);
    SSL_CTX_free(ctx);
}
編譯並執行
g++ server.c -lssl -lcrypto  -o server &&  ./server
用瀏覽器瀏覽  https://127.0.0.1:5000 , 如有警告信息, 選取並信任該憑證

c++ 寫一個簡單的 https client

寫 https 的程式基本上就是將 http 的封包透過 TLS(Transport Layer Security:傳輸層安全協議)/SSL 加密後再傳出去, 收到的封包也是先經過 TLS/SSL 解密後還原.參考文章: https://wiki.openssl.org/index.php/SSL/TLS_Client
設定完 https server(以下使用 local host: 127.0.0.1 TCP port: 5000). 安裝 SSL 開發檔:
sudo apt-get install libssl–dev
編輯一個簡單的 https client 文字檔:
// client.c
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <openssl/ssl.h>
struct sessionTLS {
    int sid;
    SSL_CTX *ctx;
    SSL *ssl;
};
void freeTLS(sessionTLS &tls) {
    if (tls.ssl != NULL) SSL_free(tls.ssl);   
    if (tls.sid > 0)     close(tls.sid);
    if (tls.ctx != NULL) SSL_CTX_free(tls.ctx); 

}
void bindSSL(sessionTLS &tls) {
    tls.ssl = SSL_new(tls.ctx);
    if(tls.ssl != NULL ) {
        SSL_set_fd(tls.ssl, tls.sid);
        SSL_connect(tls.ssl);
        printf("\nsession TLS encryption: %s\n", SSL_get_cipher(tls.ssl));
        X509 * CA = SSL_get_peer_certificate(tls.ssl);

        if ( CA != NULL )  {
            char * subject= X509_NAME_oneline(X509_get_subject_name(CA), 0, 0);
            char * issuer = X509_NAME_oneline(X509_get_issuer_name(CA) , 0, 0);
            printf("CA: %s , %s\n", subject, issuer);
            free(subject);
            free(issuer);
            X509_free(CA);
        }
    }
}
sessionTLS connectIP(const char *ipaddress, int tcpPort){
    SSL_library_init();
    sessionTLS tls;// create a new TLS object
    tls.ctx = SSL_CTX_new(SSLv23_method());
    tls.sid = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in host;
    host.sin_family = AF_INET;
    host.sin_port     = htons(tcpPort);
    inet_pton(AF_INET, ipaddress, &host.sin_addr) ;
    connect(tls.sid, (sockaddr*)&host, sizeof(host));
    bindSSL(tls);// bind tls with SSL
    return  tls;
}
int writeTLS(sessionTLS &tls, char *msg, int len) { return SSL_write(tls.ssl, msg, len); }
int  readTLS(sessionTLS &tls, char *buf, int len) { return SSL_read(tls.ssl, buf, len); }
int main() { 
    char msg[]="Hello\n";
    char buf[1024];
    sessionTLS tls = connectIP("127.0.0.1", 5000);
    writeTLS(tls, msg, strlen(msg));
    int bytes = readTLS(tls, buf, sizeof(buf));

    char *ptr = buf;
    while (bytes-- >0) printf("%c", *ptr ++);
    printf("\n");
    freeTLS(tls);
}

用 g++ 編譯並執行:
g++  client.c -lssl -lcrypto && ./a.out

2019年3月23日 星期六

用 Javascript 寫一個任意位元的亂數產生器


function random(maxnum) {
     // 將產生後的亂數用時間函數運算過, 傳回整數並把它限制在 {0, ..., 最大值-1} 之間:
     let srand = Math.floor(Math.random( )*maxnum) ^ new Date().getMilliseconds();
     return  srand % maxnum ;
}
function srandBits(bits) {
       // 產生任意位元數的亂數
        let r = bits % 8; // 以 8 位元為單位(次), 產生一個亂數
        let num = [ ];     // 初始化儲存陣列
        if (r > 0) {
               num.push(random(1 << r)); // 先產生餘數位元的亂數
               bits -= r;
        }
        while (bits > 0) { // 還剩下的位元數, 一次產生一個位元組(8 bits = 1 byte)的亂數
            num.push(random(256)); // 產生 8 位元的亂數, 0 <= 亂數 < 256
            bits -= 8; // 減掉每次產生的 8 位元
        }
        return num; // 傳回陣列
}
console.log("random:" + srandBits(32));

2019年3月19日 星期二

ECDSA 數位簽章

參考文章: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
橢圓曲線數位簽章演算法:
假設  ECC(x,y) 是橢圓曲線方程式, 座標產生器 G 將大整數映設到 ECC 曲線上,  使用質數 p 取餘數(%),   1< n < p-1,  假設 order number = n, nG = O
1.  模除數改用橢圓曲線的 order  number  計算
2. 把私章 d, 1 < d < n -1 映射到橢圓曲線的座標 Q = dG = d*G(1) = G(d) ,  Q 就是簽章者的公鑰
3. 用 sha256 擷取文件特徵值 z, z < p,並選一亂數 k, 映設到橢圓曲線 (x, y) = kG = G(k) , 1 < k < n -1
4. 只取出 G(k)的 x 座標,  r = x, 計算出 k 的模倒數 k-1  = 1/k,   (k * k-1) % n = 1
5. 計算 s = (z + rd) / k  %  n =  (z + rd)*k-1  % n,    1 < s < n - 1
6. (z, r,  s , Q)就是帶數位簽章的文件
驗證方式:
1. 數改用橢圓曲線的 order  number  計算,
2. 計算 s 的模倒數 s-1 = 1/s,  (s * s-1) % n  = 1
3. 計算  u = z*s-1  mod n
4. 計算  v  = r*s-1  mod n
5. 用 ECC 加法運算, 計算出座標 K = uG + vQ =G(z*s-1) + G(rd*s-1) = G( (z+rd)*s-1)=G(k)
6. 取出 K 的 x 座標, 應該要等於 r

2019年3月10日 星期日

WebSocket 一些重要觀念

WebSocket  定義在 RFC6455 文件當中, 有兩個重要的章節:
1. 交握協定: https://tools.ietf.org/html/rfc6455#section-1.3
WebSocket 使用的 tcp port 跟 http 相同, 當使用者用 http GET 方法時, 其中攜帶的標頭(header)就包含升級(Upgrade)至 websocket 的動作, 標籤(Sec-WebSocket-Key)後面附帶一串 base64 明文,每次開啟交握協定時都會不同,以一組 base64 明碼為例:
         dGhlIHNhbXBsZSBub25jZQ==
如果將它解開,其實裏面是一組 16 位元組的密碼.假設客戶端所發送封包內容是:
        GET /xxx HTTP/1.1
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
 http 伺服器端收到封包偵測到 Sec-WebSocket-Key, 就將明碼串接一個共通識別碼(GUID)形成長字串:
        dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
用雜湊函數 sha1 計算並轉成 base64 明文簡化成:
        s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
伺服器端產生一個封包把它放在 Sec-WebSocket-Accept: 標籤的後面, 回應用戶端切換通信協定,完成交握程序
        HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
2. 封包格式: https://tools.ietf.org/html/rfc6455#section-5.1
一旦交握完成, 雙方封包內就不再用 http 的標頭(header), 而是轉成 WebSocket 格式的標頭, WebSocket 客戶端送的資料必須使用 xor 加密, 所附帶 4 位元組(byte)遮蔽碼負責把資料每 4 個位元組循環作加密運算(xor)轉成亂碼,伺服器端則利用這 4 個遮蔽碼同樣每 4 個位元組循環作解密運算(xor)轉成明文.
標頭第 1 個位元組分上半部與下半部:
        上半部最高位元(MSB) 標示該封包是否是最後一筆封包, 後 3  個位元保留未使用
        下半部 4 個位元代表該封包的格式(opcode),分別如下:
            0000  表示是上一封包的延續
            0001 表示文字格式(ascII code)
            0010 表示16進制(binary code)
            1000 表示關閉 Websocket 通道
            1001 表示 ping 封包
            1010 表示 pong 封包
            其他保留未使用
        用 c++ 語言描述:
        bool  fin   =   p[0] & 0x80;
        auto opcode = p[0] & 0xf ;
        if( fin ) printf("final frame\n");
        switch(opcode){
          case 0 : printf("continue frame\n") ;break;
          case 1 : printf("text frame\n")      ;break;
          case 2 : printf("binary frame\n")  ;break;
          case 8 : printf("close frame\n")    ;throw "WebSocket: Connection close!";
          case 9 : printf("ping frame\n")     ;break;
          case 10: printf("pong frame\n")  ;break;
          default: printf("reserved frame\n") ;break;
        } 
標頭第 2 個位元組指示隨後的資料, 最高位元(MSB)代表是否資料經過 xor 運算, 剩下7個位元是資料長度數值, 當數值小於 126, 就當成是資料長度(payload length), 如果等於 126, 那要用隨後的另兩個位元組當資料長度, 但如果大於 126(也就是等於 127)時, 就用隨後 8 個位元組當資料長度.客戶端送來的資料必須用 xor 運算過, 因此資料長度後面,必定先緊隨 4 個位元組的遮蔽碼(mask key), 其後才是資料(payload). 而伺服器傳給客戶端並不需要加密, 也就無需遮蔽碼, 因此長度後面緊跟的就是資料(payload)了, 簡單用 c++ 語言來解碼:             
        if (p[1] & 0x80) { // maskbit should be true
           p[1] &= 0x7f; // data in buffer will be de-mask
           auto wslen = p[1];// length value copy
           p += 2; // don't need the first 2 bytes in header
           long long payloadlen;// 8 bytes integer to get payload length
           if (wslen < 126 )  payloadlen = wslen;
           else { // 126: following 2bytes, 127: following 8 bytes
               payloadlen = *p ++;
               payloadlen = (payloadlen << 8) + *p ++;     
               if (wslen>126)  for (auto i=0; i < 6; i++) payloadlen = (payloadlen << 8) + *p ++;
           }
           unsigned char maskkey[4]; // to store maskkey
           for (auto i=0; i < 4; i++ ) maskkey = p[i]; // maskkey copy
           for (auto i=0; i < payloadlen; i++) p[i] = p[i+4] ^ maskkey[i%4]; // decode & move
           size -= 4; // size of buffer was reduced by 4 bytes of maskkey
           auto &payload = p; // payload now point to data
        }
3. 客戶端可以從 http 伺服器端接收一個 index.html 檔案, 編寫的內容像是:
<html><head><title>WebSocket 發送與接收示範</title><meta charset="UTF-8"></head>
    <script>
     const ws = new WebSocket('ws://127.0.0.1:5000');// port 必須與 http 相同
      ws.addEventListener('open', () => {
           msg = "Hello, thhis is a test message.";
           console.log("Send:  " + msg);
           ws.send(msg);
       });
       ws.addEventListener('message', event => {
           console.log("Get::  " + event.data);
           ws.close();
       });
    </script>
    <body>打開瀏覽器的 Console 視窗觀看結果</body>
</html>
內嵌的 Javascript 是一個用來開啟 WebSocket 通道的程式碼, 假設 http 伺服器使用 tcp port = 5000, 將上述的 index.html 放在伺服器的 www 根目錄底下, 用瀏覽器開啟 URL 的頁面 http://127.0.0.1:5000/index.html

2019年3月7日 星期四

簡單的 http server

用 c++ 的 thread 寫一個 http 伺服器:
// tcpserver.cpp
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <thread>
using namespace std;
template<class T>
class superBuffer   {
    private:
        unsigned msize;
        T*          address;
    public:
        const unsigned &length = msize;
        T*    const    &array  = address;
        ~superBuffer()              { if (address != nullptr) delete [ ] address; }
        superBuffer(unsigned len){ msize = len; address = new T [msize+1]; }
 };
class strBuffer: public superBuffer<char> {
    public:
        unsigned size;
        void  flush()                            {size = 0; array[0] = 0;}
        strBuffer(int len): superBuffer(len)    {flush() ;              }
        strBuffer & operator ~ (void)           {flush() ; return *this;}
        strBuffer & operator - (unsigned len)   {if (size >= len) size -= len; array[size]=0; return *this;}
        strBuffer & operator + (const char *src){ return (*this)(src); }     
        strBuffer & operator + (strBuffer &msg) { return (*this)(msg.array); }
        strBuffer & operator ( )(const char *fmt,...){ // object can be a callable function
            va_list  args;
            va_start(args, fmt);
            size += vsprintf(array + size, fmt, args);
            va_end(args);  
            return *this;
          }
 };
bool telnet(char * buffer, const char *cmd)     {
        if (buffer[0]==cmd[0] && buffer[1]==cmd[1] && buffer[2]==cmd[2] && buffer[3]==cmd[3])        return true;
        return false;
 }
char *httpGET(char * buffer, int bufsize) {
        auto src = buffer;
        if (bufsize >=15 && src[0]=='G' && src[1]=='E' && src[2]=='T' && src[3]==' ') {
            char *header = strstr(src,"HTTP/1.");
            if (header == nullptr) return nullptr;  
            src += 4;
            int len = header - src - 1;
            for (int i=0; i<len; i++) buffer[i] = src[i];
            buffer[len] = 0;
            bufsize = len;
          return header;
        }
        return nullptr;
 }
bool httpSendDoc(int fd, bool ok = true)            {
        auto msg = strBuffer(32);
        if (ok) msg("HTTP/1.0 200 OK\n");
        else    msg("HTTP/1.0 404 Not Found\n");
        auto str = strBuffer(256);
        str + msg;
        str + "Server: Welcome\n";
        str + "Content-Type: text/html\n";
        str ( "Content-Length: %11ld\n", msg.size);
        str + "Connection: close\n\n";
        str + msg;
        write(fd, str.array, str.size);
        return ok;
 }
bool httpSendfile(int fd, char *pathsrc)  {
        auto str = strBuffer(2000);
        str + "."  + pathsrc;
        FILE *fin = fopen(str.array, "rb");
        if (fin == nullptr)    return httpSendDoc(fd, false);      
        fseek(fin,0, SEEK_END);
        long fsize = ftell(fin);
        fseek(fin,0, SEEK_SET);
        (~str)+ "HTTP/1.0 200 OK\n";
        str   + "Server: Welcome\n";
        str   + "Cache-Control: must-revalidate, post-check=0, pre-check=0\n";
        str   + "Expires: 0\n";
        str   + "Content-Type: application/octet-stream\n";
        str   ( "Content-Length: %11ld\n", fsize);//bug: change %11d to %11ld
        str   + "Content-Disposition: attachment; filename=tempxxxx\n\n";      
        // bug: need double \n\n at the end of header to start file transfer  
        write(fd, str.array, str.size);
        do  {
            str.size = fread(str.array, 1, 2000, fin);
            if( str.size > 0 ) write(fd, str.array, str.size);              
        } while (str.size == 2000);
        fclose(fin);     
        return true;
 }
class tcpServer {
    private:
        static  bool serverRunning;
        static  int  sessions;
        static  void tcpSession(int fd);
        static  bool readEvent(int fd, int timeout);
        int     tcpPort;
    public:
        tcpServer(int port) {        tcpPort = port;         }  
        void operator ( ) ( ) { // this is a functor
            int servicefd = socket(AF_INET, SOCK_STREAM, 0);
            struct     sockaddr_in host;
            host.sin_family      = AF_INET;    // IPv4
            host.sin_port          = htons(tcpPort);
            inet_pton(AF_INET, "127.0.0.1", &host.sin_addr) ;
            socklen_t hostsize = sizeof(host);
            int  ok = 1;
            setsockopt(servicefd, SOL_SOCKET, SO_REUSEADDR, &ok, sizeof(ok));
            bind(servicefd, (sockaddr *) &host, sizeof(host));
            listen(servicefd, 10);
            while (serverRunning) try    {
                if (readEvent(servicefd, 2) && sessions<10) {
                    thread(tcpSession, accept(servicefd, (sockaddr *) &host, &hostsize)).detach();
                }
            } catch(const char *msg) { printf("%s\n",msg);    break;   }
            while (sessions > 0) sleep(1);
            close(servicefd);
        }
 };
int  tcpServer::sessions = 0;
bool tcpServer::serverRunning = true;
bool tcpServer::readEvent(int fd, int timeout) {
        fd_set     rdset;
        FD_ZERO(&rdset);
        FD_SET(fd, &rdset);
        timeval timer;
        timer.tv_sec  = timeout;
        timer.tv_usec = 0;    // + 0u second
        if (select(fd+1, &rdset, 0, 0, &timer) < 0) throw "SOCKET ERROR!";
        if (FD_ISSET(fd, &rdset)) return true;    // socket read
        return false;// socket timeout
 }
void tcpServer::tcpSession(int fd) {
    sessions++;
    auto const buffer = new char[2000];
    while (serverRunning) try {
        if (readEvent(fd, 5)) {
            int size = read(fd, buffer, 1999);
            if (size==0)         throw "Read size=0";
            buffer[size] =0;
            if (telnet(buffer, "exit"))            serverRunning = false;
            else if (telnet(buffer, "quit"))          httpSendDoc(fd);
            else if (httpGET(buffer, size)) {
                if( buffer[0]=='/' && buffer[1]==0 ) httpSendDoc(fd);
                else                         httpSendfile(fd, buffer);
            }
            throw "Connection: close";
        } else throw "Time is up";;
    } catch (const char *msg) { printf("%s\n",msg); break;    }
    delete [] buffer;
    close(fd);
    sessions--;
 }
int main( ){
    auto A  = tcpServer(5000); // 構建 port = 5000 的 functor
    auto tA = thread(A);            // 啟動 functor 的 thread
    tA.join( );// 等待 thread 結束
 }
用 g++  tcpserver.cpp  -pthread && ./a.out 編譯並執行

2019年3月6日 星期三

c++ 自訂一個操作字串的類型

// 自訂一個操作字串的類型  str.c
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
template<class T>
class superBuffer   {
    private:// hide member, invisible for any other class except friend class
        unsigned msize;     // length to allocate
        T*          address;// pointer to the buffer
    public:    // public for everyone
        const unsigned &length = msize;  // length is read only for public access, it's alias of msize
        T*    const    &array  = address;// array (not *array) is read only, it's alias of address
        ~superBuffer()              { if (address != nullptr) delete [ ] address; }
        superBuffer(unsigned len){ msize = len; address = new T [msize+1]; } // 1 more space to fill EOS
 };
class strBuffer: public superBuffer<char> {
    public:
        unsigned size; // # of char in-use
        void  flush()                            {size = 0; array[0] = 0;}
        strBuffer(int len): superBuffer(len)    {flush() ;              }
        strBuffer & operator ~ (void)           {flush() ; return *this;}
        strBuffer & operator - (unsigned len)   {if (size >= len) size -= len; array[size]=0; return *this;}   
        strBuffer & operator + (const char *src){ return (*this)(src); }
        strBuffer & operator + (strBuffer &msg) { return (*this)(msg.array); }
        strBuffer & operator ()(const char *fmt,...){
            va_list  args;
            va_start(args, fmt);
            size += vsprintf(array + size, fmt, args);
            va_end(args);   
            return *this;
          }
         strBuffer & dump(){
             printf("%s\n", array);
             return *this;
         }
 };
 int main( ) {
     auto str = strBuffer(100); // 產生一個字串, 長度 100 的物件
     (str + ", append a string\n" ).dump(); // 用符號 + 加入字串
     (~str)("del previous string\n").dump();// 用符號 ~ 刪除前面字串
     str("append formated string :%d\n", 3).dump(); // 用雙小括號加入格式化字串
}
需要注意 ( ) 運算符號優先順序比 ~ 的運算符號優先順序高, 如果要求順序時, 可以用雙小括號將它括起來. 用  g++  str.c && ./a.out 編譯並執行
, append a string

del previous string

del previous string
append formated string :3

2019年3月4日 星期一

用 c++ 寫簡單的多執行緒 thread

用 c++  實現多執行緒在 linux 系統下用的是 pthread library, thread 的建構方法除了可以用一般函式(function)指標當參數也可以用函式物(functor):
// thread.c
#include <thread> //  要使用 std::thread 的 STL(標準範本程式庫)
using namespace std;// 函式庫使用 std::
struct functor { // 宣告一個函式物件的新類型
     void  operator ( ) (void) {  printf("This is  a functor\n");  } // 函式物( )的運算方法
}; //新類型宣告結束要加上分號
void function1 (void)  {  printf("This is  a function\n"); } // 一般的函式
void function2 (int i)   {  printf("parameter is  %d\n", i); } // 另一種帶參數的一般函式
int main( ) {
    functor F; // 實例化一個函式物件
     thread   f1( function1 );//函式名稱本身就是一個指標, 用一般函式建構執行緒 f1
     thread   f2(function2, 3);//用帶參數函式建構執行緒 f2, 參數隨後附加在函式名稱之後
     thread   f3( F );// 另外也可以使用函式物建構執行緒 f3
     printf("main thread\n");
     f1.join( );  // 等待 f1 執行緒結束
     f2.join( ); // 等待 f2 執行緒結束
     f3.join( ); // 等待 f3 執行緒結束
}
用 g++   thread.c  -pthread 編譯並執行:
main thread
This is  a function
This is  a functor
parameter is  3

要注意的是執行緒用 thread 建構完成, 執行順序由作業系統排定, 一旦宣告完, 隨時都會執行, 上述程式當 f3 建構完成後, 包括主程式 main 在內, 實際上共有 4 條執行緒同時在運作.  thread 之間溝通橋樑最簡單的方法是利用整體變數當作共享資源互相聯絡. 但缺點是很容易產生不可預期結果,嚴重者造成鎖死當機(dead lock). 因此為了掌控 thread 的運作與穩定, 最好是透過實例化的 promise 物件, 透過它內部的方法 get_future 先獲取配對的 future 物件並傳給執行緒, 這個配對的 promise 與 future 就可以共同處理資源共享的問題, 只要簡單用 promise 所提供的方法 .set_value( )設定未來值, 再由 future 的方法 .get( ) 取得結果, 來掌控 thread 內部程式碼的執行順序. 參考範例:
// promise.c
#include <future>// 要使用std::thread , std::promise,  std::future 的 STL
using namespace std;   // 函式庫使用 std::
void function3(future<int> &f){//攜帶未來參數 f 的一般函式,參數要參考物件
    int x = f.get(); // 未來用 .get( ) 方法讀取設定值, 執行緒在此等待承諾的設定完成(fullfill)
    printf("parameter is  %d\n", x);
}
int main( ) {
     promise<int> p; // 宣告一個 promise 物件, 承諾標的物型態是 <int>
     auto  f = p.get_future();// 從 promise 物件的方法產生配對的 future 物件 f
     thread f2(function3, ref(f)); // 參考 std::ref(result) , 把它傳給函式啟動新執行緒
     printf("main thread\n");
     p.set_value(10); // 承諾設定給執行緒的未來值
     f2.join( ); // 等待 f2 執行緒結束
}
用 g++  promise.c  -pthread  &&  ./a.out 編譯並執行:
main thread
parameter is  10

要建構執行緒, 如果所用的函式指標放在整體區域, 通常不會有問題,但要注意的是, 如果放在類型內去建構或是呼叫到類型內的成員(包含變數及方法)時, 這些成員也必須具有 static (實體) 屬性, 換句話說 static 方法只能取用物件內的 static 成員. 實際上類型內所宣告的 static 成員的實體是放在整體區域,並不會因物件消滅而消失, 這也是 static 成員要放在類型外面去宣告的主要原因. 而且 static 方法內並沒有 this 這個指標或是 *this 物件可用, 也就是說類型內的 static 方法或是變數跟物件是搭不上的. 在類型內 static 方法只是一個符號連結(forward declaraction), 放在類型內的好處就是讓 static 成員也需遵循類型的區域守則,透過放在類型內的 private: 或 protected: 區域加以保護而不受整體區域變處所干擾.

c++ functor(Function object)

一個類型裏面宣告了雙小括號運算方法 operator ( )( ), 讓實例化後的物件具有函式的功能,可以用變數名稱附加雙小括號呼叫去執行. 看以下範例:
// functor.c
#include <stdio.h>
struct functor {
    int operator ( ) (int op){  // 參數 op
        return op * 3 ;  // 回傳 3 倍
    }
};  // 新類型宣告結束, 記得加分號
int main( ) {
  functor A; // 實例化一個 functor, 變數是 A
  printf( "%d\n", A(3)  ) ;            // A 呼叫運算方法 ( )
}
用  g++   functor.c   &&   ./a.out 執行看
9

2019年3月1日 星期五

c++ const 修飾詞

當 const 放在型態前面時, 表示該變數不可更動內容外,也不能用等號去設定新值. 但如果放在型態後面, 像是宣告指標常數, 雖然不能更動內容, 但卻是可以修正指標指向新的位置, 參考以下範例:
// a.c
#include <stdio.h>
int main(){
int a = 10;// a 是變數, 設定為整數 10
int b[5];// b 是 5 個整數的陣列, 內容未初始化,全是亂數, 但實際上 b 是常數, 指向 一個陣列
const int c =3;   // c 是整數的常數 3
int const *d; // d 是指標常數,型態 *d 是整數, 不能改變, 但 d 指標是可以更動的.
int*  const &e = b; // e 是陣列 b 本身的指標, e 是常數不能變, 但 e[0], ... 是可以更動的
d  = &c;             // ok, 把 d 指向 c
printf("a=%d\n", a);
printf("b[0]=%d\n", b[0]);
printf("c=%d\n", c);
printf("*d=%d\n\n", *d);
a =5;
d = &a;                // ok, 把 d 指向 a
e[0]=3;                // ok
printf("a=%d\n", a);
printf("b[0]=%d\n", b[0]);
printf("c=%d\n", c);
printf("*d=%d\n", *d);
// c =22; // error ,  c is constant
// *d = 1; // error, *d is a constant
// e =d;  // error
}
編譯並執行:  g++ a.c && ./a.out 
a=10
b[0]=785709024
c=3
*d=3

a=5
b[0]=3
c=3
*d=5