面經問題整理
-
-
memcpy的實現
/*按照4個位元組拷貝*/
void *memcpy(void *dst, const void *src, size_t num) {
assert((dst != NULL) && (src != NULL));
int wordnum = num / 4;
int slice = num % 4;
int *pintsrc = (int *)src;
int *pindst = (int *)dst;
while(wordnum--) *pintdst++ = *pindsrc++;
while(slice--) *((char *)pintdst++) = *((char *)pintsrc++);
} -
ping原理
在同一個廣播域中,我們的host1想要去訪問host2,如果host1的mac表上有host2的mac地址,如果有的話,host1會向host2傳送ICMP報文。如果沒有,host1就會向外部發送arp廣播包,交換機在收到這個apr廣播包之後,會檢視自己的mac表,如果有的話,通過arp報文返回。如果沒有交換機就會向所有的埠傳送arp廣播,其他埠的主機上接收到這個arp報文段之後,如果目標不是自己,就丟棄。一直到host2的主機接受了報文之後,就會響應host2的mac的mac地址是多少,然後交換機經過學習之後,通過arp報文的形式返回給host1 。host1收到報文之後,會發送icmp報文,host1的報文段中icmp為echo request,host2的響應報文段中icmp為echo answer
在不同的廣播域中,首先通過arp報文確定閘道器的mac地址,閘道器收到arp報文之後,通過arp報文找到host2的mac地址。確定之後,host1傳送icmp報文到閘道器,然後閘道器將源mac替換為自己的mac,然後向host2轉發,host2收到報文之後,就會返回icmp報文
-
ICMP報文
Internet control message protocol,網路訊息控制協議,分為兩部分,ICMP差錯報告報文,ICMP詢問報文。
為什麼存在ICMP報文,因為IP協議是不可靠的,沒有差錯機制,而ICMP就可以彌補這個情況
-
webserver專案
reactor的優勢
-
單reactor單執行緒模型
reactor執行緒負責多路分離套接字,accept新連線,然後將請求分派到handler中
缺點:
-
一旦reactor執行緒意外中斷或則進入死迴圈,會造成整個系統通訊模組不可用
-
當reactor執行緒負載過重,會導致大量訊息擠壓和處理超時,有系統瓶頸
-
-
單reactor多執行緒模型
在事件處理方面採用了多執行緒
優點:
-
降低了reactor的效能開銷
缺點:
-
reactor負責承擔所有事件的監聽和響應,可能會存在效能問題
-
-
主從reactor‘多執行緒模型
優勢:
-
響應快
-
可擴充套件性好
-
程式設計簡單,避免了多執行緒或者多程序的切換
-
-
-
三七互娛筆試題
-
通常作業系統提供的使用者介面包括 命令介面,程式介面和圖形介面
-
RSA,SHA,DES,BASE64哪個不可逆?
RSA是非對稱加密,DES是對稱加密,BASE64為簡單的編碼解碼,SHA為hash演算法,不可逆
-
設某棵二叉樹有360個節點,則該二叉樹的最小高度是(包括根節點)?
深度為h的二叉樹,最多含有2^h -1 個節點,所以h最小取9
-
在linux中,有一個資料夾裡面含有若干檔案,通常用哪個命令獲取這個檔案的大小? B
-
ls -h 只有加l的時候才會顯示
-
du -sh du顯示當前目錄及子目錄的磁碟佔用情況
-
df -h df顯示整個檔案系統的使用情況
-
fdish -h
-
-
馮。諾伊曼機工作的基本方式的特點是?
按地址訪問並且順序執行指令
-
預設的linux系統中,從後臺啟動程序,因該在命令的結尾新增哪個符號?
&
-
dup和dup2函式
dup函式返回當前可用檔案描述符的最小值,dup2可以使用第二個引數指定的新的描述符的數值,如果第二個引數已經開啟,。就先將其關閉,如果兩個引數相同返回第二個引數,並且不關閉。 dup返回的新的檔案描述符和dup2第二個引數共享同一個檔案表項
-
手寫泛型max函式
template <typename T>
void Swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}為什麼函式模板能夠執行不同的型別引數?
編譯器對函式模板進行了兩次編譯,第一次是檢查語法是否正確,第二次是找到呼叫函式的程式碼,然後通過呼叫程式碼來判斷引數型別
-
執行緒有哪些狀態?
執行態,掛起態,阻塞態,終止態 【掛起態和阻塞態的區別,阻塞態是等待某個事件的發生,放棄CPU的使用權。掛起態是等待獲取CPU的資源,只要有CPU就能夠繼續往下執行。而阻塞態即使獲得了CPU,而等待的事件沒有發生,也不會繼續往下執行】
-
TCP/IP在協議安全缺陷主要表現在那幾方面?
TCP協議的安全問題,三次握手來建立一條連線。但是這個過程是有漏洞的,假設傳送方為A,接收方為B,第一次A向B傳送了一個SYN包,第二次的時候B會回覆一個ACK+SYN包,這個時候C監聽B的報文,然後給B傳送RST包,然後C偽裝成A向B傳送SYN包建立新的連線
-
UDP中使用bind和connect的作用
-
首先,UDP的connect和TCP的connect有本質的區別,TCP中呼叫connect會引起三次握手,而UDP中呼叫connect僅僅是把對端的IP和PORT記錄下來
-
UDP中多次呼叫connect有兩個作用,指定一個新的IP&PORT連線,斷開和之前的IP&PORT連線
-
UDP中使用connect可以提高效率,普通的UDP傳送兩個報文,核心做了如下操作 建立連線,傳送報文,斷開連線,建立連線,傳送報文,斷開連線。而採用connect方式的UDP傳送兩個報文核心處理如下:建立連線 傳送報文 傳送報文 斷開連線
-
在高併發服務的時候更加穩定,如果client A想要和serverB,C進行UDP的通訊,如果通過非connect的方式,我們讓a和B交替通訊,A和B通訊 IPa:PORTa <=> IPb:PORTb, IPa:PORTa' <=> IPc:PORTc 如果PORTa和PORTa'相同,那麼就有可能出現A等待B的報文,卻收了C的報文,導致收報錯誤,解決方法就是採用connect的通訊方式,分別建立兩個UDP,然後分別connect到B和C,這樣就不會造成上述的問題了
-
-
socket程式設計中write,read和send,recv之間的區別?
我們建立好tcp連線之後,就可以將得到的fd當作檔案描述符來使用。
write的返回值大於0,表示寫入了部分或者全部的資料,可以通過while迴圈來實現資料的完整寫入。如果返回的值小於0,代表出現了錯誤。如果錯誤為EINTR,代表寫的時候出現了中斷錯誤,如果是EPIPE,表示網路出現了問題,可能是對方已經關閉連線
read讀取的時候和write類似,只不過判斷錯誤的型別不同,小於0表示出現了錯誤。如果錯誤為EINTR代表中斷,如果是ECONNECT表示網路連接出現了問題
recv和send函式提供了和read,write差不多的功能,只不過提供了第四個引數來控制讀寫操作。
MSG_DONTROUTE 不查表,告訴目標主機在本地網路中,一般用於網路診斷和路由程式中
MSG_OOB 接收或者傳送帶外資料
MSG_PEEK 檢視資料,並且不從系統緩衝區移走資料。從系統緩衝區中讀取資料,而不清除系統緩衝區的內容,一般有多個程序讀寫的時候可以使用這個標誌
MSG_WAITALL 等待所有的資料,recv會一直阻塞,一直等到條件滿足或者發生錯誤。當讀到指定的位元組,返回值為len,正常返回。當讀到了檔案的結尾, 函式正常返回,返回值小於len
-
socket中close和shutdown的區別
https://www.jianshu.com/p/eecab8d50697
shutdown是一種優雅的單方向或者雙方向關閉socket的方法,而close是立即雙方強制關閉socket並且釋放相關資源
如果多個程序共享同一個socket,shutdown會影響所有的執行緒,而close隻影響本執行緒
下面從兩個方面來進行解釋:
-
server端呼叫shutdown
server呼叫了shutdown之後,此前發出的非同步的send/recv都不會立即返回,當所有傳送的包被client確認之後,不管是關閉傳送還是關閉接收,server會發送FIN到client,然後開始四次揮手斷開連線
-
server端呼叫close
根據引數的設定不同,呼叫close會出現不同的兩種情況
第一,當l_onoff為0,l_linger為0的時候,server呼叫close會向客戶端傳送RST報文段,然後丟棄緩衝區中未讀取的資料並且關閉socket以及釋放相關的資源
第二,當l_onoff非0,l_linger非0的時候,server呼叫close之後,會向客戶端傳送一個FIN報文段,在收到client的ack之後,server進入了FIN_WAIT2狀態,如果在l_linger的時間內還沒有完成四次揮手,就會強制關閉
client端如何知道已經接收到了RST報文?
server傳送RST報文之後,並不等待從client端收到任何ack應答,直接關閉socket,而client收到RST之後,也不會產生任何響應
在阻塞模式下,核心無法主動通知應用層出現錯誤,只有當應用層主動呼叫read或者write這樣的IO系統呼叫之後,核心才會用通知的方式來告訴應用層對方已經發送了RST報文
在非阻塞模式下,select或者epoll會返回sockfd可讀,應用層在進行讀取操作的時候,read會會報RST錯誤
client收到RST之後如何處理?
client收到RST報文之後,如果繼續呼叫read函式,會返回RST錯誤,在已經產生RST錯誤的前提下,如果再次呼叫write,會產生EPIPE錯誤,然後核心會向程序傳送SIGPIPE訊號,在未處理訊號SIGPIPE的前提下,訊號的處理函式會將程序終止
-
-
-
do{...}while(0)的妙用
-
幫助定義複雜的巨集來避免錯誤
這樣在展開的時候,就會變成
if(a > 0)
foo1();
foo2();這樣就會出現錯誤了,無論a > 0,foo2都會執行
那如果將foo1和foo2包起來呢
# define func() {foo1();foo2();}
程式碼編譯的時候,就會改成{...};
這樣在展開的時候,就會變成
if(a > 0) { foo1(); foo2(); };
這樣後面就會多一個分號,可能不會報錯,但是要儘量避免
而如果用
# define func() do{foo1();foo2();}while(0)
就能夠讓分號正確的使用,符合語法
-
定義單一的函式塊來完成複雜的操作
在do{...}while(0);中,可以定義變數並且不用考慮變數名會和函式中的變數起衝突
-
避免由巨集定義引起的警告,空巨集在編譯的時候可能會引起warning,為了避免這個,可以使用do{...}while(0);來定義空巨集
-
保證只執行一次,並且可以用break跳出迴圈,可以用來替代goto,並且程式碼可讀性變高
-
-
c++11瞭解多少?
-
右值引用 std::move()
-
auto for迴圈
-
std::lambda,是一個匿名函式
auto func1 = [](int i) {return i + 4;}
-
std::bind實現函式組合,可以繫結普通函式,lambda表示式,成員函式,成員變數,模板函式等
-
std::function 通過std::function中各種可呼叫的實體(普通函式,lambda表示式,函式指標,函式物件等),形成一個新的可呼叫的std::function物件
-
initializer_list initial lizer
std::vector v = {1, 2, 3, 4};
-
thread 執行緒的建立
-
互斥量
-
atomic 原子操作
簡單的應用,對Int,char,bool等進行原子性封裝,在多執行緒併發的時候,對std::atomic物件的訪問不會造成競爭-冒險,可以實現資料結構的無鎖設計
#include <thread> #incldue <atomic> #include <iostream> using namespace std; atomic_long total(0); void click(int i) { cout << " thread" << i << endl; for(i = 0; i < 100; i++) { total += 1; } } int main() { std::thread threads[10]; for(int i = 0; i < 10; i++) { threads[i] = std::thread(click, i); } for(auto &t : threads) { t.join(); } cout << "value: " << total << endl; return 0; }
高階用法
store 原子寫操作 load 原子讀操作 exchange 允許兩個值進行交換,並且保證整個過程是原子的 compare_exchange_weak 當前值和期望值相同時,修改當前值為設定值,返回true;當前值和期望值不相等的時候,將期望值修改成當前值,返回false;weak允許假失敗,當期望值和當前值比較的時候,可能用 = 判斷相同,但是用整個函式判斷卻有可能失敗, true和3都表示真,但是compare_exchange_weak會返回虛假的false,但是比比strong效能好,在一些迴圈演算法中weak還可以接受 compare__exchange_strong 當前值和期望值不相等的時候,將期望值修改成當前值,返回false,當前值和期望值相等的時候,將當前值改成期望值
-
override和final關鍵詞
override關鍵詞強制檢查虛擬函式,確認派生類中的成員函式覆蓋基類中的虛成員函式(可能函式名相同,但是引數不同)。final關鍵詞禁止虛擬函式的繼續重寫
-
delete和default關鍵詞
delete,禁止編譯器預設生成的函式,可以用在模板特例化中,通過delete來過濾一些特定的型別引數;
default,當我們過載了c++預設生成的函式之後,c++將不再生成這些函式的預設形式,但是c++允許通過=default來要求編譯器生成一個預設函式
-
正則表示式
-
-
移動語義和完美轉發介紹一下?
移動語義,首先說一下可拷貝和可移動,可拷貝就是有的類是可以複製的,可以呼叫拷貝建構函式。有的類物件是獨一無二的,是不可複製的,但是可以把資源所有權交給新的物件,這個稱為可移動的,這樣的話,在一些物件的構造時可以直接獲取到已經存在的資源而不需要通過拷貝,申請新的記憶體,這樣移動就可以避免拷貝,從而提高效能
https://github.com/changkun/modern-cpp-tutorial/blob/master/book/zh-cn/03-runtime.md
右值可以細分為兩種,純右值和將亡值,將亡值就是即將被銷燬,卻能夠被移動的值,函式在返回的時候,產生一個將亡值,被物件的移動構造引用,從而延長了生命週期,並且將其中的指標拿出來儲存到obj中,然後將將亡值的指標設定為null,這樣就能夠比避免無意義的構造拷貝
完美轉發,是為了讓我們在傳遞引數的時候,保持原來的引數型別(左引用保持左引用,右引用保持右引用)。std::forward
-
協程
和執行緒一樣共享堆,可以懸停和恢復的函式
-
stl vector push_back的複雜度,擴容機制? 為什麼要二倍擴容?
vector在push_back以成倍增長在均攤後可以達到常數的複雜度,相對於O(n)的複雜度更好
-
阻塞訊號集,未決訊號集
阻塞訊號能決定未決訊號集,當阻塞訊號集中該訊號成功從1變成0的時候,這個時候未決訊號集也變成0
-
htonl和inet_pton的區別
都是對IP地址的轉換,只不過第二個比第一個多了一個指定緩衝區的大小,還有返回的結果,htonl返回整型,inet_pton返回字串
-
全域性變數,靜態變數初始化時間
全域性變數的初始化時間,既有編譯的時候,也有在執行的時候。
static initialization當用常量開對變數進行初始化的時候,是在程式載入的過程中完成的,分為zero initialization和constant initialization,一個是初始化為0儲存在bss段,另一個儲存在data段
而需要經過函式呼叫才能完成初始化的全域性變數,是靜態初始化和動態初始化的結合,狀態的時候先給變數分配地址空間,進行zero initlization,然後程式執行的時候在呼叫函式進行dynamic initialization
全域性靜態變數,在main函式執行之前的靜態初始化過程中分配記憶體並且初始化,區域性靜態變數在第一次使用的時候進行分配記憶體並且初始化
-
mysql索引
如果沒有索引這個東西, 一旦我們執行一個sql語句,就很有可能造成一個全表的搜尋,效率太低了。為了提高效率,我們開始採用資料結構,首先來對比hashmap,插入,修改,刪除,查詢的複雜度為O(1),而樹結構只能達到logn級別的,那為什麼索引的底層實現不用hashmap而是使用樹呢,是因為如果我們要進行範圍查詢,以及比較大小的時候,hashmap只能是進行全表的搜尋,而Tree依然能夠保證logn的高效率。在樹中,二叉樹和b/b+樹相比,在資料非常大的時候,二叉樹會變得很高,而b/b+樹能夠完美的利用區域性性原理(時間上,當前要執行的指令和下一條執行的指令,當前資料的訪問和下一次訪問都是在一個較短的時間內;空間上,當前指令和相鄰的指令,當前訪問的資料集和臨近的幾個資料集都存放在一個較小的區域上)
b+樹只有葉子節點儲存資料,非葉子節點儲存索引,所以和b樹相比,每一個非節點能儲存的索引變多,以及不需要進行中序遍歷就能夠訪問範圍內的數,這樣效率會更加提高
-
sleep和wait的區別?
sleep呼叫的時候會讓執行緒暫停指定的時間,但是監控依然存在,物件鎖也不會釋放。wait呼叫會放棄物件鎖,等待呼叫notify喚醒之後才能再次獲取物件鎖進入執行狀態
-
對於管程的理解
-
概念
管程可以看作一個軟體模組,它是將共享的變數和對於這個共享變數的操作封裝起來,形成一個具有一定介面的功能模組, 程序可以呼叫管程來實現程序級別的併發控制
程序只能互斥的使用管程,當一個程序使用管程的時候,另一個程序必須等待
在管程入口等待的佇列稱為入口等待佇列,由於程序可能會執行喚醒操作,因此可能有多個等待使用管程的佇列,這樣的佇列稱為緊急佇列,優先順序高於等待佇列
-
特徵
模組化,抽象的資料型別,資訊隱藏,使用的互斥性
-
過程
enter過程
一個程序進入管程之前要提出申請,一般由管程提供一個外部過程
leave過程
當一個程序離開管程的時候,如果緊急佇列不為空,那麼它就必須負責喚醒緊急佇列中的一個程序
條件變數c
是一個指標,如果motfull表示不滿,如果緩衝區已經滿了,那麼將要在緩衝區進行寫入的程序就要等待notfull
wait(c)
表示進入管程的程序申請使用c資源,如果此時這個資源可用,程序使用。如果不可用,程序阻塞,放入緊急佇列中
signal(c)
表示進入管程的程序使用的某種資源要釋放,此時程序會喚醒等待這種資源而進入緊急佇列的第一個程序
-
-
二叉樹任意兩個節點之間的最長路徑的長度
假設根節點為node,max_len(node) = max_len(node->right) + max_len(left) + 2
max(node->right) = max(node->right->right, node->right->left) + 1
-
為什麼MTU的大小普遍都是1500 ?
一個標準的乙太網資料幀是1518,頭資訊有14位元組,尾部校驗和FCS佔了4位元組
考慮到傳輸效率和傳輸時間而折中的一個值,如果某一個節點的MTU和其他節點不同,還需要進行拆包重組
-
TCP擁塞控制和流量控制的區別?
擁塞控制是為了防止過多的資料注入到網路中,導致網路超出負荷;流量控制是為了解決傳送方和接收方接受速度不一致而導致資料丟失問題,採用滑動視窗的形式來解決問題
-
TCP擁塞控制的缺點
-
網路資源分配的不公平導致加重擁塞,在擁塞發生的時候,有擁塞控制反應機制的TCP資料流會按照擁塞控制進入擁塞避免階段,主動減少傳送進入網路的資料的數量。而UDP沒有擁塞控制,這樣會導致遵守擁塞控制的TCP得到的網路資源越來越少,沒有遵守擁塞控制 的UDP得到的資源卻越來越多
-
一些TCP連線之間也存在公平性問題,比如一些TCP在擁塞前使用了大視窗,或者RTT比較小,資料包比其他TCP大,這樣也會佔據頻寬
-
-
GET和POST的安全問題
-
POST更加安全一點,因為GET的傳輸方式是在URL中顯示引數,容易洩露資訊。post相對於get來說更加安全一點,把資料隱藏在了請求體中
-
HTTP協議中提到了GET是安全的方法,因為GET不會改變伺服器中的資料,所以不會產生副作用
-
只要正確使用的話,沒有多大問題。對於非保密的資訊通過get來傳輸,而對於保密的資訊來通過post傳輸
-
-
檢視最近的日誌用什麼命令?
tail -f 檔名
-
設計模式
單例模式,分為餓漢模式和飽漢模式,餓漢模式就是在類在載入的時候進行例項化
餓漢模式
class Node { private static Node node; static { node = new Node(); } private Node(){ } public: static Node getnode() { return node; } }
飽漢模式
class Node { private static Node node; private Node () { } public static Node node () { if(node == null) { node = new Node(); } return node; } }
-
雜湊避免衝突的演算法?
-
線性探測法,如果鼬衝突的話,往後找,找到第一個非空的位置放進去,缺點:刪除比較麻煩,只能標記該位置已經被刪除;容易產生堆聚現象
-
線性補償探測法,將增量從1變成了q,q和m互斥
-
隨機探測,每一個關鍵字的步長是隨機生成的,可以避免或者減少堆聚
-
拉鍊法,通過連結串列來進行儲存
-
平方探測法
-
-
快排的時間複雜度和空間複雜度。為什麼快排不穩定
快排的時間複雜度:最優情況下(時間複雜度 nlogn,空間複雜度nlogn),最差情況下(時間複雜度n^2,空間複雜度 n)
-
堆排序的時間複雜度和空間複雜度?
時間複雜度,建堆的複雜度 O(n),重建堆的複雜度O(logn) nlogn,空間複雜度O(1)
-
vector裡面size和capacity的區別?
size指的是當前有多少值,capacity是指定最少要多少元素才會使其容量重新分配
-
c++程式的記憶體格局
棧,堆,自由儲存區,全域性/靜態儲存區,常量儲存區
-
一個 c++原始檔從文字到可執行檔案經歷的過程
預處理,編譯,彙編,連結
i s o exe
預處理:將define刪除,將巨集定義展開;處理一些條件編譯指令,ifndef,ifdef,endif等;處理#include預編譯指令。過濾掉註釋,新增行號和檔名標識;保留#pragma指令
編譯:將預處理的檔案進行一系列的詞法分析,語法分析,語義分析,以及優化後產生的彙編檔案程式碼檔案
彙編:將彙編檔案翻譯成機器語言,生成目標檔案
連結:將每個原始碼獨立的編譯,然後按照他們的要求組裝起來,主要解決程式碼之間的依賴問題
處理方式分為靜態連結和動態連結。
-
前++和後++如何過載?
class Complex { public: Complex(int t) { a = t; } friend Complex& operator ++ (Complex &c); friend const Complex operator++(Complex &c, int); private: int a; } Complex& operator++(Complex t) { t.a++; return t; } const Complex operator++(Complex &c, int) { Complex tmp(c.a); c.a++; return tmp; }
-
哪些運算子可以過載?
不可過載的運算子, :: . ?:
-
計算機網路的七層結構
應用層,表示層,會話層,傳輸層,網路層,資料鏈路層,物理層
-
二叉樹的最大路徑和
int cal(TreeNode *root, int& m) { if(!root) { return 0; } int left = cal(root->left, m); int right = cal(root->right, m); m = max(m, left + right + root->val); m = max(m, max(left + root->val, right + root->val)); m = max(m, root->val); left += root->val; right += root->val; int tmp = max(left, right); tmp = max(tmp, root->val); return tmp; }
-
能不能同時用static和const修飾類的成員函式?
不能,c++編譯器在實現const的成員函式得時候為了確保該函式不能修改類的例項的狀態,會在函式中新增一個隱式的引數const this*,但是當一個成員為static的時候,這個函式是沒有this指標的,也就是說會發生衝突
-
七層模型,每一層的作用
應用層:為程序提供網路介面
表示層:資料格式的變化,資料加密和解密,資料壓縮和恢復
會話層:組織同步的兩個會話使用者之間的對話,並且管理資料的交換
傳輸層:向用戶提供可靠的端到端的服務,透明的傳輸報文
網路層:路由定址
資料鏈路層:在通訊實體上建立資料鏈路交換,傳送以幀為單位的資料,通過差錯控制,流量控制方法,將有差錯的物理線路變成無差錯的資料鏈路
物理層:利用物理傳輸介質為資料鏈路層提供物理連線
-
二分
int search(int l, int r, int val) { while(l < r) { int mid = l + r >> 1; if(check(mid)) { ans = mid; l = mid; } else { r = mid - 1; } } }
-
linux查詢檔案中含有某行字串的行數
find filename | xargs cat | grep . "hello" | wc -l
-
http 1.0, http 1.1, http 2.0的區別
http 1.0和http1.1相比
-
頻寬優化和網路連線的使用,在http 1.0中,存在一些浪費的情況,比如客戶端只是需要一個物件的一部分,而伺服器卻將整個物件送過來,並不支援斷電重續功能。http 1.1在請求頭中引入了range,允許只請求資源的某個部分,返回碼是206.
-
長連線
-
錯誤通知管理,http 1.1中,409代表請求的資源和當前的狀態發生衝突。410代表伺服器上的某個資源被永久性刪除
-
host頭處理,在http 1.0中認為每臺伺服器都繫結一個唯一的IP,因此在URL中並沒有傳遞主機名。而隨著技術的發展,一個伺服器可以存在多個虛擬主機,並且共享一個IP地址,所以http1.1的請求訊息支援host頭域
http1.x的優化
-
降低延遲,多路複用
-
請求優先順序
-
header壓縮
-
基於https的加密傳輸
-
伺服器端推送
http2.0和http1.x相比
-
2.0可以多個請求在一個連線上並行執行
-
-
24點遊戲
#include <bits/stdc++.h> using namespace std; # define eps 1e-5 bool judge(vector<double> sto) { if(sto.size() == 1) { return fabs(sto[0] - 24) <= eps; } int len = sto.size(); for(int i = 0; i < len; i++) { for(int j = i + 1; j < len; j++) { vector<int> vis(len, 0); vis[i] = vis[j] = 1; vector<double> tmp; for(int k = 0; k < len; k++) { if(vis[k])continue; tmp.push_back(sto[k]); } double oper; // 加法 oper = sto[i] + sto[j]; tmp.push_back(oper); if(judge(tmp)) { return true; } tmp.pop_back(); // 減法 oper = sto[i] - sto[j] > 0 ? sto[i] - sto[j] : sto[j] - sto[i]; tmp.push_back(oper); if(judge(tmp)) { return true; } tmp.pop_back(); // 乘法 oper = sto[i] * sto[j]; tmp.push_back(oper); if(judge(tmp)) { return true; } tmp.pop_back(); // 除法 //if(sto[i] == 0 || sto[j] == 0) continue; if(sto[i] != 0) { oper = sto[j] / sto[i]; tmp.push_back(oper); if(judge(tmp)) { return true; } tmp.pop_back(); } if(sto[j] != 0) { oper = sto[i] / sto[j]; tmp.push_back(oper); if(judge(tmp)) { return true; } tmp.pop_back(); } } } return false; } int main() { int i, tmp, a, b, c, d; while(cin >> a >> b >> c >> d) { vector<double> sto; double cal = static_cast<double>(a); sto.push_back(cal); cal = static_cast<double>(b); sto.push_back(cal); cal = static_cast<double>(c); sto.push_back(cal); cal = static_cast<double>(d); sto.push_back(cal); cout << (judge(sto) == 1 ? "true" : "false") << endl; } return 0; }
-
對抽象類和介面的理解
抽象類是隻能定義型別,不能生成物件的類。抽象類中定義的純虛擬函式,一方面是為了給派生類提供虛擬函式,另一方面定義為純虛擬函式就是為了不定義函式體
介面時一個特殊的抽象類,成員函式全部是public,並且都是純虛擬函式
-
mysql主從複製
-
redis的持久化,有哪些方式,原理是什麼?
https://blog.csdn.net/qq_17312239/article/details/81009522
redis的持久化就是將資料放到斷電後不會丟失的裝置中
資料庫在進行持久化的時候都做了哪些事情?
-
客戶端向伺服器端傳送寫操作,此時資料儲存在客戶端記憶體中
-
伺服器端收到寫請求,此時資料儲存在伺服器端的記憶體中
-
伺服器端呼叫write往硬碟上寫,此時資料儲存在系統記憶體的緩衝區中
-
作業系統將緩衝區中的資料寫入到磁碟控制器中,此時資料在磁碟緩衝中
-
磁碟控制器將資料寫入到磁碟的物理介質中,此時資料才真正落到磁碟上
當資料庫系統系統發生故障的時候,這個時候系統的核心還是完好的,只要執行完write之後,是沒有問題的,剩下的讓OS來處理
當系統發生斷電的時候,上面五步都會失效,只有當資料在完成第五部之後,才能保證斷電後資料不丟失
廣泛的來說,對於資料損害有三種處理方式
-
進行資料整體的備份
-
進行資料命令的備份
-
資料庫不對九的資料進行修改,只是通過追加得方法來完成寫操作
RDB
在指定的時間間隔內將記憶體中的資料集快照寫入到磁碟中
有三種處理方式,一個是在指定的時間內,如果有超過一個key被修改,就發起快照儲存;還有一個方法是,在指定的時間內,如果超過x個key被修改,就自動做快照;還有一個方法就是呼叫slave通知redis做一次快照持久化,但是因為redis是一個主執行緒處理所有的請求,這種方法會阻塞所有的client請求,不推薦使用
處理過程:
-
redis呼叫fork
-
父程序繼續處理client的請求,子程序負責將記憶體中的內容寫入到臨時檔案中,根據寫時複製機制,父子程序會供向物理頁面,當父程序處理寫請求的時候,OS會為父程序要修改的頁面建立副本,子程序fork整個資料庫的一個快照
-
當子程序將快照寫入到臨時檔案完畢之後,用臨時檔案替換掉原來的快照,之後子程序退出
快照持久化是將記憶體中的資料完整的寫入到磁碟,並不只是更新髒資料
優勢:
-
redis資料庫只會包含一個檔案,備份的時候就非常方便
-
在恢復大型資料的時候比aof快
-
在儲存rdb檔案的時候,唯一要做的就是fork一個程序,然後子程序去處理,獨立性比較好
劣勢:
-
rdb進行備份的時間間隔比較大,如果要儘量避免伺服器故障的時候丟失資料,儘量不要使用rdb
-
當資料集比較大的時候,fork可能會非常耗時
AOF
將收到的每一個寫命令通過write寫入到檔案中
有多種處理方式,比如每次收到寫命令就立即強制寫入,或者每秒強制寫入
但是通過記錄命令的方式,也會帶來別的問題,比如呼叫incr test 100次,檔案就必須儲存所有的命令,但是事實上有很多是冗餘的,其實只需要一個命令就夠了
為了解決這個問題,redis提供了bgrewriteaof命令,大體意思就是redis將通過快照的形式將記憶體中的資料寫入到臨時檔案中,然後替換掉原來的檔案。
處理過程:
-
呼叫fork
-
子程序根據記憶體中的快照,往臨時檔案中寫入重建資料庫狀態的命令
-
父程序繼續處理client請求,除了將寫命令寫入到原來的aof中,還把寫命令快取起來
-
當子程序把快照內容通過命令的方式寫入到臨時檔案中後,子程序通知父程序,然後將快取的命令寫入到臨時檔案中
-
父程序用臨時檔案替換掉老的aof檔案
優勢:
-
使用aof使得redis更加的耐用,資料防止丟失方面比rdb好
-
redis可以在aof檔案體積比較大的時候,自動對aof重寫
-
日誌非常容易讀懂,有序的儲存了對資料庫執行的寫入操作
劣勢:
-
對於相同的資料集,aof的體積大於rdb
-
-
mysql組合索引在使用的時候有順序的區別嗎?
沒有區別,sql執行優化器會對語句進行優化,但是sql優化是有一定開銷的,所以,最好按照標準的方法寫
-
最左匹配原則
mysql建立複合索引的規則是對複合索引的最左邊進行排序,在第一個欄位的基礎上,然後再對下一個進行排序
所以,第一個欄位是絕對有序的,但是第二個欄位就是無序的了
-
如何設計100000QPS的系統
從多個方面來考慮:
-
資料庫方面,redis + mysql,讀多還是寫多,建立索引,語句優化
-
分散式方面 master-slave,cluster,sential
-
伺服器方面,一致性hash
-
-
TCP的被動關閉端為什麼不需要類似TIME_WAIT狀態?
time_wait的作用是服務端要確定客戶端收到最後一個ack,ack有一個編號,假設被動端有資料丟失,第二次揮手,客戶端返回的ack,伺服器沒有收到,然後伺服器還是出去fin_wait_1狀態,然後客戶端處於close_wait狀態。然後第三次揮手,客戶端向伺服器端傳送fin,然後沒有收到,因為沒有收到伺服器返回的ack,所以客戶端會一直髮送,然後有一個突然傳送成功了,此時伺服器端可以理解成迅速道道fin_wait_2,然後立刻進入TIME_WAIT狀態
-
設計模式的原則
總原則:開閉原則(對擴充套件開放,對修改關閉)在程式需要擴充套件的時候,不去修改原有的程式碼,而是擴充套件原有程式碼
-
單一職責原則:不要存在多於一個導致類變更的原因,也就是說每個類應該實現單一的原則
-
里氏替換原則:任何基類出現過的地方,子類一定可以出現,只有當派生類可以替換掉基類,軟體單位的功能不受到影響的時候,基類才能夠真正被複用,而派生類也能夠在基類的基礎上增加新的行動
-
依賴倒轉原則:面向介面變成,依賴於抽象而不依賴於具體。寫程式碼用到具體類的時候,不與具體類互動,而是與具體類的上層介面互動
-
介面隔離原則:每個介面中不存在子類用不到卻必須實現的方法,如果不然,就要將介面拆分,使用多個隔離的介面
-
最少知道原則:一個類對自己依賴的類知道的越少越好,也就是說被依賴的類多麼複雜,都應該講邏輯封裝在方法內部,通過public方法提供給外部,這樣當依賴的類變化的時候,才能最小的影響該類
-
合成複用原則:儘量首先使用合成/聚合的方法,而不是使用繼承
-
-
幾種常見的設計模式
-
單例模式
作用:保證一個類只有一個例項,並且提供一個訪問它的全域性訪問點,使得系統中只有唯一的一個物件例項
應用:常用於管理資源,比如日誌,執行緒池
/* 單例模式的作用,就是整個系統中只有一個物件 我們需要考慮兩個部分,一個是隻有一個物件 為了達到這個目的,我們可以將這個物件變成靜態變數,這樣只需要申請一次即可 還有一個部分是,阻止外部構造,外部拷貝構造,外部賦值建構函式 將外部建構函式,外部拷貝建構函式,外部賦值建構函式設定為privatee 細分的話,還有懶漢模式和餓漢模式,餓漢模式就是上述的情況,在類進行定義的時候就進行例項化,它是執行緒安全的。懶漢模式,就是物件的定義是在第一次要使用的時候才進行定義,它是執行緒不安全的,可以通過加鎖的形式來保證執行緒安全 */ // 餓漢模式 #include <bits/stdc++.h> using namespace std; class Singleton { public: static Singleton* GetSingleton(){ return &singleton; } private: static Singleton singleton; Singleton() { cout << "create\n"; } Singleton(Singleton const& singleton); Singleton operator = (Singleton const& singleton); ~Singleton(){}; }; Singleton Singleton::singleton; int main() { cout << "catch\n"; Singleton* t = Singleton::GetSingleton(); return 0; } // 懶漢模式,通過加互斥鎖的形式來保證執行緒安全 //不加鎖 #include <bits/stdc++.h> using namespace std; class Singleton { public: static Singleton& GetSingleton() { static Singleton singleton; return singleton; } void test() { cout << 1 << endl; } void Del() { if(singleton == NULL) { return ; } delete singleton; } private: Singleton(){} Singleton(Singleton const &singleton); Singleton& operator=(Singleton const &singleton); }; int main() { Singleton& a = Singleton::GetSingleton(); a.test(); return 0; } // 加鎖 #include <bits/stdc++.h> using namespace std; class Singleton { public: static pthread_mutex_t mutex; static Singleton* GetSingleton() { if(singleton == NULL) { pthread_mutex_lock(&mutex); singleton = new Singleton(); pthread_mutex_unlock(&mutex); } return singleton; } void Del() { if(singleton == NULL) { return ; } delete singleton; } protected: Singleton(){ cout << "create the Singleton\n"; pthread_mutex_init(&mutex, NULL); } private: static Singleton* singleton; Singleton(Singleton const &singleton); Singleton operator = (Singleton const &singleton); ~Singleton() { cout << "destroy the Singleton\n"; } }; Singleton* Singleton::singleton = NULL; pthread_mutex_t Singleton::mutex; int main() { cout << "get the singleton\n"; Singleton* t = Singleton::GetSingleton(); return 0; } // 做一個懶漢模式和餓漢模式對比吧 // 餓漢模式是在這個程式執行之前就已經建立好了。懶漢模式是在這個程式執行的時候,需要這個物件才會進行建立(加鎖/不加鎖) // 還有一個需要注意的地方就是要注意物件銷燬的銷燬,如果是從棧上申請的,直接解構函式就可以,如果是從堆上申請的,就是需要呼叫delete。單獨弄一個函式就行
-
工廠模式
主要是封裝物件的建立,分離物件的建立和操作過程,用於批量的管理物件的建立過程,便於程式的維護和擴充套件
-
簡單工廠模式
對於不同的產品的建立定義一個工廠類,將產品的型別作為引數傳入到工廠的建立函式,根據型別分支選擇不同的產品建構函式
#include <bits/stdc++.h> using namespace std; typedef enum ProductTypeTag { TypeA, TypeB, TypeC }PRODUCTTYPE; class Product { public: virtual void show() = 0; }; class ProductA : public Product { public: void show() { cout << "I am ProductA" << endl; } }; class ProductB : public Product { public: void show() { cout << "I ams ProductB" << endl; } }; class ProductC : public Product { public: void show() { cout << "I ams ProductC" << endl; } }; class Factory { public: Product* CreateProduct(PRODUCTTYPE type) { switch (type) { case TypeA: return new ProductA(); case TypeB: return new ProductB(); case TypeC: return new ProductC(); default: return NULL; } } }; int main() { Factory productCreator; Product *productA = productCreator.CreateProduct(TypeA); Product *productB = productCreator.CreateProduct(TypeB); productA->show(); return 0; }
-
工廠方法模式
#include <bits/stdc++.h> using namespace std; typedef enum ProductTypeTag { TypeA, TypeB, TypeC }PRODUCTTYPE; class Product { public: virtual void show() = 0; }; class ProductA : public Product { public: void show() { cout << "I am ProductA" << endl; } }; class ProductB : public Product { public: void show() { cout << "I ams ProductB" << endl; } }; class ProductC : public Product { public: void show() { cout << "I ams ProductC" << endl; } }; class Factory { public: virtual Product* createProduct() = 0; }; class FactoryA : public Factory { public: Product* createProduct() { return new ProductA(); } }; class FactoryB : public Factory { public: Product* createProduct() { return new ProductB(); } }; class FactoryC : public Factory { public: Product* createProduct() { return new ProductC(); } }; int main() { Factory* factoryA = new FactoryA(); Product* productA = factoryA->createProduct(); return 0; }
工廠方法模式,就是在簡單工廠模式的基礎上,將簡單工廠模式中的工廠變成一個基類,然後對於每一個產品有一個工廠,方便後期種類的擴充套件
-
抽象工廠模式
#include <bits/stdc++.h> using namespace std; class ProductA { public: virtual void show() = 0; }; class ProductA1 : public ProductA { public: void show(); }; class ProductA2 : public ProductA { public: void show(); }; class ProductB { public: virtual void show() = 0; }; class ProductB1 : public ProductB { public: void show() { cout << "I ams ProductB1" << endl; } }; class Factory { public: virtual ProductA* createProduct() = 0; virtual ProductB *createProduct() = 0; }; class FactoryA : public Factory { public: Product* createProduct() { return new ProductA(); } }; class Factory1 : public Factory { public: productA *createPeoductA() { return new ProductA1(); } productB *createProductB() { return new ProductB!(); } class Factory2 : public Factory { public: productA *createProductA() { return new ProductA2(); } productB *createProductB() { return new ProductB2(); } }; int main() { return 0; }
工廠方法模式適用於結構型別單一的場合,抽象工廠方法適用於產品結構種類多的場合,可以讓低端工廠生產不同型別的低端產品,高階工廠生產不同種類的高階產品
-
-
-
socket通訊原理
TCP/IP是有一個四層結構的,從上到下是 應用層,傳輸層,網路層,鏈路層。而socket是在應用層和傳輸層的一個抽象層,相當於為應用程序提供了一個介面。把複雜度TCP/IP協議族放在socket介面的後面,讓socket去組織資料 然後再就是討論到在網路之中,程序如何程序通訊了。在網路中,**協議 + IP + 埠號**能唯一的標識一個程序。有了這三個因素,我們就可以在網路中定位一個程序了 然後伺服器端的函式呼叫順序是 socekt,bind,listen,accept。socket的作用是建立一個檔案描述符,確定協議族AF_INET,socket型別(SOCK_STREAM, SOCK_DGRAM)。bind函式的作用是繫結本地套接字,為客戶端提供服務。listen的作用是設定一個監聽上限,這個上限指的是排隊佇列的大小。accept函式的作用就是等待客戶端的連線了 對於客戶端的函式的呼叫順序是socket,bind,connect。sonnect的作用就是和伺服器端建立連線。 當我們輸入一個url之後,會先通過三次握手建立連線,然後再就是客戶端向伺服器端傳送一個請求報文,然後伺服器端會返回一個響應報文。然後客戶端再進行資料的分析,之後就是通過四次揮手斷開連線。
-
寫入時複製的思想
如果有多個呼叫者要求相同的資源,他們呢會獲取相同的指標指向相同的資源,直到某一個呼叫者要修改資源的內容的時候,系統會複製一份副本給這個呼叫者,然後其他的呼叫者所見到的資源還會保持不變
-
協程
輕量級執行緒,排程由使用者來控制。擁有自己的暫存器上下文和棧。 允許我們寫著同步程式碼的邏輯,然後做著非同步的事情
-
一根繩子分三段成三角形的概率
通過畫圖的方式來解決,首先把三段繩子算出來,x, y, a - x - y。然後任意兩邊之和大於第三邊,列出三個式子, 然後根據線來畫圖
-
報文的傳輸時間計算思路
首先是速率,就是單位時間內傳輸的資料量。然後再就是時延,就是指的資料從一個網路節點跳到另一個節點所花費的時間。這個時間包括,節點處理時延(驗證是否出錯,確定如何轉發分組),排隊時延,傳輸時延
-
有5 10 25 三種硬幣,給定一個數字N,有多少種組合情況
int cal(int n) { int dp[maxn]; memset(dp, 0, sizeof(dp)); dp[0] = 1; int f[3] = {5, 10, 25}; for(int i = 0; i < 3; i++) { for(int j = f[i]; j <= n; j++) { dp[j] = dp[j] + dp[j - f[i]]; } } return dp[j]; }
所有的組合情況
-
25匹馬賽跑,共有五個賽道,最少賽多少次就可以找出前三名?
首先分成五組進行賽跑,然後獲得每一組的排名,A1, B1, C1, D1, E1。然後每組後兩名淘汰掉,現在還有15個馬,然後五組第一名再進行一次,然後D,E全部被淘汰。然後還剩下9個馬,然後將B3,C2,C3淘汰掉,然後A2, A3, B1,B2,C1再比一次。選出前兩名
一共是5 + 1 + 1 = 7
-
查詢一個表 name欄位中出現次數最多的top3
select name, count(*) as count from 表名 group by name order by count desc limit 3;
-
棧和佇列的區別
-
佇列是先進先出,棧是先進後出
-
操作的物件也不同,棧操作的是棧頂,而佇列的話,可以在隊尾入隊,隊頭出隊
-
遍歷資料的速度不同,如果說想要從頭進行遍歷的話,棧需要遍歷整個棧最後才能取出來,並且還需要額外的空間進行儲存。如果是佇列的話,可以從頭部開始遍歷
-
-
作業系統中常見的幾種程序排程演算法
-
先來先去
-
短作業優先
-
優先順序排程演算法
-
輪轉排程演算法
-
多級反饋佇列
-
-
c++行內函數
https://www.cnblogs.com/chenwx-jay/p/inline_cpp.html
是c++的一種特殊函式,可以像函式一樣被呼叫,但是在呼叫的時候,並不像函式的呼叫機制一樣,沒有函式引數的壓棧,減少了呼叫的開銷。然後行內函數是通過直接將函式插入到呼叫處來實現的
優點:
-
和函式呼叫相比,開銷小
-
編譯器在呼叫一個行內函數的時候,會首先檢查引數型別,如果正確,然後再進行一些列的相關檢查 巨集定義不會檢查引數型別,安全隱患比較大
-
inline函式可以組為一個類的成員函式,和類的普通成員函式作用相同,可以訪問一個類的私有成員和保護成員
缺點:
-
具有一定的侷限性,行內函數的函式體體積一般不能太大,如果體積過大的話,編譯器會放棄內聯的方式,而是採用不用的方式呼叫函式
-
-
什麼函式不能宣告為虛擬函式?
-
普通函式,只能被過載
-
建構函式
-
靜態成員函式,對於每一個類只有一份程式碼
-
行內函數,在編譯的時候展開
-
友元函式
-
-
linux socket非阻塞connect方法
如何設定是阻塞模式,connect會阻塞到連線成功或者連線超時(75 S到幾分鐘),如果是非阻塞的話,呼叫connect之後,函式就會立即返回,如果errno返回EINPROGRESS,就代表三次握手仍在繼續,這個時候可以呼叫select檢測非阻塞connect是否完成。如果多次返回0,代表超時。如果大於0,說明檢測到了可讀或者可寫的套接字描述符。
如果select返回的是 > 0 的值,說明檢測到可讀或者可寫的套接字檔案描述符。
當建立連線成功的時候,socket 描述符變成了可寫
socket 描述符變成既可讀又可寫,有兩種可能性,一種是伺服器端傳送過來資料。另一種是連接出錯。可以通過getsockopt來進行判斷,如果獲得的errno是0,就代表第一種情況。如果是ETIMEOUT,ECONNREFUSED等就代表建立連接出現錯誤
另外一種方式是再次呼叫connect,如果返回的errno是EISCONN,就代表已經連線
-
建立socket
-
設定套接字為非阻塞
fcntl
-
connect
-
呼叫select,通過FD_ISSET檢查是否可寫
-
-
socket可讀,可寫的條件
-
接收低水位,讓select返回可讀時套接字接收緩衝區所需的資料量,預設值為1
-
傳送低水位,讓select返回可寫時套接字傳送緩衝區所需的可用空間大小,預設值是2048
socket可讀的條件:
-
socket接收緩衝區的資料大於等於這個socket接收緩衝區的接收低水位
-
這個連線的讀這一半關閉,這樣的socket不會阻塞,並且返回0
-
socket是一個用於監聽的socket,並且已經完成的連線數非0
-
有一個socket有異常錯誤條件等待處理
socket可寫的條件:
-
socket傳送緩衝區的資料大於這個socket的傳送緩衝區的低水位
-
這個連線的寫這一半關閉
-
有一個socket異常錯誤條件等待處理
-
-
EPOLL_ONESHOT
這個EPOLL_ONESHOT的作用就是監聽到一次事件之後,將這個事件對應的檔案描述從監聽集合中移除,讀完之後再次把對應的fd新增到監聽集合中。避免出現多個執行緒處理同一個讀事件
-
new的底層實現
先是分配記憶體,然後再呼叫物件的建構函式。分配記憶體底層是通過malloc實現的
malloc的實現過程:
-
獲取分配區的鎖,防止多執行緒衝突
-
計算出實際需要分配的記憶體大小
-
如果是小於64Byte的話,就會去fast bins上去找,如果有合適的大小的話,分配結束,否則繼續往下走
-
如果是小於512Byte的話,就會去small bins上去找,如果有合適的大小的話,分配結束,否則繼續往下走
-
然後ptmallic會遍歷fast bins中的chunk,相鄰的chunk進行合併,然後連線到unosrted bins中,然後遍歷unsorted bins。如果只有一個合適的,並且大於等待分配的chunk的大小,會進行切割。然後將剩餘的chunk扔回到unsorted bins中,如果有正好合適的,就將這個chunk從unosrted bins中刪除。然後會對unsorted bins中的chunk進行整理,輸入small bins中的放回到small bins,輸入large bins中的放回到large bins中。然後還沒有找到的話,會去tio chunk中尋找
-
如果還沒有的話,主分配區會呼叫sbrk增加top trunk的大小,非主分配區,直接呼叫mmap來進行分配
-
-
volatile關鍵字
使得一個變數在多個執行緒中可見,但是並不能保證多個執行緒產生的競態條件
-
redis和mysql的應用場景
redis是基於記憶體的,讀寫速度快,也可以做持久化。但是記憶體空間有限,當資料量超過記憶體空間的時候,需要進行擴充記憶體,記憶體的成本比較高;
mysql是基於磁碟的,讀寫速度沒有redis那麼快,但是不受空間容量限制,價效比高
-
redis資料型別和應用場景
-
string,是二進位制安全的,也就是說可以包含任何資料
-
hash,儲存,讀取
-
list,構建訊息佇列,檢視最新訊息
-
set,一堆不可重複的組合,求交集,並集,差集
-
zset,排行榜
-
心動網路
-
redis的資料在硬碟上是如何進行儲存的?
rdb和aof
-
lambda表示式的引數如何進行傳遞
-
vector如何設定擴容機制,reverse,resize函式,如果儲存的是類的話,會不會呼叫類的建構函式
-
mysql什麼時候不走索引
-
static_cast如果是強行轉換unique_ptr的話,會不會成功
-
inline函式是在什麼時候進行編譯的