1. 程式人生 > >C++程式設計 (三)--- 深入C++後臺開發

C++程式設計 (三)--- 深入C++後臺開發

    搞了很久搜尋了,可是做的很多都是業務邏輯和PM的需求,也沒有高大上的技術。感覺隨著開源專案的成熟技術的門檻在逐漸的降低,曾經高大上的技術已經漸漸變得沒什麼門檻了。。。(記得脈脈上看到一句很好玩的話,程式設計師是一個高大上的職業,直到JAVA語言的出現。。。)不過我也還是認真總結和實踐了一些深入的技術,在做業務的時候也有一些積累的吧。總的來說C++後臺開發深入一些的有網路程式設計、多執行緒程式設計、程序/執行緒同步/通訊和排程、動態連結庫使用、常用的框架的深入閱讀和理解、常用的執行時程式問題排查(記憶體洩露、無法響應新的請求)、分散式系統的使用、高併發系統優化。所以本文一共分為如下十一個部分:

一、網路程式設計

二、多執行緒程式設計

三、多程序/執行緒同步/通訊/排程

四、動態連結庫使用

五、開源框架的深入閱讀和理解(以thrift為例)

六、常用執行時程式問題排查

七、分散式系統問題

八、高併發系統的優化

九、程式碼風格和設計模式

十、C++語言的新特性

十一、Linux核心知識

一、網路程式設計:

             1. TCP與UDP:
                      a. TCP與UDP報頭
                          1). TCP報頭(最少20個位元組):源埠、目的埠、序號、確認號、資料偏移、ACK、RST、SYN、FIN、視窗、校檢和、緊急指標、選項、填充。
                          2). UDP報頭:(8個位元組)預案埠、目的埠、使用者資料包長度、校檢和、資料。
                      b. TCP與UDP的區別
                          1). TCP是面向連線的服務,有擁塞控制和超時重傳,因此有滑動視窗。
                          2). UDP是非面向連線的服務,支援一對多通訊,如廣播。

2. TCP 3次握手、4次揮手過程:

                               
                            a. TCP三次握手、四次揮手漏洞分析,天網如何使用TCP協議中斷翻牆協議訪問。
                                    為什麼要進行三次握手?因為雙方要交換序列號和視窗大小,傳送方確認接收方接收到了syn請求,防止出現死鎖。
                                   TCP三次握手的漏洞,SYN FLOOD攻擊:客戶端不停的偽造IP來給服務端發起請求,服務端對每個syn都要分配一個TCB,通常每個TCB至少280個位元組。應對:syn cookie技術,使用雙方通訊資訊、MSS、時間等計算,看看與對方回報文中的sequence number是否相同。
                                   TCP四次揮手?因為TCP連線有個半連線狀態,假設AB要釋放,那麼A傳送了一個釋放請求給B,B立即回覆確認。但在此之間B傳送的資料A依然需要接受,B需要回復給A它不再發送資料了。
                                   為什麼TIME_WAIT需要有2MSL的時間?為了避免最後一個ACK沒有被接收到,預留重發時間。

                  3.  多路IO複用模型:
                          a. 阻塞、非阻塞:
應用程式的呼叫是否立即返回!
                          b. 非同步、同步:
資料拷貝的時候程序是否阻塞!
                          c. select、poll、epoll
                              1). 三種IO複用模型對比:
                                        select支援最大開啟檔案數目有限(一般select使用32個32位整數作為檔案描述符集)、使用者態資料需要拷貝到核心態、每次都需要線性遍歷每個FD,速度太慢; poll最大開啟fd數目不限;epoll克服了上面所有的缺點,但是如果每個連線都是活躍的,效率也不高。                              2). select:
                                        select執行流程:
                                        a). 
