TCP HTTP 詳細內存分析 & time_wait setsockopt & RPC機制

 http://www.kegel.com/c10k.html#nb.edgehtml

 http://www.chinasb.org/archives/2012/11/4954.shtmljava

 

in_addr_t 通常爲32位的unsigned int.web

#define IPV4(a,b,c,d) ((a<<0)|(b<<8)|(c<<16)|(d<<24))數據庫

 unsigned int value=IPV4(127,0,0,1);    //這裏是已逗號分開。編程

in_addr_t ip;windows

memcpy(&ip,&value,sizeof(value));瀏覽器

//最終顯示127.0.0.1安全

printf("the ip value is %s",inet_ntoa(*((struct in_addr*)&ip)));服務器

 

UDP協議:發送進程在發送每一個數據報的時候當即發送出去,並不等待多個數據報堆積在一塊兒以一個較大數據報發送出去,它是記錄型的協議。cookie

TCP協議:發送進程在發送每一個數據報的時候在內核處理過程當中有可能並不當即發送出去,而是會將多個數據報集中在一塊兒以一個較大的數據報來發送,它是字節流的協議。

發送接收方式
1.異步
報文發送和接收是分開的,相互獨立的,互不影響。這種方式又分兩種狀況:

(1)異步雙工:接收和發送在同一個程序中,有兩個不一樣的子進程分別負責發送和接收
(2)異步單工:接收和發送是用兩個不一樣的程序來完成。

2.同步
報文發送和接收是同步進行,既報文發送後等待接收返回報文。同步方式通常須要考慮超時問題,即報文發上去後不能無限等
待,須要設定超時時間,超過該時間發送方再也不等待讀返回報文,直接通知超時返回。

 

http下,基本上就是請求處理,每一個http請求都是同步處理的,處理完成一次性返回結果,沒法主動推送內容給客戶端,只能是客戶端不停的輪詢。因此併發難上去,實時性達不到,性能也有點遜色,可是編碼簡單,上手容易。如今的公司早先一款頁遊,也是基於http的,可是遊戲須要大量實時的消息推送,所以又架了個實時服務器,客戶端socket長鏈接實時服務器,邏輯服務器把內容推送到實時服務器再由實時服務器廣播給flash客戶端,這樣就比較麻煩費勁。作交互性,實時性的遊戲,socket仍是最合適的。

      在socket長鏈接下,客戶端和服務端創建一個長鏈接,服務端能夠隨時把消息內容推送給客戶端,客戶端也能夠隨時發送給服務端,不須要不停的創建斷開鏈接,節省了部分資源,最重要的,實時性獲得很好的實現。缺點是,一臺服務器的總鏈接數是固定的,到達必定數目後就會有瓶頸.

 何時用短鏈接呢?
通常長鏈接用於少數client  to server   的頻繁的通訊,例如:數據庫的鏈接用長鏈接, 若是用短鏈接頻繁的通訊會形成socket錯誤,並且頻繁的socket 建立也是對資源的浪費。
而像WEB網站的http服務通常都用短連接,由於長鏈接對於服務端來講會耗費必定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的鏈接用短鏈接會更省一些資源。

RFC 1945定義了HTTP/1.0版本。其中最著名的就是RFC 2616RFC 2616定義了今天廣泛使用的一個版本——HTTP 1.1HTTP是一個應用層協議,由請求和響應構成,是一個標準的客戶端服務器模型。HTTP是一個無狀態的協議。HTTP協議永遠都是客戶端發起請求,服務器回送響應。

1. HTTP的幾個重要概念

1.1 鏈接:Connection

一個傳輸層的實際環流,它是創建在兩個相互通信的應用程序之間。

http1.1requestreponse頭中都有可能出現一個connection的頭,此header的含義是當clientserver通訊時對於長連接如何進行處理。

http1.1中,clientserver都是默認對方支持長連接的, 若是client使用http1.1協議,但又不但願使用長連接,則須要在header中指明connection的值爲close;若是server方也不想支持長連接,則在response中也須要明確說明connection的值爲close。不論request仍是responseheader中包含了值爲closeconnection,都代表當前正在使用的tcp連接在當天請求處理完畢後會被斷掉。之後client再進行新的請求時就必須建立新的tcp連接了。

1.2 消息:Message

HTTP通信的基本單位,包括一個結構化的八元組序列並經過鏈接傳輸。

1.3 請求方法

       HTTP的請求方法包括以下幾種:

