内容不断更新,目前包括协议中握手和数据帧的分析

 

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 小马歌 阅读(416) | 评论 (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 小马歌 阅读(399) | 评论 (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 小马歌 阅读(416) | 评论 (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 小马歌 阅读(552) | 评论 (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 小马歌 阅读(722) | 评论 (0)编辑 收藏
 
功能特点:
可以导出导入任意大小的数据库。FaisunSQL 采用分卷导出的方式,将数据库分为多个部份多次导出,因此理论上无论多大的数据库,它都可以胜任。 
导出的文件本身可以在PHP环境下执行,因此不需要借助其他工具(也不再需要 FaisunSQL程序)。导出的文件为完整的 PHP 文件,直接在服务器中执行即可,使用方便。 
虽然为多页导出和导入,但其过程会自动运行,且执行速度较快,成功率高。 
程序编写时考虑了程序的可整合性,因此只要略加修改即可整合到其他程序的后台。 
导出方式、每个数据文件的大小和数据表等都可以进行设置,个性化强。 
程序对数据进行了一定的压缩,减少了备份文件的空间占用。 
对导出的程序进行了加密,安全性高。 
导出和导入时基本上按照默认的配置即可,使用方便快捷。 
posted @ 2014-04-01 15:50 小马歌 阅读(222) | 评论 (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 小马歌 阅读(363) | 评论 (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 小马歌 阅读(1286) | 评论 (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 小马歌 阅读(1560) | 评论 (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 小马歌 阅读(874) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 21 22 23 24 25 26 27 28 29 下一页 Last