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);
引數:
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.