q      GET   q      POST  q      HEAD  q      PUT  q      DELETE    q      OPTIONS   q      TRACE     q      CONNECT 

2.1 使用telnet進行http測試

       Windows下,可以使用命令窗口進行http簡單測試。

       輸入cmd進入命令窗口,在命令行鍵入以下命令後按回車:

telnet www.baidu.com 80

       然後在窗口中按下Ctrl+]後按回車可以讓返回結果回顯。

接着開始發請求消息,例如發送以下請求消息請求baidu的首頁消息,使用的HTTP協議爲HTTP/1.1

GET /index.html HTTP/1.1

 

2.5 經常使用的請求方式

       經常使用的請求方式是GETPOST.

l         GET方式:是以實體的方式獲得由請求URI所指定資源的信息,若是請求URI只是一個數據產生過程,那麼最終要在響應實體中返回的是處理過程的結果所指向的資源,而不是處理過程的描述。

l         POST方式:用來向目的服務器發出請求,要求它接受被附在請求後的實體,並把它看成請求隊列中請求URI所指定資源的附加新子項,Post被設計成用統一的方法實現下列功能:

1:對現有資源的解釋;

2:向電子公告欄、新聞組、郵件列表或相似討論組發信息;

3:提交數據塊;

4:經過附加操做來擴展數據庫 

從上面描述能夠看出,Get是向服務器發索取數據的一種請求;而Post是向服務器提交數據的一種請求,要提交的數據位於信息頭後面的實體中。

GETPOST方法有如下區別:

1   在客戶端,Get方式在經過URL提交數據,數據在URL中能夠看到;POST方式,數據放置在HTML HEADER內提交

2   GET方式提交的數據最多隻能有1024字節,而POST則沒有此限制。

3   安全性問題。正如在(1)中提到,使用 Get 的時候,參數會顯示在地址欄上,而 Post 不會。因此,若是這些數據是中文數據並且是非敏感數據,那麼使用get;若是用戶輸入的數據不是中文字符並且包含敏感數據,那麼仍是使用 post爲好。

4   安全的和冪等的。所謂安全的意味着該操做用於獲取信息而非修改信息。冪等的意味着對同一 URL 的多個請求應該返回一樣的結果。完整的定義並不像看起來那樣嚴格。換句話說,GET 請求通常不該產生反作用。從根本上講,其目標是當用戶打開一個連接時,她能夠確信從自身的角度來看沒有改變資源。好比,新聞站點的頭版不斷更新。雖然第二次請求會返回不一樣的一批新聞,該操做仍然被認爲是安全的和冪等的,由於它老是返回當前的新聞。反之亦然。POST 請求就不那麼輕鬆了。POST 表示可能改變服務器上的資源的請求。仍然以新聞站點爲例,讀者對文章的註解應該經過 POST 請求實現,由於在註解提交以後站點已經不一樣了(比方說文章下面出現一條註解)。

3. 深刻了解篇

3.1 CookieSession

CookieSession都爲了用來保存狀態信息,都是保存客戶端狀態的機制,它們都是爲了解決HTTP無狀態的問題而所作的努力。Session能夠用Cookie來實現,也能夠用URL回寫的機制來實現。用Cookie來實現的Session能夠認爲是對Cookie更高級的應用。

CookieSession有如下明顯的不一樣點:

1Cookie將狀態保存在客戶端,Session將狀態保存在服務器端;

2Cookies是服務器在本地機器上存儲的小段文本並隨每個請求發送至同一個服務器。Cookie最先在RFC2109中實現,後續RFC2965作了加強。網絡服務器用HTTP頭向客戶端發送cookies,在客戶終端,瀏覽器解析這些cookies並將它們保存爲一個本地文件,它會自動將同一服務器的任何請求縛上這些cookiesSession並無在HTTP的協議中定義;

3Session是針對每個用戶的,變量的值保存在服務器上,用一個sessionID來區分是哪一個用戶session變量,這個值是經過用戶的瀏覽器在訪問的時候返回給服務器,當客戶禁用cookie時,這個值也可能設置爲由get來返回給服務器;