設定maxfd,將fd加入select監控集,使用一個array儲存放到select監控集中的d,一是用於在select返回後,array作為源資料和fdset進行fd_isset判斷。二是在select返回後會把以前加入的但並無事件發生的fd清空,則每次開始select都要從array取得fd逐一加入。(select模型必須在select前迴圈array(加fd,取maxfd),返回後迴圈array。

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
引數:    

        sockfd: socket 函式返回的套接字描述符
        readfds : 讀描述符集合
        writefds: 寫描述符集合
        errorfds: 錯誤描述符集合
        timeout:  超時
返回值
        成功:返回值 0:無 >0:描述符就緒的總位數
        錯誤:返回INVALID_SOCKET(-1)

示例:

<span style="font-size:12px;">/* 實現功能:通過select處理多個socket
 * 監聽一個埠,監聽到有連結時,新增到select的w.
 */
#include "select.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>

typedef struct _CLIENT{
    int fd;
    struct sockaddr_in addr; /* client's address information */
} CLIENT;

#define MYPORT 59000

//最多處理的connect
#define BACKLOG 5
//最多處理的connect
CLIENT client[BACKLOG];

//當前的連線數
int currentClient = 0; 

//資料接受 buf
#define REVLEN 10
char recvBuf[REVLEN];
//顯示當前的connection
void showClient();

int main()
{
    int i, ret, sinSize;
    int recvLen = 0;
    fd_set readfds, writefds;
    int sockListen, sockSvr, sockMax;
    struct timeval timeout;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;

    for(i=0; i<BACKLOG; i++)
    {
        client[i].fd = -1;
    }

    //socket
    if((sockListen=socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("socket error\n");
        return -1;
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family  =  AF_INET;
    server_addr.sin_port = htons(MYPORT);
    server_addr.sin_addr.s_addr  =  htonl(INADDR_ANY); 

    //bind
    if(bind(sockListen, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
    {
        printf("bind error\n");
        return -1;
    }

    //listen
    if(listen(sockListen, 5) < 0)
    {
        printf("listen error\n");
        return -1;
    }

    for(i=0; i<BACKLOG; i++)
    {
        client[i].fd = -1;
    }

    //select
    while(1)
    {
        FD_ZERO(&readfds);
        FD_SET(sockListen, &readfds);
        sockMax = sockListen;
    
        //加入client
        for(i=0; i<BACKLOG; i++)
        {
            if(client[i].fd >0)
            {
                FD_SET(client[i].fd, &readfds);
                if(sockMax<client[i].fd) 
                    sockMax = client[i].fd;
            }
        }
        
        timeout.tv_sec=3;                
        timeout.tv_usec=0;
        //select
        ret = select((int)sockMax+1, &readfds, NULL, NULL, &timeout);
        if(ret < 0)
        {
            printf("select error\n");
            break;
        }
        else if(ret == 0)
        {
            printf("timeout ...\n");
            continue;
        }
        printf("test111\n");
    
        //讀取資料
        for(i=0; i<BACKLOG; i++)
        {
            if(client[i].fd>0 && FD_ISSET(client[i].fd, &readfds))
            {
                if(recvLen != REVLEN)
                {
                    while(1)
                    {
                        //recv資料
                        ret = recv(client[i].fd, (char *)recvBuf+recvLen, REVLEN-recvLen, 0);
                        if(ret == 0)
                        {
                            client[i].fd = -1;
                            recvLen = 0;
                            break;
                        }
                        else if(ret < 0)
                        {
                            client[i].fd = -1;
                            recvLen = 0;
                            break;
                        }
                        //資料接受正常
                        recvLen = recvLen+ret;
                        if(recvLen<REVLEN)
                        {
                            continue;
                        }
                        else
                        {
                            //資料接受完畢
                            printf("%s, buf = %s\n", inet_ntoa(client[i].addr.sin_addr) , recvBuf);
                            //close(client[i].fd);
                            //client[i].fd = -1;
                            recvLen = 0;
                            break;
                        }
                    }
                }
            }
        }
    
        //如果可讀
        if(FD_ISSET(sockListen, &readfds))
        {
            printf("isset\n");
            sockSvr = accept(sockListen, NULL, NULL);//(struct sockaddr*)&client_addr
        
            if(sockSvr == -1)
            {
                printf("accpet error\n");
            }
            else
            {
                currentClient++;
            }
        
            for(i=0; i<BACKLOG; i++)
            {
                if(client[i].fd < 0)
                {
                    client[i].fd = sockSvr;
                    client[i].addr = client_addr;
                    printf("You got a connection from %s \n",inet_ntoa(client[i].addr.sin_addr) );
                    break;
                }
            }
            //close(sockListen);
        }
    }

    printf("test\n");
    return 0;
}

//顯示當前的connection
void showClient()
{
    int i;
    printf("client count = %d\n", currentClient);

    for(i=0; i<BACKLOG; i++)
    {
        printf("[%d] = %d", i, client[i].fd);
    }
    printf("\n");
}</span>


                                        b). int poll(struct pollfd *fds, nfds_t nfds, int timeout);

引數:
    fds
:是一個struct pollfd結構型別的陣列,用於存放需要檢測其狀態的Socket描述符;每當呼叫這個函式之後,系統不會清空這個陣列,操作起來比較方便;特別是對於socket連線比較多的情況下,在一定程度上可以提高處理的效率;這一點與select()函式不同,呼叫select()函式之後,select()函式會清空它所檢測的socket描述符集合,導致每次呼叫select()之前都必須把socket描述符重新加入到待檢測的集合中;因此,select()函式適合於只檢測一個socket描述符的情況,而poll()函式適合於大量socket描述符的情況;

    nfds:nfds_t型別的引數,用於標記陣列fds中的結構體元素的總數量;

    timeout:是poll函式呼叫阻塞的時間,單位:毫秒;

返回值:
    >0:陣列fds中準備好讀、寫或出錯狀態的那些socket描述符的總數量;

    ==0:陣列fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,如果所檢測的       socket描述符上沒有任何事件發生的話,那麼poll()函式會阻塞timeout所指定的毫秒時間長度之後返回,如果timeout==0,那麼poll() 函式立即返回而不阻塞,如果timeout==INFTIM,那麼poll() 函式會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發生是才返回,如果感興趣的事件永遠不發生,那麼poll()就會永遠阻塞下去;

     -1: poll函式呼叫失敗,同時會自動設定全域性變數errno;
示例:

<span style="font-size:12px;">/* 實現功能:通過poll, 處理多個socket
 * 監聽一個埠,監聽到有連結時,新增到poll.
 */
#include "select.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/time.h>
#include <netinet/in.h>

typedef struct _CLIENT{
    int fd;
    struct sockaddr_in addr; /* client's address information */
} CLIENT;

#define MYPORT 59000

//最多處理的connect
#define BACKLOG 5

//當前的連線數
int currentClient = 0; 

//資料接受 buf
#define REVLEN 10
char recvBuf[REVLEN];

#define OPEN_MAX 1024

int main()
{
    int i, ret, sinSize;
    int recvLen = 0;
    fd_set readfds, writefds;
    int sockListen, sockSvr, sockMax;
    int timeout;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;

    struct pollfd clientfd[OPEN_MAX];


    //socket
    if((sockListen=socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("socket error\n");
        return -1;
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family  =  AF_INET;
    server_addr.sin_port = htons(MYPORT);
    server_addr.sin_addr.s_addr  =  htonl(INADDR_ANY); 

    //bind
    if(bind(sockListen, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
    {
        printf("bind error\n");
        return -1;
    }

    //listen
    if(listen(sockListen, 5) < 0)
    {
        printf("listen error\n");
        return -1;
    }


    //clientfd 初始化
    clientfd[0].fd = sockListen;
    clientfd[0].events = POLLIN; //POLLRDNORM;
    sockMax = 0;
    for(i=1; i<OPEN_MAX; i++)
    {
        clientfd[i].fd = -1;
    }

    //select
    while(1)
    {
        timeout=3000;                
        //select
        ret = poll(clientfd, sockMax+1, timeout);
    
        if(ret < 0)
        {
            printf("select error\n");
            break;
        }
        else if(ret == 0)
        {
            printf("timeout ...\n");
            continue;
        }
    
        if (clientfd[0].revents & POLLIN)//POLLRDNORM
        {
            sockSvr = accept(sockListen, NULL, NULL);//(struct sockaddr*)&client_addr
        
            if(sockSvr == -1)
            {
                printf("accpet error\n");
            }
            else
            {
                currentClient++;
            }
        
            for(i=0; i<OPEN_MAX; i++)
            {
                if(clientfd[i].fd<0)
                {
                    clientfd[i].fd = sockSvr;
                    break;
                }
            }
            if(i==OPEN_MAX)
            {
                printf("too many connects\n");
                return -1;
            }
            clientfd[i].events = POLLIN;//POLLRDNORM;
            if(i>sockMax)
                sockMax = i;
        }
    
        //讀取資料
        for(i=1; i<=sockMax; i++)
        {
            if(clientfd[i].fd < 0)
                continue;
        
            if (clientfd[i].revents & (POLLIN | POLLERR))//POLLRDNORM
            {
                if(recvLen != REVLEN)
                {
                    while(1)
                    {
                        //recv資料
                        ret = recv(clientfd[i].fd, (char *)recvBuf+recvLen, REVLEN-recvLen, 0);
                        if(ret == 0)
                        {
                            clientfd[i].fd = -1;
                            recvLen = 0;
                            break;
                        }
                        else if(ret < 0)
                        {
                            clientfd[i].fd = -1;
                            recvLen = 0;
                            break;
                        }
                        //資料接受正常
                        recvLen = recvLen+ret;
                        if(recvLen<REVLEN)
                        {
                            continue;
                        }
                        else
                        {
                            //資料接受完畢
                            printf("buf = %s\n",  recvBuf);
                            //close(client[i].fd);
                            //client[i].fd = -1;
                            recvLen = 0;
                            break;
                        }
                    }
                }
            }
        }
    }

    return 0;
}</span>

                                        c).int epoll_create(int size);

                                              int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event );

                                              int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);

epoll_create生成一個 Epoll 專用的檔案描述符,其實是申請一個核心空間,用來存放你想關注的 socket fd 上是否發生以及發生了什麼事件。 size 就是你在這個 Epoll fd 上能關注的最大 socket fd 數,大小自定,只要記憶體足夠。
epoll_ctl控制某個 Epoll 檔案描述符上的事件:註冊、修改、刪除。其中引數 epfd 是 epoll_create() 建立 Epoll 專用的檔案描述符。相對於 select 模型中的 FD_SET 和 FD_CLR 巨集。op:EPOLL_CTL_ADD  Register the target file descriptor fd on the epoll instance, EPOLL_CTL_MOD Change the event event associated with the target file descriptor fd, EPOLL_CTL_DEL Remove  (deregister)  the  target  file descriptor fd from the epoll instance。

<span style="font-size:12px;">/* 實現功能:通過epoll, 處理多個socket
 * 監聽一個埠,監聽到有連結時,新增到epoll_event
 */
#include "select.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <netinet/in.h>
typedef struct _CLIENT{
    int fd;
    struct sockaddr_in addr; /* client's address information */
} CLIENT;
#define MYPORT 59000
//最多處理的connect
#define MAX_EVENTS 500
//當前的連線數
int currentClient = 0; 
//資料接受 buf
#define REVLEN 10
char recvBuf[REVLEN];
//EPOLL相關 
//epoll描述符
int epollfd;
//事件陣列
struct epoll_event eventList[MAX_EVENTS];
void AcceptConn(int srvfd);
void RecvData(int fd);

int main()
{
    int i, ret, sinSize;
    int recvLen = 0;
    fd_set readfds, writefds;
    int sockListen, sockSvr, sockMax;
    int timeout;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    //socket
    if((sockListen=socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("socket error\n");
        return -1;
    }
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family  =  AF_INET;
    server_addr.sin_port = htons(MYPORT);
    server_addr.sin_addr.s_addr  =  htonl(INADDR_ANY); 
    //bind
    if(bind(sockListen, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
    {
        printf("bind error\n");
        return -1;
    }
    //listen
    if(listen(sockListen, 5) < 0)
    {
        printf("listen error\n");
        return -1;
    }
    //1. epoll 初始化
    epollfd = epoll_create(MAX_EVENTS);
    struct epoll_event event;
    event.events = EPOLLIN|EPOLLET;
    event.data.fd = sockListen;
    //2. epoll_ctrl
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockListen, &event) < 0)
    {
        printf("epoll add fail : fd = %d\n", sockListen);
        return -1;
    }
    //epoll
    while(1)
    {
        timeout=3000;                
        //3. epoll_wait
        int ret = epoll_wait(epollfd, eventList, MAX_EVENTS, timeout);
        if(ret < 0)
        {
            printf("epoll error\n");
            break;
        }
        else if(ret == 0)
        {
            printf("timeout ...\n");
            continue;
        }
        //直接獲取了事件數量,給出了活動的流,這裡是和poll區別的關鍵
        int n = 0;
        for(n=0; n<ret; n++)
        {
            //錯誤退出
            if ((eventList[n].events & EPOLLERR) ||
                (eventList[n].events & EPOLLHUP) ||
                !(eventList[n].events & EPOLLIN))
            {
              printf ( "epoll error\n");
              close (eventList[n].data.fd);
              return -1;
            }
            
            if (eventList[n].data.fd == sockListen)
            {
                AcceptConn(sockListen);
        
            }else{
                RecvData(eventList[n].data.fd);
                //不刪除
             //   epoll_ctl(epollfd, EPOLL_CTL_DEL, pEvent->data.fd, pEvent);
            }
        }
    }
    close(epollfd);
    close(sockListen);
    printf("test\n");
    return 0;
}
/**************************************************
函式名:AcceptConn
功能:接受客戶端的連結
引數:srvfd:監聽SOCKET
***************************************************/
void AcceptConn(int srvfd)
{
    struct sockaddr_in sin;
    socklen_t len = sizeof(struct sockaddr_in);
    bzero(&sin, len);
    int confd = accept(srvfd, (struct sockaddr*)&sin, &len);
    if (confd < 0)
    {
       printf("bad accept\n");
       return;
    }else
    {
        printf("Accept Connection: %d", confd);
    }
    //setnonblocking(confd);
    //4. epoll_wait
    //將新建立的連線新增到EPOLL的監聽中
    struct epoll_event event;
    event.data.fd = confd;
    event.events =  EPOLLIN|EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, confd, &event);
}

//讀取資料
void RecvData(int fd)
{
    int ret;
    int recvLen = 0;
    
    memset(recvBuf, 0, REVLEN);
    printf("RecvData function\n");
    
    if(recvLen != REVLEN)
    {
        while(1)
        {
            //recv資料
            ret = recv(fd, (char *)recvBuf+recvLen, REVLEN-recvLen, 0);
            if(ret == 0)
            {
                recvLen = 0;
                break;
            }
            else if(ret < 0)
            {
                recvLen = 0;
                break;
            }
            //資料接受正常
            recvLen = recvLen+ret;
            if(recvLen<REVLEN)
            {
                continue;
            }
            else
            {
                //資料接受完畢
                printf("buf = %s\n",  recvBuf);
                recvLen = 0;
                break;
            }
        }
    }

    printf("content is %s", recvBuf);
}</span>

                   4. 網路協議分層模型: 
                   5. 其他一些小問題:

                       a. 網路模型直接排序一般採用大端儲存的。
                       b. keepalive???:                       c. 長連線、短連線:
                       d. 防火牆如何利用TCP協議終止你的翻牆請求?

                   6. 網路分層模型和不同層的支援的協議:

(如何畫時序圖)

二、多執行緒程式設計:

                   1. 執行緒與程序:
                        執行緒共享:程序指令、大多數資料、開啟的檔案、訊號處理函式和訊號處置、當前工作目錄、使用者id和組ID
                        程序共享:執行緒ID、暫存器集合(程式計數器和函式指標)、棧、errno、訊號掩碼、優先順序
                        執行緒&程序:執行緒是系統排程的最小單元,fork需要把父程序記憶體影響複製到子程序;fork返回以後父子程序需要進行IPC通訊。 

                   2. 多執行緒程式設計:
pthread_create( pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void arg);
                                    pthread_join(pthread_t *tid, void **status) //如果status指標非空,把執行緒的返回值、一個指向某個物件的指標存入status指向的位置
                                    pthread_self(void) // 執行緒自身id
                                    pthread_detach(tid)// 執行緒終止時,所有資源被釋放
                                    pthread_exit(void *status)//執行緒終止

                  3. 多程序程式設計:
fork()//建立子程序
                                    exec()//4個函式族。建立程序,但子程序執行時廢棄當前程序的資料段和堆疊段

                   4. 程序/執行緒間同步\通訊:

                      一般我們討論程序間共享資料,執行緒間同步。因為執行緒很多資料都是共有的,而程序間很難共享資料,因此執行緒的同步和程序的資料共享一般都是我們面臨的問題的。
                      a. 程序間共享/通訊資料(IPC)方法:
                          管道、FIFO、共享記憶體、訊息佇列、訊號、socket
                           對比:
                                    1. 管道:速度慢、容量有限、只有父子程序通訊
                                    2. FIFO: 任何程序都能通訊,但速度慢
                                    3. 訊息佇列:容量受限
                                    4. 訊號量:不能傳遞複雜資訊
                                    5. 共享記憶體:速度快、容量大。需要注意同步


                          1). 
                      b. 執行緒間同步方法(SYNC):
                          臨界區、鎖、訊號量、事件、interlocked variable

                          1). 鎖:

                                            pthread_mutex_t counter_mutex=PTHREAD_MUTEX_INITIALIZER; COND

                                            pthread_mutex_lock(pthread_mutex_t * mptr);
                                            // do something

                                            pthread_mutex_unlock(pthread_mutex_t *mptr);


                           http://www.cnblogs.com/memewry/archive/2012/08/22/2651696.html

五、開源框架深入閱讀和理解:

                 1. thrift協議的資料型別、協議、傳輸、服務型別
                 2. thrift協議原始碼閱讀

六、常用執行時程式排查:

                 1. 使用cp替換so檔案為什麼服務會core?
                 2. 如何排查記憶體洩露

七、分散式系統問題:

十、C++語言的新特性:

             1. 智慧指標 std::shared_ptr 用法:
                           a). 智慧指標是用來實現指標物件的共享和記憶體生存期自動管理(一般使用引用計數實現,我理解智慧指標就是一個棧物件,在智慧指標的生命期結束時,對智慧指標指向的動態記憶體使用減1,如果計數減為0,則釋放記憶體)。
                           b). 所有的智慧指標都會過載* -> 等符號。

#include <boost/shared_ptr.hpp>
using namespace std;
class implementation
{
public:
    ~implementation() { std::cout <<"destroying implementation\n"; }
    void do_something() { std::cout << "did something\n"; }
};
void test(){ 
    boost::shared_ptr<implementation> sp1(new implementation()); 
    boost::shared_ptr<implementation> sp2 = sp1; 
    sp2.reset(); 
    boost::shared_ptr<int> a1(ptr); 
    std::cout<<*ptr<<endl;
}
                           b). 使用智慧指標注意事項:
                                 a. 不要把原生指標給多個shared_ptr管理。
                                 b. 不要在函式實參裡建立shared_ptr。
                                 c. shared_ptr作為被保護物件時小心迴圈引用。

                                 d. 不要把this指標傳給shared_ptr。

                           c). static_ptr_cast ???:
                                 

                  2. auto用法:

參考文獻: 1. Unix網路程式設計:
2. thrift服務原始碼分析: http://yanyiwu.com/work/2014/12/06/thrift-tnonblockingserver-analysis.html 3.