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,并且协议通常使用二制度编码,大大下降了数据的大小,减小流量消耗。
相关文章
相关标签/搜索