4)就安全性來講:當你訪問一個使用session 的站點,同時在本身機子上創建一個cookie,建議在服務器端的SESSION機制更安全些.由於它不會任意讀取客戶存儲的信息。

 

 

 TCP sockect  內存使用狀況

  • 如何標識一個TCP鏈接:系統用一個4四元組來惟一標識一個TCP鏈接:{local ip, local port,remote ip,remote port}。
  • server最大tcp鏈接數:server一般固定在某個本地端口上監聽,等待client的鏈接請求。不考慮地址重用(unix的SO_REUSEADDR選項)的狀況下,即便server端有多個ip,本地監聽端口也是獨佔的,所以server端tcp鏈接4元組中只有remote ip(也就是client ip)和remote port(客戶端port)是可變的,所以最大tcp鏈接爲客戶端ip數×客戶端port數,對IPV4,不考慮ip地址分類等因素,最大tcp鏈接數約爲2的32次方(ip數)×2的16次方(port數),也就是server端單機最大tcp鏈接數約爲2的48次方。

服務端,鏈接達到必定數量,諸如50W時,有些隱藏很深的問題,就不斷的拋出來。 經過查看dmesg命令查看,發現大量TCP: too many of orphaned sockets錯誤,也很正常,下面到了須要調整tcp socket參數的時候了。

第一個須要調整的是tcp_rmem,即TCP讀取緩衝區,單位爲字節,查看默認值

cat /proc/sys/net/ipv4/tcp_rmem 4096 87380 4161536

默認值爲87380bit ≈ 86K,最小爲4096bit=4K,最大值爲4064K。

第二個須要調整的是tcp_wmem,發送緩衝區,單位是字節,默認值

cat /proc/sys/net/ipv4/tcp_wmem 4096 16384 4161536

解釋同上

第三個須要調整的tcp_mem,調整TCP的內存大小,其單位是頁,1頁等於4096字節。系統默認值:

cat /proc/sys/net/ipv4/tcp_mem 932448 1243264 1864896

tcp_mem(3個INTEGER變量):low, pressure, high

  • low:當TCP使用了低於該值的內存頁面數時,TCP不會考慮釋放內存。
  • pressure:當TCP使用了超過該值的內存頁面數量時,TCP試圖穩定其內存使用,進入pressure模式,當內存消耗低於low值時則退出pressure狀態。
  • high:容許全部tcp sockets用於排隊緩衝數據報的頁面量,當內存佔用超過此值,系統拒絕分配socket,後臺日誌輸出「TCP: too many of orphaned sockets」。

通常狀況下這些值是在系統啓動時根據系統內存數量計算獲得的。 根據當前tcp_mem最大內存頁面數是1864896,當內存爲(1864896*4)/1024K=7284.75M時,系統將沒法爲新的socket鏈接分配內存,即TCP鏈接將被拒絕。

實際測試環境中,據觀察大概在99萬個鏈接左右的時候(零頭不算),進程被殺死,觸發out of socket memory錯誤(dmesg命令查看得到)。每個鏈接大體佔用7.5K內存(下面給出計算方式),大體可算的此時內存佔用狀況(990000 * 7.5 / 1024K = 7251M)。

這樣和tcp_mem最大頁面值數量比較吻合,所以此值也須要修改。

三個TCP調整語句爲:

echo "net.ipv4.tcp_mem = 786432 2097152 3145728">> /etc/sysctl.conf echo "net.ipv4.tcp_rmem = 4096 4096 16777216">> /etc/sysctl.conf echo "net.ipv4.tcp_wmem = 4096 4096 16777216">> /etc/sysctl.conf

備註: 爲了節省內存,設置tcp讀、寫緩衝區都爲4K大小,tcp_mem三個值分別爲3G 8G 16G,tcp_rmemtcp_wmem最大值也是16G。

目標達成

 free -m 

top -p 某刻

獲取當前socket鏈接狀態統計信息:

cat /proc/net/sockstat

獲取當前系統打開的文件句柄:

sysctl -a | grep file
 


TIME_WAIT:

tcp創建鏈接須要3個握手,可是關閉鏈接須要4個握手,關閉鏈接後主動關閉的一方會處於time_wait狀態一段時間,這個是能夠設置的,好像windows最少是30s,time_wait狀態的目的如下兩:

1。防止上一次鏈接中的包,迷路後從新出現,影響新鏈接(通過2MSL,上一次鏈接中全部的重複包都會消失)

2。可靠的關閉TCP鏈接
在主動關閉方發送的最後一個 ack(fin) ,有可能丟失,這時被動方會從新發fin, 若是這時主動方處於 CLOSED 狀態 ,就會響應 RST 而不是 ACK。

因此主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED。

 

setsockopt()

