內容不斷更新,目前包括協議中握手和數據幀的分析

           

          1.1 背景

          1.2 協議概覽

          協議包含兩部分:握手,數據傳輸。

          客戶端的握手如下:
          GET /chat HTTP/1.1
          Host: server.example.com
          Upgrade: websocket
          Connection: Upgrade
          Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
          Origin: http://example.com
          Sec-WebSocket-Protocol: chat, superchat
          Sec-WebSocket-Version: 13

          服務端的握手如下:
          HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
          客戶端和服務端都發送了握手,并且成功,數據傳輸即可開始。
           
          1.3 發起握手
          發起握手是為了兼容基于HTTP的服務端程序,這樣一個端口可以同時處理HTTP客戶端和WebSocket客戶端
          因此WebSocket客戶端握手是一個HTTP Upgrade請求:
          GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
          握手中的域的順序是任意的。
           
          5 數據幀
          5.1 概述
          WebScoket協議中,數據以幀序列的形式傳輸。
          考慮到數據安全性,客戶端向服務器傳輸的數據幀必須進行掩碼處理。服務器若接收到未經過掩碼處理的數據幀,則必須主動關閉連接。
          服務器向客戶端傳輸的數據幀一定不能進行掩碼處理。客戶端若接收到經過掩碼處理的數據幀,則必須主動關閉連接。
          針對上情況,發現錯誤的一方可向對方發送close幀(狀態碼是1002,表示協議錯誤),以關閉連接。
          5.2 幀協議
          WebSocket數據幀結構如下圖所示:
                0                   1                   2                   3       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1      +-+-+-+-+-------+-+-------------+-------------------------------+      |F|R|R|R| opcode|M| Payload len |    Extended payload length    |      |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |      |N|V|V|V|       |S|             |   (if payload len==126/127)   |      | |1|2|3|       |K|             |                               |      +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +      |     Extended payload length continued, if payload len == 127  |      + - - - - - - - - - - - - - - - +-------------------------------+      |                               |Masking-key, if MASK set to 1  |      +-------------------------------+-------------------------------+      | Masking-key (continued)       |          Payload Data         |      +-------------------------------- - - - - - - - - - - - - - - - +      :                     Payload Data continued ...                :      + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +      |                     Payload Data continued ...                |      +---------------------------------------------------------------+
           
          FIN:1位
          表示這是消息的最后一幀(結束幀),一個消息由一個或多個數據幀構成。若消息由一幀構成,起始幀即結束幀。
           
          RSV1,RSV2,RSV3:各1位
          MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
          這里我翻譯不好,大致意思是如果未定義擴展,各位是0;如果定義了擴展,即為非0值。如果接收的幀此處非0,擴展中卻沒有該值的定義,那么關閉連接。
           
          OPCODE:4位
          解釋PayloadData,如果接收到未知的opcode,接收端必須關閉連接。
          0x0表示附加數據幀
          0x1表示文本數據幀
          0x2表示二進制數據幀
          0x3-7暫時無定義,為以后的非控制幀保留
          0x8表示連接關閉
          0x9表示ping
          0xA表示pong
          0xB-F暫時無定義,為以后的控制幀保留
           
          MASK:1位
          用于標識PayloadData是否經過掩碼處理。如果是1,Masking-key域的數據即是掩碼密鑰,用于解碼PayloadData。客戶端發出的數據幀需要進行掩碼處理,所以此位是1。
           
          Payload length:7位,7+16位,7+64位
          PayloadData的長度(以字節為單位)。
          如果其值在0-125,則是payload的真實長度。
          如果值是126,則后面2個字節形成的16位無符號整型數的值是payload的真實長度。注意,網絡字節序,需要轉換。
          如果值是127,則后面8個字節形成的64位無符號整型數的值是payload的真實長度。注意,網絡字節序,需要轉換。
          長度表示遵循一個原則,用最少的字節表示長度(我理解是盡量減少不必要的傳輸)。舉例說,payload真實長度是124,在0-125之間,必須用前7位表示;不允許長度1是126或127,然后長度2是124,這樣違反原則。
          Payload長度是ExtensionData長度與ApplicationData長度之和。ExtensionData長度可能是0,這種情況下,Payload長度即是ApplicationData長度。
           
           
          WebSocket協議規定數據通過幀序列傳輸。
          客戶端必須對其發送到服務器的所有幀進行掩碼處理。
          服務器一旦收到無掩碼幀,將關閉連接。服務器可能發送一個狀態碼是1002(表示協議錯誤)的Close幀。
          而服務器發送客戶端的數據幀不做掩碼處理,一旦客戶端發現經過掩碼處理的幀,將關閉連接。客戶端可能使用狀態碼1002。
           

          消息分片

          分片目的是發送長度未知的消息。如果不分片發送,即一幀,就需要緩存整個消息,計算其長度,構建frame并發送;使用分片的話,可使用一個大小合適的buffer,用消息內容填充buffer,填滿即發送出去。

          分片規則:

          1.一個未分片的消息只有一幀(FIN為1,opcode非0)

          2.一個分片的消息由起始幀(FIN為0,opcode非0),若干(0個或多個)幀(FIN為0,opcode為0),結束幀(FIN為1,opcode為0)。

          3.控制幀可以出現在分片消息中間,但控制幀本身不允許分片。

          4.分片消息必須按次序逐幀發送。

          5.如果未協商擴展的情況下,兩個分片消息的幀之間不允許交錯。

          6.能夠處理存在于分片消息幀之間的控制幀

          7.發送端為非控制消息構建長度任意的分片

          8.client和server兼容接收分片消息與非分片消息

          9.控制幀不允許分片,中間媒介不允許改變分片結構(即為控制幀分片)

          10.如果使用保留位,中間媒介不知道其值表示的含義,那么中間媒介不允許改變消息的分片結構

          11.如果協商擴展,中間媒介不知道,那么中間媒介不允許改變消息的分片結構,同樣地,如果中間媒介不了解一個連接的握手信息,也不允許改變該連接的消息的分片結構

          12.由于上述規則,一個消息的所有分片是同一數據類型(由第一個分片的opcode定義)的數據。因為控制幀不允許分片,所以一個消息的所有分片的數據類型是文本、二進制、opcode保留類型中的一種。

          需要注意的是,如果控制幀不允許夾雜在一個消息的分片之間,延遲會較大,比如說當前正在傳輸一個較大的消息,此時的ping必須等待消息傳輸完成,才能發送出去,會導致較大的延遲。為了避免類似問題,需要允許控制幀夾雜在消息分片之間。

          控制幀

           

           

          根據官方文檔整理,官方文檔參考http://datatracker.ietf.org/doc/rfc6455/?include_text=1

          posted @ 2014-04-19 15:16 小馬歌 閱讀(444) | 評論 (0)編輯 收藏
           
          今天@julyclyde 在微博上問我websocket的細節。但是這個用70個字是無法說清楚的,所以就整理在這里吧。恰好我最近要重構年前寫的websocket的代碼。
          眾所周知,HTTP是一種基于消息(message)的請求(request )/應答(response)協議。當我們在網頁中點擊一條鏈接(或者提交一個表單)的時候,瀏覽器給服務器發一個request message,然后服務器算啊算,答復一條response message。主動發起TCP連接的是client,接受TCP連接的是server。HTTP消息只有兩種:request和response。client只能發送request message,server只能發送response message。一問一答,因此按HTTP協議本身的設計,服務器不能主動的把消息推給客戶端。而像微博、網頁聊天、網頁游戲等都需要服務器主動給客戶端推東西,現在只能用long polling等方式模擬,很不方便。
           
          OK,來看看internet的另一邊,網絡游戲是怎么工作的?
          我之前在一個游戲公司工作。我們做游戲的時候,普遍采用的模式是雙向、異步消息模式。
          首先通信的最基本單元是message。(這點和HTTP一樣)
          其次,是雙向的。client和server都可以給對方發消息(這點和HTTP不一樣)
          最后,消息是異步的。我給服務器發一條消息出去,然后可能有一條答復,也可能有多條答復,也可能根本沒有答復。無論如何,調用完send方法我就不管了,我不會傻乎乎的在這里等答復。服務器和客戶端都會有一個線程專門負責read,以及一個大大的switch… case,根據message id做相應的action。
          while( msg=myconnection.readMessage()){
          switch(msg.id()){
          case LOGIN: do_login(); break;
          case TALK: do_talk(); break;
          }
          }
          Websocket就是把這樣一種模式,搬入到HTTP/WEB的框架內。它主要解決兩個問題:
          從服務器給客戶端主動推東西。
          HTTP協議傳輸效率低下的問題。這一點在web service中尤為突出。每個請求和應答都得帶上很長的http header!
          websocket協議在RFC 6455中定義,這個RFC在上個月(2011年12月)才終于定稿、提交。所以目前沒有任何一個瀏覽器是能完全符合這個RFC的最終版的。Google是websocket協議的主力支持者,目前主流的瀏覽器中,對websocket支持最好的就是chrome。chrome目前的最新版本是16,支持的是RFC 6455的draft 13,http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13 。IE9則是完全不支持websocket。而server端,只有jetty和Node.js對websocket的支持比較好。
           
          Websocket協議可以分為兩個階段,一個是握手階段,一個是數據傳輸階段。
          在建立TCP連接之后,首先是websocket層的握手。這階段很簡單,client給server發一個http request,server給client一個http response。這個階段,所有數據傳輸都是基于文本的,和現有的HTTP/1.1協議保持兼容。
          這是一個請求的例子:
          Connection:Upgrade
          Host:echo.websocket.org
          Origin:http://websocket.org
          Sec-WebSocket-Key:ov0xgaSDKDbFH7uZ1o+nSw==
          Sec-WebSocket-Version:13
          Upgrade:websocket
           
          (其中Host和Origin不是必須的)
          Connection是HTTP/1.1中常用的一個header,以前經常填的是keepalive或close。這里填的是Upgrade。在設計HTTP/1.1的時候,委員們就想到一個問題,假如以后出HTTP 2.0了,那么現有的這套東西怎么辦呢?所以HTTP協議中就預定義了一個header叫Upgrade。如果客戶端發來的請求中有這個,那么意思就是說,我支持某某協議,并且我更偏向于用這個協議,你看你是不是也支持?你要是支持,咱們就換新協議吧!
          然后就是websocket協議中所定義的兩個特殊的header,Sec-WebSocket-Key和Sec-WebSocket-Version。
          其中Sec-WebSocket-Key是客戶端生的一串隨機數,然后base64之后填進去的。Sec-WebSocket-Version是指協議的版本號。這里的13,代表draft 13。下面給出,我年前寫的發送握手請求的JAVA代碼:
          // 生一個隨機字符串,作為Sec-WebSocket-Key
          ?View Code JAVA
                  byte[] nonce = new byte[16];        rand.nextBytes(nonce);        BASE64Encoder encode = new BASE64Encoder();        String chan = encode.encode(nonce);         HttpRequest request = new BasicHttpRequest("GET", "/someurl");        request.addHeader("Host", host);        request.addHeader("Upgrade", "websocket");        request.addHeader("Connection", "Upgrade");        request.addHeader("Sec-WebSocket-Key", chan); // 生的隨機串         request.addHeader("Sec-WebSocket-Version", "13");        HttpResponse response;        try {            conn.sendRequestHeader(request);            conn.flush();            request.toString();            response = conn.receiveResponseHeader();        } catch (HttpException ex) {            throw new RuntimeException("handshake fail", ex);        }
           
          服務器在收到握手請求之后需要做相應的答復。消息的例子如下:
          HTTP/1.1 101 Switching Protocols
          Connection:Upgrade
          Date:Sun, 29 Jan 2012 18:05:49 GMT
          Sec-WebSocket-Accept:7vI97qQ5QRxq6lD6E5RRX36mOBc=
          Server:jetty
          Upgrade:websocket
          (其中Date、Server都不是必須的)
          第一行是HTTP的Status-Line。注意,這里的Status Code是101。很少見吧!Sec-WebSocket-Accept字段是一個根據client發來的Sec-WebSocket-Key得到的計算結果。
          算法為:
          把客戶端發來的key作為字符串,與” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11″這個字符串連接起來,然后做sha1 Hash,將計算結果base64編碼。注意,用來和” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11″做連接操作的字符串,是base64編碼后的。也就是說,客戶端發來什么樣就是什么樣,不要試圖去做base64解碼。
          示例代碼如下:
          ?View Code CPP
              std::string value=request.get("Sec-WebSocket-Key");    value+="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";    unsigned char hash[20];    sha1::calc(value.c_str(),value.length(),hash);    std::string res=base64_encode(hash,sizeof(hash));    std::ostringstream oss;    oss<<"HTTP/1.1 101 Switching Protocols\r\n"        "Upgrade: websocket\r\n"        "Connection: Upgrade\r\n"        "Sec-WebSocket-Accept: "<<res<<"\r\n";                   connection.send(oss.str());
          握手成功后,進入數據流階段。這個階段就和http協議沒什么關系了。是在TCP流的基礎上,把數據分成frame而已。首先,websocket的一個message,可以被分成多個frame。從邏輯上來看,frame的格式如下
          isFinal
          opcode
          isMasked
          Data length
          Mask key
          Data(可以是文本,也可以是二進制)
           
          isFinal:
          每個frame的第一個字節的最高位,代表這個frame是不是該message的最后一個frame。1代表是最后一個,0代表后面還有。
          opcode:
          指明這個frame的類型。目前定義了這么幾類continuation、text 、binary 、connection close、ping、pong。對于應用而言,最關心的就是,這個message是binary的呢,還是text的?因為html5中,對于這兩種message的接口有細微不一樣。
          isMasked:
          客戶端發給服務器的消息,要求加擾之后發送。加擾的方式是:客戶端生一個32位整數(mask key),然后把data和這32位整數做異或。
          mask key:
          前面已經說過了,就是用來做異或的隨機數。
          Data:
          這才是我們真正要傳輸的數據啊!!
          發送frame時加擾的代碼如下:
                  java.util.Random rand ;
                  ByteBuffer buffer;
                  byte[] dataToSend;
                  …
                  
                  byte[] mask = new byte[4];
                  rand.nextBytes(mask);
                  buffer.put(mask);
                  int oldpos = buffer.position();        
                  buffer.put(data);
                  int newpos = buffer.position();
                  // 按位異或
                  for (int i = oldpos; i != newpos; ++i) {
                      int maskIndex = (i – oldpos) % mask.length;
                      buffer.put(i, (byte) (buffer.get(i) ^ (byte) mask[maskIndex]));
                  }
          下面討論一下這個協議的某些設計:
          為什么要做這個異或操作呢?
          說來話長。首先從Connection:Upgrade這個header講起。本來它是留給TLS用的。就是,假如我從80端口去連接一個服務器,然后通過發送Connection:Upgrade,仿照上面所說的流程,把http協議”升級”成https協議。但是實際上根本沒人這么用。你要用https,你就去連接443。我80端口根本不處理https。由于這個header只是出現在rfc中,并未實際使用,于是大多數cache server看不懂這個header。這樣的結果是,cache server可能以為后面的傳輸數據依然是普通的http協議,然后按照原來的規則做cache。那么,如果這個client和server都已經被黑客很好的操控,他就可以往這個cache server上投毒。比如,從client發送一個websocket frame,但是偽裝成普通的http GET請求,指向一個JS文件。但是這個GET請求的目的地未必是之前那個websocket server,可能是另外一臺web server。然后他再去操控這個web server,做好配合,給一個看起來像http response的答復(實際是websocket frame),里面放的是被修改過的js文件。然后cache server就會把這個js文件錯誤的緩存下來,以后發給其他人。
          首先,client是誰?是瀏覽器。它在一個不很安全的環境中,很容易受到XSS或者流氓插件的攻擊。假如我們的頁面遭到了xss,使得攻擊者可以利用JS從受害者的頁面上發送任意字符串給服務器,如果沒有這個異或操作,那么他就可以控制什么樣的二進制數據出現在信道上,從而實現上述攻擊。但是我還是覺得有點問題。proxy server一般都會對目的地做嚴格的限制,比如,sina的squid肯定不會幫new.163.com做cache。那么既然你已經控制了一個web server,為什么不讓js直接這么做呢?那篇paper的名字叫《Talking to Yourself for Fun and Pro?t》,有空我繼續看。貌似是中國人寫的。
          還有,為什么要把message分成frame呢? 因為HTTP協議有chunk功能,可以讓服務器一邊生數據,一邊發。而websocket協議也考慮到了這點。如果沒有framing功能,那么我必須知道整個message的長度之后,才能開始發送message的data。
          posted @ 2014-04-19 15:00 小馬歌 閱讀(415) | 評論 (0)編輯 收藏
           

          C/C++並沒有提供內建的int轉string函數,這裡提供幾個方式達到這個需求。

          1.若用C語言,且想將int轉char *,可用sprintf(),sprintf()可用類似printf()參數轉型。

           1/* 
           2(C) OOMusou 2007 http://oomusou.cnblogs.com
           3
           4Filename    : int2str_sprintf.cpp
           5Compiler    : Visual C++ 8.0 / ANSI C
           6Description : Demo the how to convert int to const char *
           7Release     : 01/06/2007 1.0
           8*/

           9#include "stdio.h"
          10
          11void int2str(int , char *);
          12
          13int main() {
          14  int i = 123;
          15  char s[64];
          16  int2str(i, s);
          17  puts(s);
          18}

          19
          20void int2str(int i, char *s) {
          21  sprintf(s,"%d",i);
          22}


          2.若用C語言,還有另外一個寫法,使用_itoa(),Microsoft將這個function擴充成好幾個版本,可參考MSDN Library。

           1/* 
           2(C) OOMusou 2007 http://oomusou.cnblogs.com
           3
           4Filename    : int2str_itoa.cpp
           5Compiler    : Visual C++ 8.0 / ANSI C
           6Description : Demo the how to convert int to const char *
           7Release     : 01/06/2007 1.0
           8*/

           9#include "stdio.h"  // puts()
          10#include "stdlib.h" // _itoa()
          11
          12void int2str(int , char *);
          13
          14int main() {
          15  int i = 123;
          16  char s[64];
          17  int2str(i, s);
          18  puts(s);
          19}

          20
          21void int2str(int i, char *s) {
          22  _itoa(i, s, 10);
          23}


          3.若用C++,stringstream是個很好用的東西,stringstream無論是<<或>>,都會自動轉型,要做各型別間的轉換,stringstream是個很好的媒介。

           1/* 
           2(C) OOMusou 2007 http://oomusou.cnblogs.com
           3
           4Filename    : int2str_sstream.cpp
           5Compiler    : Visual C++ 8.0 / ISO C++
           6Description : Demo the how to convert int to string
           7Release     : 01/06/2007 1.0
           8*/

           9
          10#include <iostream>
          11#include <string>
          12#include <sstream>
          13
          14using namespace std;
          15
          16string int2str(int &);
          17
          18int main(void{
          19  int i = 123;
          20  string s;
          21  s = int2str(i);
          22
          23  cout << s << endl;
          24}

          25
          26string int2str(int &i) {
          27  string s;
          28  stringstream ss(s);
          29  ss << i;
          30
          31  return ss.str();
          32}


          4.若用C++,據稱boost有更好的方法,不過我還沒有裝boost,所以無從測試

          posted @ 2014-04-11 11:50 小馬歌 閱讀(437) | 評論 (0)編輯 收藏
           

          在網絡程序的開發中,免不了會涉及到服務器與客戶端之間的協議交互,由于客戶端與服務器端的平臺的差異性(有可能是windows,android,linux等等),以及網絡字節序等問題,通信包一般會做序列化與反序列化的處理,也就是通常說的打包解包工作。google的protobuf是一個很棒的東西,它不僅提供了多平臺的支持,而且直接支持從配置文件生成代碼。但是這么強大的功能,意味著它的代碼量以及編譯生成的庫文件等等都不會小,如果相對于手機游戲一兩M的安裝包來說,這個顯然有點太重量級了(ps:查了一下protobuf2.4.1的jar的包大小有400k左右),而且在手機游戲客戶端與服務器的交互過程中,很多時候基本的打包解包功能就足夠了。

          今天經同事推薦,查閱了一下msgpack相關的資料。msgpack提供快速的打包解包功能,官網上給出的圖號稱比protobuf快4倍,可以說相當高效了。最大的好處在與msgpack支持多語言,服務器端可以用C++,android客戶端可以用java,能滿足實際需求。

          在實際安裝msgpack的過程中,碰到了一點小問題,因為開發機器是32位機器,i686的,所以安裝完后跑一個簡單的sample時,c++編譯報錯,錯誤的表現為鏈接時報錯:undefined reference to `__sync_sub_and_fetch_4',后來參考了下面的博客,在configure時指定CFLAGS="-march=i686"解決,注意要make clean先。

          msgpack官網地址:http://msgpack.org/

          安裝過程中參考的博客地址:http://blog.csdn.net/xiarendeniao/article/details/6801338



          ====================================================================================

          ====================================================================================

          補上近期簡單的測試結果

          測試機器,cpu 4核 Dual-Core AMD Opteron(tm) Processor 2212,單核2GHz,1024KB cache, 內存4G。


          1. 簡單包測試

          1. struct ProtoHead  
          2. {  
          3.     uint16_t uCmd;      // 命令字  
          4.     uint16_t uBodyLen;  // 包體長度(打包之后的字符串長度)  
          5.     uint32_t uSeq;      //消息的序列號,唯一標識一個請求  
          6.     uint32_t uCrcVal;           // 對包體的crc32校驗值(如果校驗不正確,服務器會斷開連接)  
          7. };  
          8.   
          9. struct ProtoBody  
          10. {  
          11.     int num;  
          12.     std::string str;  
          13.     std::vector<uint64_t> uinlst;  
          14.     MSGPACK_DEFINE(num, str, uinlst);  
          15. };  

          測試中省略了包頭本地字節序與網絡字節序之間的轉化,只有包體做msgpack打包處理.

          vector數組中元素數量為16個,每次循環做一次打包與解包,并驗證前后數據是否一致,得到的測試結果如下:

          總耗時(s)

          循環次數

          平均每次耗時(ms)

          0.004691

          100

          0.04691

          0.044219

          1000

          0.044219

          0.435725

          10000

          0.043573

          4.473818

          100000

          0.044738

          總結:基本每次耗時0.045ms左右,每秒可以打包解包22k次,速度很理想。


          2. 復雜包測試(vector嵌套)

          1. struct test_node  
          2. {  
          3.     std::string str;  
          4.     std::vector<uint32_t> idlst;  
          5.   
          6.     test_node()  
          7.     {  
          8.         str = "it's a test node";  
          9.   
          10.         for (int i = 0; i++; i < 10)  
          11.         {  
          12.             idlst.push_back(i);  
          13.         }  
          14.     }  
          15.   
          16.     bool operator == (const test_node& node) const  
          17.     {  
          18.         if (node.str != str)  
          19.         {  
          20.             return false;  
          21.         }  
          22.   
          23.         if (node.idlst.size() != idlst.size())  
          24.         {  
          25.             return false;  
          26.         }  
          27.   
          28.         for (int i = 0; i < idlst.size(); i++)  
          29.         {  
          30.             if (idlst[i] != node.idlst[i])  
          31.             {  
          32.                 return false;  
          33.             }  
          34.         }  
          35.         return true;  
          36.     }  
          37.   
          38.     MSGPACK_DEFINE(str, idlst);  
          39. };  
          40.   
          41. struct ProtoBody  
          42. {  
          43.     int num;  
          44.     std::string str;  
          45.     std::vector<uint64_t> uinlst;  
          46.     std::vector<test_node> nodelst;  
          47.   
          48.     MSGPACK_DEFINE(num, str, uinlst, nodelst);  
          49. };  
          每個nodelst中插入16個node,每個node中的idlst插入16個id,同1中的測試方法,得到測試結果如下:

          總耗時(s)

          循環次數

          平均每次耗時(ms)

          0.025401

          100

          0.25401

          0.248396

          1000

          0.248396

          2.533385

          10000

          0.253339

          25.823562

          100000

          0.258236

          基本上每次打包解包一次要耗時0.25ms,每秒估算可以做4k次打包解包,速度還是不錯的。


          3. 加上crc校驗

          如果每個循環中,打包過程加上crc的計算,解包過程中加上crc校驗,得到測試結果如下:

          總耗時(s)

          循環次數

          平均每次耗時(ms)

          0.025900

          100

          0.25900

          0.260424

          1000

          0.260424

          2.649585

          10000

          0.264959

          26.855452

          100000

          0.268555

          基本上每次打包解包耗時0.26ms左右,與沒有crc差別不大;

          posted @ 2014-04-10 13:43 小馬歌 閱讀(570) | 評論 (0)編輯 收藏
           

          MessagePack(以下簡稱MsgPack)一個基于二進制高效的對象序列化類庫,可用于跨語言通信。它可以像JSON那樣,在許多種語言之間交換結構對象;但是它比JSON更快速也更輕巧。支持Python、Ruby、Java、C/C++等眾多語言。比Google Protocol Buffers還要快4倍。

          代碼:
          > require ‘msgpack’
          > msg = [1,2,3].to_msgpack  #=> “\x93\x01\x02\x03″
          > MessagePack.unpack(msg)   #=> [1,2,3]

          以上摘自oschina介紹。

          msgpack官方主頁:http://msgpack.org/

          github主頁:https://github.com/msgpack/msgpack

          因我只使用C++版本,故只下載了CPP部分,大家請按需下載。

          源碼安裝msgpack

          打開終端下載msgpac 4 cpp最新版本0.5.7

          wget http://msgpack.org/releases/cpp/msgpack-0.5.7.tar.gz

          解壓

          tar zxvf msgpack-0.5.7.tar.gz

          進入解壓后的文件夾中進行安裝

          cd msgpack-0.5.7 ./configure make sudo make install

          當然了,你也可以使用git和svn直接抓取源代碼進行編譯,不過需要安裝版本控制工具。

          自動安裝msgpack
          apt-get install libmsgpack-dev

          (安裝過程中會將頭文件拷貝到 /usr/local/include/ 庫文件拷貝到/usr/local/lib/)

          安裝好了,我們直接使用用它看看效果。

          直接包含msgpack.hpp即可使用。

          simple using
          #include <msgpack.hpp> #include <vector> #include <string> #include <iostream>  int main() { 	std::vector<std::string> _vecString; 	_vecString.push_back("Hello"); 	_vecString.push_back("world");  	// pack 	msgpack::sbuffer _sbuffer; 	msgpack::pack(_sbuffer, _vecString); 	std::cout << _sbuffer.data() << std::endl;  	// unpack 	msgpack::unpacked msg; 	msgpack::unpack(&msg, _sbuffer.data(), _sbuffer.size()); 	msgpack::object obj = msg.get(); 	std::cout << obj << std::endl;  	// convert 	std::vector<std::string> _vecRString; 	obj.convert(&_vecRString);  	// print 	for(size_t i = 0; i < _vecRString.size(); ++i) 	{ 		std::cout << _vecRString[i] << std::endl; 	}      return 0; }

          結果就不貼了,大家自己運行下便知。

          using stream
          #include <msgpack.hpp> #include <vector> #include <string> #include <iostream>  int main() { 	// msgpack stream  	// use msgpack::packer to pack multiple objects. 	msgpack::sbuffer buffer_; 	msgpack::packer pack_(&buffer_); 	pack_.pack(std::string("this is 1st string")); 	pack_.pack(std::string("this is 2nd string")); 	pack_.pack(std::string("this is 3th string"));  	// use msgpack::unpacker to unpack multiple objects. 	msgpack::unpacker unpack_; 	unpack_.reserve_buffer(buffer_.size()); 	memcpy(unpack_.buffer(), buffer_.data(), buffer_.size()); 	unpack_.buffer_consumed(buffer_.size());  	msgpack::unpacked result_; 	while (unpack_.next(&result_)) 	{ 		std::cout << result_.get() << std::endl; 	}  	return 0; }

          使用sbuffer stream序列化多個對象。

          如何序列化自定義數據結構

          msgpack支持序列化/反序列化自定義數據結構,只需要簡單的使用MSGPACK_DEFINE宏即可。

          ##include <msgpack.hpp> #include <vector> #include <string>  class my_class { private: 	std::string my_string; 	std::vector vec_int; 	std::vector vec_string; public: 	MSGPACK_DEFINE(my_string, vec_int, vec_string); };  int main() { 	std::vector<my_class> my_class_vec;  	// add some data  	msgpack::sbuffer buffer; 	msgpack::pack(buffer, my_class_vec);  	msgpack::unpacked msg; 	msgpack::unpack(&msg, buffer.data(), buffer.size());  	msgpack::object obj = msg.get(); 	std::vector<my_class> my_class_vec_r; 	obj.convert(&my_class_vec_r);  	return 0; }

          這樣我們就可以在網絡通訊等地方可以使用msgpack來序列化我們的數據結構,完全可以做到安全高效,并且可以在接收方使用別的語言來處理結構做邏輯。完全是 多種語言-多種語言,現在支持的語言如下:

          Ruby Perl Python C/C++ Java PHP JS OC C# Lua Scala D Haskell Erlang Ocaml Smallalk GO LabVIEW

          完全夠我們使用了,當然了,如果沒有你要的語言,建議看源代碼模仿一個。

          關于性能測試結果可以查看:linux使用msgpack及測試 

          posted @ 2014-04-10 13:42 小馬歌 閱讀(746) | 評論 (0)編輯 收藏
           
          功能特點:
          可以導出導入任意大小的數據庫。FaisunSQL 采用分卷導出的方式,將數據庫分為多個部份多次導出,因此理論上無論多大的數據庫,它都可以勝任。 
          導出的文件本身可以在PHP環境下執行,因此不需要借助其他工具(也不再需要 FaisunSQL程序)。導出的文件為完整的 PHP 文件,直接在服務器中執行即可,使用方便。 
          雖然為多頁導出和導入,但其過程會自動運行,且執行速度較快,成功率高。 
          程序編寫時考慮了程序的可整合性,因此只要略加修改即可整合到其他程序的后臺。 
          導出方式、每個數據文件的大小和數據表等都可以進行設置,個性化強。 
          程序對數據進行了一定的壓縮,減少了備份文件的空間占用。 
          對導出的程序進行了加密,安全性高。 
          導出和導入時基本上按照默認的配置即可,使用方便快捷。 
          posted @ 2014-04-01 15:50 小馬歌 閱讀(238) | 評論 (0)編輯 收藏
           

          說起項目構建工具,Linux 用戶最熟悉的恐怕就是 Autotools,它將編譯安裝這個步驟大大簡化。但對于項目作者來說,想要使用 Autotools 生成有效的配置文件著實需要下一番功夫,用現在流行的話來說就是用戶體驗不夠友好。對 Unix shell 的依賴,也使得 Autotools 天生對于跨平臺支持不佳。

          后來我從大貓同學那里聽說了 CMake,CMake 使用 C++ 編寫,原生支持跨平臺,不需要像 Autotools 那樣寫一堆的配置文件,只需一個 CMakeLists.txt 文件即可。簡潔的使用方式,強大的功能使得我立馬對 CMake 情有獨鐘。在后來的使用過程中,雖然會遇到一些因為使用習慣帶來的小困擾,但我對于 CMake 還是基本滿意的。直到我發現了 GYP。

          GYP(Generate Your Projects)是由 Chromium 團隊開發的跨平臺自動化項目構建工具,Chromium 便是通過 GYP 進行項目構建管理。為什么我要選擇 GYP,而放棄 CMake 呢?功能上 GYP 和 CMake 很是相似,在我看來,它們的最大區別在于配置文件的編寫方式和其中蘊含的思想。

          編寫 CMake 配置文件相比 Autotools 來說已經簡化很多,一個最簡單的配置文件只需要寫上源文件及生成類型(可執行文件、靜態庫、動態庫等)即可。對分支語句和循環語句的支持也使得 CMake 更加靈活。但是,CMake 最大的問題也是在這個配置文件,請看下面這個示例文件:

          1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 
          cmake_minimum_required(VERSION 2.8) project(VP8 CXX)  add_definitions(-Wall) cmake_policy(SET CMP0015 NEW) include_directories("include") link_directories("lib") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "../lib") set(VP8SRC VP8Encoder.cpp VP8Decoder.cpp)  if(X86)     set(CMAKE_SYSTEM_NAME Darwin)     set(CMAKE_SYSTEM_PROCESSOR i386)     set(CMAKE_OSX_ARCHITECTURES "i386")      add_library(vp8 STATIC ${VP8SRC}) elseif(IPHONE)     if(SIMULATOR)         set(PLATFORM "iPhoneSimulator")         set(PROCESSOR i386)         set(ARCH "i386")     else()         set(PLATFORM "iPhoneOS")         set(PROCESSOR arm)         set(ARCH "armv7")     endif()      set(SDKVER "4.0")     set(DEVROOT "/Developer/Platforms/${PLATFORM}.platform/Developer")     set(SDKROOT "${DEVROOT}/SDKs/${PLATFORM}${SDKVER}.sdk")     set(CMAKE_OSX_SYSROOT "${SDKROOT}")     set(CMAKE_SYSTEM_NAME Generic)     set(CMAKE_SYSTEM_PROCESSOR ${PROCESSOR})     set(CMAKE_CXX_COMPILER "${DEVROOT}/usr/bin/g++")     set(CMAKE_OSX_ARCHITECTURES ${ARCH})      include_directories(SYSTEM "${SDKROOT}/usr/include")     link_directories(SYSTEM "${SDKROOT}/usr/lib")      add_definitions(-D_PHONE)     add_library(vp8-armv7-darwin STATIC ${VP8SRC}) endif() 

          你能一眼看出這個配置文件干了什么嗎?其實這個配置文件想要產生的目標(target)只有一個,就是通過${VP8SRC} 編譯生成的靜態庫,但因為加上了條件判斷,及各種平臺相關配置,使得這個配置文件看起來很是復雜。在我看來,編寫 CMake 配置文件是一種線性思維,對于同一個目標的配置可能會零散分布在各個地方。而 GYP 則相當不同,GYP 的配置文件更多地強調模塊化、結構化。看看下面這個示例文件:

          1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 
          {   'targets': [     {       'target_name': 'foo',       'type': '<(library)',       'dependencies': [         'bar',       ],       'defines': [         'DEFINE_FOO',         'DEFINE_A_VALUE=value',       ],       'include_dirs': [         '..',       ],       'sources': [         'file1.cc',         'file2.cc',       ],       'conditions': [         ['OS=="linux"', {           'defines': [             'LINUX_DEFINE',           ],           'include_dirs': [             'include/linux',           ],         }],         ['OS=="win"', {           'defines': [             'WINDOWS_SPECIFIC_DEFINE',           ],         }, { # OS != "win",           'defines': [             'NON_WINDOWS_DEFINE',           ],         }]       ],     }   ], } 

          我們可以立馬看出上面這個配置文件的輸出目標只有一個,也就是 foo,它是一個庫文件(至于是靜態的還是動態的這需要在生成項目時指定),它依賴的目標、宏定義、包含的頭文件路徑、源文件是什么,以及根據不同平臺設定的不同配置等。這種定義配置文件的方式相比 CMake 來說,讓我覺得更加舒服,也更加清晰,特別是當一個輸出目標的配置越來越多時,使用 CMake 來管理可能會愈加混亂。

          配置文件的編寫方式是我區分 GYP 和 CMake 之間最大的不同點,當然 GYP 也有一些小細節值得注意,比如支持跨平臺項目工程文件輸出,Windows 平臺默認是 Visual Studio,Linux 平臺默認是 Makefile,Mac 平臺默認是 Xcode,這個功能 CMake 也同樣支持,只是缺少了 Xcode。Chromium 團隊成員也撰文詳細比較了 GYP 和 CMake 之間的優缺點,在開發 GYP 之前,他們也曾試圖轉到 SCons(這個我沒用過,有經驗的同學可以比較一下),但是失敗了,于是 GYP 就誕生了。

          當然 GYP 也不是沒有缺點,相反,我覺得它的「缺點」一大堆:

          • 文檔不夠完整,項目不夠正式,某些地方還保留著 Chromium 的影子,看起來像是還沒有完全獨立出來。
          • 大量的括號嵌套,很容易讓人看暈,有過 Lisp 使用經驗的同學可以對號入座。對于有括號恐懼癥,或者不使用現代編輯器的同學基本可以繞行。
          • 為了支持跨平臺,有時不得不加入某些特定平臺的配置信息,比如只適用于 Visual Studio 的 RuntimeLibrary配置,這不利于跨平臺配置文件的編寫,也無形中增加了編寫復雜度。
          • 不支持 make clean,唯一的方法就是將輸出目錄整個刪除或者手動刪除其中的某些文件。

          如果你已經打算嘗試 GYP,那一定記得在生成項目工程文件時加上 --depth 參數,譬如:

          $ gyp --depth=. foo.gyp 

          這也是一個從 Chromium 項目遺留下來的歷史問題。

          也許你根本用不上跨平臺特性,但是 GYP 依然值得嘗試。我編寫了一份 GYP 配置文件的模板,有興趣的同學可以參考。GYP 和 CMake 分別代表了兩種迥異的「風格」,至于孰優孰劣,還得仁者見仁,智者見智。

          posted @ 2014-03-18 14:36 小馬歌 閱讀(382) | 評論 (0)編輯 收藏
           

           

          最近需要獲取別人網站上的音樂數據。用了file_get_contents函數,但是總是會遇到獲取失敗的問題,盡管按照手冊中的 例子設置了超時,可多數時候不會奏效:


          $config['context'] = stream_context_create(array(‘http’ => array(‘method’ => “GET”,
             ’timeout’ => 5//這個超時時間不穩定,經常不奏效
             )
            ));

          這時候,看一下服務器的連接池,會發現一堆類似的錯誤,讓我頭疼萬分:
          file_get_contents(http://***): failed to open stream…

          現在改用了curl庫,寫了一個函數替換:
          function curl_file_get_contents($durl){
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $durl);
            curl_setopt($ch, CURLOPT_TIMEOUT, 5);
            curl_setopt($ch, CURLOPT_USERAGENT, _USERAGENT_);
            curl_setopt($ch, CURLOPT_REFERER,_REFERER_);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $r = curl_exec($ch);
            curl_close($ch);
             return $r;
          }

          如此,除了真正的網絡問題外,沒再出現任何問題。
          這是別人做過的關于curl和file_get_contents的測試:
          file_get_contents抓取google.com需用秒數:

           

          2.31319094

          2.30374217
          2.21512604
          3.30553889
          2.30124092

          curl使用的時間:

           

          0.68719101

          0.64675593
          0.64326
          0.81983113
          0.63956594

          差距很大?呵呵,從我使用的經驗來說,這兩個工具不只是速度有差異,穩定性也相差很大。

          建議對網絡數據抓取穩定性要求比較高的朋友使用上面的 curl_file_get_contents函數,不但穩定速度快,還能假冒瀏覽器欺騙目標地址哦!

           

           

           

          看到的其他文章收藏于此===============================

          php fsockopen

          方法1: 用file_get_contents 以get方式獲取內容
          <?php
          $url='http://www.domain.com/';
          $html = file_get_contents($url);
          echo $html;
          ?>

          方法2: 用fopen打開url, 以get方式獲取內容
          <?php
          $fp = fopen($url, 'r');
          stream_get_meta_data($fp);
          while(!feof($fp)) {
          $result .= fgets($fp, 1024);
          }
          echo "url body: $result";
          fclose($fp);
          ?>



          方法3:用file_get_contents函數,以post方式獲取url
          <?php
          $data = array ('foo' => 'bar');
          $data = http_build_query($data);

          $opts = array (
          'http' => array (
          'method' => 'POST',
          'header'=> "Content-type: application/x-www-form-urlencoded\r\n" .
          "Content-Length: " . strlen($data) . "\r\n",
          'content' => $data
          )
          );

          $context = stream_context_create($opts);
          $html = file_get_contents('http://localhost/e/admin/test.html', false, $context);

          echo $html;
          ?>


          方法4:用fsockopen函數打開url,以get方式獲取完整的數據,包括header和body

          <?php
          function get_url ($url,$cookie=false)
          {
          $url = parse_url($url);
          $query = $url[path]."?".$url[query];
          echo "Query:".$query;
          $fp = fsockopen( $url[host], $url[port]?$url[port]:80 , $errno, $errstr, 30);
          if (!$fp) {
          return false;
          } else {
          $request = "GET $query HTTP/1.1\r\n";
          $request .= "Host: $url[host]\r\n";
          $request .= "Connection: Close\r\n";
          if($cookie) $request.="Cookie:   $cookie\n";
          $request.="\r\n";
          fwrite($fp,$request);
          while()) {
          $result .= @fgets($fp, 1024);
          }
          fclose($fp);
          return $result;
          }
          }
          //獲取url的html部分,去掉header
          function GetUrlHTML($url,$cookie=false)
          {
          $rowdata = get_url($url,$cookie);
          if($rowdata)
          {
          $body= stristr($rowdata,"\r\n\r\n");
          $body=substr($body,4,strlen($body));
          return $body;
          }

              return false;
          }
          ?>



          方法5:用fsockopen函數打開url,以POST方式獲取完整的數據,包括header和body

          <?php
          function HTTP_Post($URL,$data,$cookie, $referrer="")
          {

              // parsing the given URL
          $URL_Info=parse_url($URL);

              // Building referrer
          if($referrer=="") // if not given use this script as referrer
          $referrer="111";

              // making string from $data
          foreach($data as $key=>$value)
          $values[]="$key=".urlencode($value);
          $data_string=implode("&",$values);

              // Find out which port is needed - if not given use standard (=80)
          if(!isset($URL_Info["port"]))
          $URL_Info["port"]=80;

              // building POST-request:
          $request.="POST ".$URL_Info["path"]." HTTP/1.1\n";
          $request.="Host: ".$URL_Info["host"]."\n";
          $request.="Referer: $referer\n";
          $request.="Content-type: application/x-www-form-urlencoded\n";
          $request.="Content-length: ".strlen($data_string)."\n";
          $request.="Connection: close\n";

              $request.="Cookie:   $cookie\n";

              $request.="\n";
          $request.=$data_string."\n";

              $fp = fsockopen($URL_Info["host"],$URL_Info["port"]);
          fputs($fp, $request);
          while(!feof($fp)) {
          $result .= fgets($fp, 1024);
          }
          fclose($fp);

              return $result;
          }

          ?>


          方法6:使用curl庫,使用curl庫之前,可能需要查看一下php.ini是否已經打開了curl擴展

          <?php
          $ch = curl_init();
          $timeout = 5;
          curl_setopt ($ch, CURLOPT_URL, 'http://www.domain.com/');
          curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
          curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
          $file_contents = curl_exec($ch);
          curl_close($ch);

          echo $file_contents;
          ?>

           

           

           

           

          php中 curl, fsockopen ,file_get_contents 三個函數 都可以實現采集模擬發言 。 三者有什么區別,或者講究么  

          趙永斌:
          有些時候用file_get_contents()調用外部文件,容易超時報錯。換成curl后就可以.具體原因不清楚
          curl 效率比file_get_contents()和fsockopen()高一些,原因是CURL會自動對DNS信息進行緩存(亮點啊 有我待親測)

          范佳鵬:
          file_get_contents curl fsockopen
          在當前所請求環境下選擇性操作,沒有一概而論:
          具我們公司開發KBI應用來看:
          剛開始采用:file_get_contents
          后來采用:fsockopen
          最后到至今采用:curl

          (遠程)我個人理解到的表述如下(不對請指出,不到位請補充)
          file_get_contents 需要php.ini里開啟allow_url_fopen,請求http時,使用的是http_fopen_wrapper,不會keeplive.curl是可以的。
          file_get_contents()單個執行效率高,返回沒有頭的信息。
          這個是讀取一般文件的時候并沒有什么問題,但是在讀取遠程問題的時候就會出現問題。
          如果是要打一個持續連接,多次請求多個頁面。那么file_get_contents和fopen就會出問題。
          取得的內容也可能會不對。所以做一些類似采集工作的時候,肯定就有問題了。
          sock較底層,配置麻煩,不易操作。 返回完整信息。

          潘少寧-騰訊:

          file_get_contents 雖然可以獲得某URL的內容,但不能post  get啊。
          curl 則可以post和get啊。還可以獲得head信息
          而socket則更底層。可以設置基于UDP或是TCP協議去交互
          file_get_contents 和 curl 能干的,socket都能干。
          socket能干的,curl 就不一定能干了
          file_get_contents  更多的時候 只是去拉取數據。效率比較高  也比較簡單。
          趙的情況這個我也遇到過,我通過CURL設置host 就OK了。  這和網絡環境有關系

          posted @ 2014-02-14 16:44 小馬歌 閱讀(1314) | 評論 (0)編輯 收藏
           

          原文:http://blog.rushcj.com/2010/08/21/try-thrift/

          Thrift是一個非常棒的工具,是Facebook的開源項目,目前的開發非常的活躍,由Apache管理,所以用的是Apache Software License,這非常重要,因為可以放心的對其修改并用到自己的項目中。

          談到修改Thrift,這非常重要。因為我覺得如果要嚴肅的使用Thrift,不可避免的要深入了解它,并幾乎都要修改Thrift的代碼。一個通信框架,它不可能幫你做到所有的事情,也不可能在不了解的情況下就貿然的使用。

          1.Thrift 的Java Server/Client有個較為嚴重的bug(https://issues.apache.org/jira/browse/THRIFT-601 ),隨機向thrift  sever的監聽端口發些數據,可能會導致Server OutOfMemory,細細看看代碼,這個bug有點土。

          2.Thrift Client線程不安全,多線程下使用可能導致Server和客戶端程序崩潰。Client的每次調用遠程方法其實是有多次Socket寫操作,因此每個線程中使用的Client要保證獨立,如果多個線程混用同一個Client(其實是用同一個Socket),可能會導致傳輸的字節順序混亂,使得Server OutOfMemory(參考1)

          3.Thrift定義數據結構時,盡量避免用map, 或者set。在cpp下, map被對應為std::map(rb tree)和std::set,thrift生成的類不會重載”<”,因此需要手動修改生成類,否則link沒法通過。較為麻煩。

          4.如果Client端基于效率考慮,要緩存Socket,需要重新實現其TTransport類,以支持 Socket緩存池。當然,這個實現其實跟thrift沒多大關系,算是2次開發。但一般都要這么做的吧?

          5.如果Client基于效率考慮,緩存了Socket,那么thrift Server端的模式選擇就較為重要了。如果使用同步的TThreadPoolServer,那么無可避免的,客戶端緩存1個Socket,Server端就會有一個線程一直處于Server狀態,等待peek這個Socket上的數據。這個線程就不能用于其它請求了。所以,及時清理Client端的Socket及控制Socket池的大小是非常必要的。

          6.聽同事說CPP Thrift Server的Epoll NonBlocking模式有效率問題。其實,并發要求不高的Server用LT模式的EPoll其實很方便的,當然,這個要自己給Thrfit Server做patch了,不過也不麻煩。開發起來也是很方便的。我想給我們的Server加個EPOLLONESHOT的同步EPoll實現。

          7.CPP下的 TThreadPoolServer和TThreadServer由一個有趣的問題,如果有客戶端維護長連接,那么對這個Server實例做析構的時候會堵塞(前面說過了,在peek中…)。

          8.用valgrind看,thrift cpp似乎有一些內存問題。沒細看。

          9.無論是Java,還是CPP,Server端都無法通過合法的方式獲取Client的ip, port。可以通過編寫ThriftServerEventHandler可以處理這件事情。如果想要獲取Client ip, port的話,可以看看這個東西。

          posted @ 2014-02-14 16:42 小馬歌 閱讀(1588) | 評論 (0)編輯 收藏
           

          nginx upstream keepalive connections

           

          Nginx從 1.1.4 開始,實現了對后端機器的長連接支持,這是一個激動人心的改進,這意味著 Nginx 與后端機器的通信效率更高,后端機器的負擔更低。

           

          例如,對一個沒有長連接支持的后端機器,會出現大量TIME_WAIT 狀態的連接,使用以下命令驗證之:

          netstat -n | grep TIME_WAIT

           

          經過查閱官方文檔,其目前已經實現了http, fastcgi, memcache 協議的長連接支持。而之前的版本中僅支持memcache 協議。

           

          1. 啟用到 memcache 服務器的長連接 
          在upstream 配置段中增加 keepalive N 指令即可: 
          upstream memcached_backend {

              server 127.0.0.1:11211;

              server 10.0.0.2:11211;

               keepalive 32;

          }

           

          server {

              ...

              location /memcached/ {

                  set $memcached_key $uri;

                  memcached_pass memcached_backend;

              }

          }

           

           

          2.  啟用fastcgi 長連接支持 
          除了需要在upstream 中配置 keepalive N 外,還需要在 location 中增加 fastcgi_keep_conn on;

          upstream fastcgi_backend {

              server 127.0.0.1:9000;

               keepalive 8;

          }

           

          server {

              ...

              location /fastcgi/ {

                  fastcgi_pass fastcgi_backend;

                   fastcgi_keep_conn on;

                  ...

              }

          }

           

          3.  啟用對后端機器HTTP 長連接支持

          upstream http_backend {

              server 127.0.0.1:8080;

              keepalive 16;

          }

           

          server {

              ...

              location /http/ {

                  proxy_pass http://http_backend;

                  proxy_http_version 1.1;

                  proxy_set_header Connection "";

                  ...

              }

          }

           

          注意:需要設置nginx 代理請求的 http 協議版本號為 1.1,  以及清除掉 Connection 請求header,  官方文檔描述:

          For HTTP, the proxy_http_version directive should be set to “ 1.1 ”  and the  “Connection ”  header field should be cleared .

           

          The connections parameter should be set low enough to allow upstream servers to process additional new incoming connections as well. 

           

          即是說:keepalive N 指令中 , N 的值應該盡可能設置小一些,以便后端機器可以同時接受新的連接。

           

          在我負責的生產環境中,前端是nginx,  靜態文件緩存使用 varnish,  使用長連接之后, varnish機器的連接數從 8000 多下降至 200 多,負載值也有明顯降低。

           

          但是針對fastcgi,  即后端機器是 php-fpm 服務時,在 nginx 日志中出現以下錯誤:

           upstream sent unsupported FastCGI protocol version: 0 while reading upstream 

           

          廣泛搜集,目前還未解決之。如果您遇到同樣的問題并解決之,請一定聯系筆者信箱zhangxugg@163.com,  甚是感謝。

          posted @ 2014-02-14 15:35 小馬歌 閱讀(896) | 評論 (0)編輯 收藏
          僅列出標題
          共95頁: First 上一頁 21 22 23 24 25 26 27 28 29 下一頁 Last 
           
          主站蜘蛛池模板: 康定县| 星座| 邯郸市| 广宗县| 密云县| 都昌县| 富锦市| 嫩江县| 岐山县| 瓦房店市| 原平市| 九台市| 鱼台县| 闵行区| 潞西市| 大埔区| 廊坊市| 五指山市| 塔城市| 金坛市| 吉木乃县| 酉阳| 克拉玛依市| 千阳县| 广河县| 宁南县| 从化市| 正安县| 金溪县| 靖宇县| 陇川县| 齐河县| 新竹市| 富阳市| 瓮安县| 滦平县| 平原县| 珠海市| 山阳县| 碌曲县| 五常市|