int PASCAL FAR setsockopt(SOCKET s, int level, int optname, const char FAR *optval, int optlen);
s:標識一個套接字的描述符。
level:選項定義的層次;目前僅支持SOL_SOCKET和IPPROTO_TCP層次。
optname:需設置的選項。
optval: 指針,指向存放選項值的 緩衝區
optlen:optval 緩衝區長度。
⒈設置調用 closesocket()後,仍可繼續重用該 socket。調用 closesocket()通常不會當即關閉 socket,而經歷TIME_WAIT的過程。
  BOOL bReuseaddr = TRUE;
  setsockopt(s,SOL_SOCKET, SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
⒉ 若是要已經處於鏈接狀態的soket在調用close socket()後強制關閉,不經歷TIME_WAIT的過程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
⒊在 send(),recv()過程當中有時因爲網絡情況等緣由,收發不能預期進行,能夠設置收發時限:
int nNetTimeout = 1000; //1秒
//發送時限
setsockopt( socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收時限
setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
⒋在 send()的時候,返回的是實際發送出去的字節(同步)或發送到 socket 緩衝區的字節(異步);系統默認的狀態發送和接收一次爲8688字節(約爲8.5K);在實際的過程當中若是發送或是接收的數據量比較大,能夠設置 socket 緩衝區,避免 send(),recv()不斷的循環收發:
// 接收 緩衝區
int nRecvBuf = 32 * 1024; //設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//發送 緩衝區
int nSendBuf = 32*1024; //設置爲32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
⒌在發送數據的時,不執行由系統 緩衝區socket緩衝區的拷貝,以提升程序的性能:
int nZero = 0;
setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
⒍在接收數據時,不執行將 socket 緩衝區的內容拷貝到系統緩衝區:
int nZero = 0;
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
⒎通常在發送 UDP數據報的時候,但願該 socket發送的數據具備廣播特性:
BOOL bBroadcast = TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
⒏在client鏈接服務器過程當中,若是處於非阻塞模式下的 socketconnect()的過程當中能夠設置 connect()延時,直到accpet()被調用(此設置只有在非阻塞的過程當中有顯著的做用,在阻塞的 函數調用中做用不大)
BOOL bConditionalAccept = TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
⒐若是在發送數據的過程當中 send()沒有完成,還有數據沒發送,而調用了close socket(),之前通常採起的措施是 shutdown(s,SD_BOTH),可是數據將會丟失。
某些具體程序要求待未發送完的數據發送出去後再關閉 socket,可經過設置讓程序知足要求:
struct linger {
  u_short l_onoff;
  u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff = 1;
//在調用closesocket()時還有數據未發送完,容許等待.
//若m_sLinger.l_onoff=0;則調用closesocket()後強制關閉
m_sLinger.l_linger = 5; //設置等待時間爲5秒
setsockopt(s, SOL_SOCKET, SO_LINGER, (const char*)&m_sLinger, sizeof(linger));

 


 

RPC 容許進程間通訊,機制屏蔽了底層,

簡單來講,當機器 A 上的進程調用機器 B 上的進程時,A 上的調用進程被掛起,而 B 上的被調用進程開始執行。經過參數將信息傳送給被調用方,而後能夠經過被調用方傳回的結果獲得返回。RPC 框架屏蔽了底層傳輸方式(TCP/UDP)、序列化和反序列化(XML/JSON/二進制)等內容,使用框架只須要知道被調用者的地址和接口就能夠了,無須額外地爲這些底層內部編程。 
目前主流的 RPC 框架有以下幾種。

    • Thrift:Facebook 開源的跨語言框架
    • gRPC:Google 基於 HTTP/2 和 Protobuf 的能用框架
    • Avro:Hadoop 的子項目

 

全部這些步驟的效果是,將客戶過程對客戶樁發出的本地調用轉換成對服務器過程的本地調用,而客戶端和服務器都不會意識到有中間步驟的存在。 
這個時候,你可能會想,既然是調用另外一臺機器的服務,使用 RESTful API 也能夠實現啊,爲何要選擇 RPC 呢?咱們能夠從兩個方面對比:

    • 資源粒度。RPC 就像本地方法調用,RESTful API 每一次添加接口均可能須要額外地組織開放接口的數據,這至關於在應用視圖中再寫了一次方法調用,並且它還須要維護開發接口的資源粒度、權限等。
    • 流量消耗。RESTful API 在應用層使用 HTTP 協議,哪怕使用輕型、高效、傳輸效率高的 JSON 也會消耗較大的流量,而 RPC 傳輸既可使用 TCP 也可使用 UDP,並且協議通常使用二制度編碼,大大下降了數據的大小,減小流量消耗。
相關文章
相關標籤/搜索