﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-聂永的博客-随笔分类-C1M</title><link>http://www.blogjava.net/yongboy/category/54842.html</link><description>记录工作/学习的点点滴滴。</description><language>zh-cn</language><lastBuildDate>Mon, 01 Jun 2015 03:42:52 GMT</lastBuildDate><pubDate>Mon, 01 Jun 2015 03:42:52 GMT</pubDate><ttl>60</ttl><item><title>100万并发连接服务器笔记之Java Netty处理1M连接会怎么样</title><link>http://www.blogjava.net/yongboy/archive/2013/05/13/399203.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Mon, 13 May 2013 03:16:00 GMT</pubDate><guid>http://www.blogjava.net/yongboy/archive/2013/05/13/399203.html</guid><wfw:comment>http://www.blogjava.net/yongboy/comments/399203.html</wfw:comment><comments>http://www.blogjava.net/yongboy/archive/2013/05/13/399203.html#Feedback</comments><slash:comments>9</slash:comments><wfw:commentRss>http://www.blogjava.net/yongboy/comments/commentRss/399203.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yongboy/services/trackbacks/399203.html</trackback:ping><description><![CDATA[<h2>前言</h2> <p>每一种该语言在某些极限情况下的表现一般都不太一样，那么我常用的Java语言，在达到100万个并发连接情况下，会怎么样呢，有些好奇，更有些期盼。<br />这次使用经常使用的顺手的<strong><a href="http://netty.io/">netty</a></strong> NIO框架（netty-3.6.5.Final），封装的很好，接口很全面，就像它现在的域名 <strong>netty.io</strong>，专注于网络IO。<br />整个过程没有什么技术含量，浅显分析过就更显得有些枯燥无聊，准备好，硬着头皮吧。</p> <h2>测试服务器配置</h2> <p>运行在VMWare Workstation 9中，64位Centos 6.2系统，分配14.9G内存左右，4核。<br />已安装有Java7版本：</p><pre><code>java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)
</code></pre>
<p>在/etc/sysctl.conf中添加如下配置：</p><pre><code>fs.file-max = 1048576
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
</code></pre>
<p>在/etc/security/limits.conf中添加如下配置：</p><pre><code>     *	soft nofile 1048576
     *	hard nofile 1048576
</code></pre>
<h2>测试端</h2>
<p>测试端无论是配置还是程序和以前一样，翻看前几篇博客就可以看到client5.c的源码，以及相关的配置信息等。</p>
<h2>服务器程序</h2>
<p>这次也是很简单呐，没有业务功能，客户端HTTP请求，服务端输出chunked编码内容。 </p>
<h3>入口HttpChunkedServer.java：</h3><script src="https://gist.github.com/yongboy/5558672.js"></script>
<h3>唯一的自定义处理器HttpChunkedServerHandler.java:</h3><script src="https://gist.github.com/yongboy/5558675.js"></script>
<h3>启动脚本start.sh</h3><script src="https://gist.github.com/yongboy/5558701.js"></script>
<h2>达到100万并发连接时的一些信息</h2>
<p>每次服务器端达到一百万个并发持久连接之后，然后关掉测试端程序，断开所有的连接，等到服务器端日志输出在线用户为0时，再次重复以上步骤。在这反反复复的情况下，观察内存等信息的一些情况。以某次断开所有测试端为例后，当前系统占用为（设置为<code>list_free_1</code>）:</p><pre><code>                  total       used       free     shared    buffers     cached
     Mem:         15189       7736       7453          0         18        120
     -/+ buffers/cache:       7597       7592
     Swap:         4095        948       3147
</code></pre>
<p>通过top观察，其进程相关信息</p><pre><code>    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                       
   4925 root      20   0 8206m 4.3g 2776 S  0.3 28.8  50:18.66 java
</code></pre>
<p>在启动脚本start.sh中，我们设置堆内存为6G。</p>
<p><strong>ps aux|grep java</strong>命令获得信息：</p><pre><code>  root      4925 38.0 28.8 8403444 4484764 ?     Sl   15:26  50:18 java -server...HttpChunkedServer 8000
</code></pre>
<p>RSS占用内存为4484764K/1024K=4379M</p>
<p>然后再次启动测试端，在服务器接收到<strong>online user 1023749</strong>时，<code>ps aux|grep java</code>内容为：</p><pre><code>  root      4925 43.6 28.4 8403444 4422824 ?     Sl   15:26  62:53 java -server...
</code></pre>
<p>查看当前网络信息统计</p><pre><code>  ss -s
  Total: 1024050 (kernel 1024084)
  TCP:   1023769 (estab 1023754, closed 2, orphaned 0, synrecv 0, timewait 0/0), ports 12

  Transport Total     IP        IPv6
  *    1024084   -         -        
  RAW     0         0         0        
  UDP     7         6         1        
  TCP     1023767   12        1023755  
  INET    1023774   18        1023756  
  FRAG    0         0         0    
</code></pre>
<p>通过top查看一下</p><pre><code>  top -p 4925
  top - 17:51:30 up  3:02,  4 users,  load average: 1.03, 1.80, 1.19
  Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
  Cpu0  :  0.9%us,  2.6%sy,  0.0%ni, 52.9%id,  1.0%wa, 13.6%hi, 29.0%si,  0.0%st
  Cpu1  :  1.4%us,  4.5%sy,  0.0%ni, 80.1%id,  1.9%wa,  0.0%hi, 12.0%si,  0.0%st
  Cpu2  :  1.5%us,  4.4%sy,  0.0%ni, 80.5%id,  4.3%wa,  0.0%hi,  9.3%si,  0.0%st
  Cpu3  :  1.9%us,  4.4%sy,  0.0%ni, 84.4%id,  3.2%wa,  0.0%hi,  6.2%si,  0.0%st
  Mem:  15554336k total, 15268728k used,   285608k free,     3904k buffers
  Swap:  4194296k total,  1082592k used,  3111704k free,    37968k cached

    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                       
   4925 root      20   0 8206m 4.2g 2220 S  3.3 28.4  62:53.66 java
</code></pre>
<p>四核都被占用了，每一个核心不太平均。这是在虚拟机中得到结果，可能真实服务器会更好一些。 因为不是CPU密集型应用，CPU不是问题，无须多加关注。</p>
<p>系统内存状况</p><pre><code>  free -m
               total       used       free     shared    buffers     cached
  Mem:         15189      14926        263          0          5         56
  -/+ buffers/cache:      14864        324
  Swap:         4095       1057       3038
</code></pre>
<p>物理内存已经无法满足要求了，占用了1057M虚拟内存。</p>
<p>查看一下堆内存情况</p><pre><code>  jmap -heap 4925
  Attaching to process ID 4925, please wait...
  Debugger attached successfully.
  Server compiler detected.
  JVM version is 23.21-b01

  using parallel threads in the new generation.
  using thread-local object allocation.
  Concurrent Mark-Sweep GC

  Heap Configuration:
     MinHeapFreeRatio = 40
     MaxHeapFreeRatio = 70
     MaxHeapSize      = 6442450944 (6144.0MB)
     NewSize          = 629145600 (600.0MB)
     MaxNewSize       = 629145600 (600.0MB)
     OldSize          = 5439488 (5.1875MB)
     NewRatio         = 2
     SurvivorRatio    = 1
     PermSize         = 52428800 (50.0MB)
     MaxPermSize      = 52428800 (50.0MB)
     G1HeapRegionSize = 0 (0.0MB)

  Heap Usage:
  New Generation (Eden + 1 Survivor Space):
     capacity = 419430400 (400.0MB)
     used     = 308798864 (294.49354553222656MB)
     free     = 110631536 (105.50645446777344MB)
     73.62338638305664% used
  Eden Space:
     capacity = 209715200 (200.0MB)
     used     = 103375232 (98.5863037109375MB)
     free     = 106339968 (101.4136962890625MB)
     49.29315185546875% used
  From Space:
     capacity = 209715200 (200.0MB)
     used     = 205423632 (195.90724182128906MB)
     free     = 4291568 (4.0927581787109375MB)
     97.95362091064453% used
  To Space:
     capacity = 209715200 (200.0MB)
     used     = 0 (0.0MB)
     free     = 209715200 (200.0MB)
     0.0% used
  concurrent mark-sweep generation:
     capacity = 5813305344 (5544.0MB)
     used     = 4213515472 (4018.321487426758MB)
     free     = 1599789872 (1525.6785125732422MB)
     72.48054631000646% used
  Perm Generation:
     capacity = 52428800 (50.0MB)
     used     = 5505696 (5.250640869140625MB)
     free     = 46923104 (44.749359130859375MB)
     10.50128173828125% used

  1439 interned Strings occupying 110936 bytes.
</code></pre>
<p>老生代占用内存为72%，较为合理，毕竟系统已经处理100万个连接。</p>
<p>再次断开所有测试端，看看系统内存（free -m）</p><pre><code>               total       used       free     shared    buffers     cached
  Mem:         15189       7723       7466          0         13        120
  -/+ buffers/cache:       7589       7599
  Swap:         4095        950       3145
</code></pre>
<p>记为<code>list_free_2</code>。</p>
<p><code>list_free_1</code>和<code>list_free_2</code>两次都释放后的内存比较结果，系统可用物理已经内存已经降到7589M，先前可是7597M物理内存。<br />总之，我们的JAVA测试程序在内存占用方面已经，最低需要7589 + 950 = 8.6G内存为最低需求内存吧。</p>
<h2>GC日志</h2>
<p>我们在启动脚本处设置的一大串参数，到底是否达到目标，还得从gc日志处获得具体效果，推荐使用<a href="https://github.com/chewiebug/GCViewer">GCViewer</a>。</p>
<p>GC事件概览：<br /><a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_eventdetails_2.png"><img title="gc_eventdetails" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" alt="gc_eventdetails" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_eventdetails_thumb.png" border="0" height="476" width="814" /></a></p>
<p>其它:<br /><a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_total_1_2.png"><img title="gc_total_1" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" alt="gc_total_1" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_total_1_thumb.png" border="0" height="367" width="364" /></a>&nbsp;<a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_total_2_2.png"><img title="gc_total_2" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" alt="gc_total_2" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_total_2_thumb.png" border="0" height="367" width="363" /></a> <a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_total_3_2.png"><img title="gc_total_3" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" alt="gc_total_3" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/bf3eb5ad9dd9_9C7F/gc_total_3_thumb.png" border="0" height="368" width="367" /></a></p>
<p>总之：</p>
<ul>
<li>只进行了一次Full GC，代价太高，停顿了12秒。 
</li><li>PartNew成为了停顿大户，导致整个系统停顿了41秒之久，不可接受。 
</li><li>当前JVM调优喜忧参半，还得继续努力等 </li></ul>
<h2>小结</h2>
<p>Java与与Erlang、C相比，比较麻烦的事情，需要在程序一开始就得准备好它的堆栈到底需要多大空间，换个说法就是JVM启动参数设置堆内存大小，设置合适的垃圾回收机制，若以后程序需要更多内存，需停止程序，编辑启动参数，然后再次启动。总之一句话，就是麻烦。单单JVM的调优，就得持续不断的根据检测、信息、日志等进行适当微调。</p>
<ul>
<li>JVM需要提前指定堆大小，相比Erlang/C，这可能是个麻烦 
</li><li>GC(垃圾回收)，相对比麻烦，需要持续不断的根据日志、JVM堆栈信息、运行时情况进行JVM参数微调 
</li><li>设置一个最大连接目标，多次测试达到顶峰，然后释放所有连接，反复观察内存占用，获得一个较为合适的系统运行内存值 
</li><li>Eclipse Memory Analyzer结合jmap导出堆栈DUMP文件，分析内存泄漏，还是很方便的 
</li><li>想修改运行时内容，或者称之为热加载，默认不可能 
</li><li>真实机器上会有更好的反映 </li></ul>
<p>吐槽一下：<br />JAVA OSGI，相对比Erlang来说，需要人转换思路，不是那么原生的东西，总是有些别扭，社区或商业公司对此的修修补补，不过是实现一些面向对象所不具备的热加载的企业特性。</p>
<p>测试源代码，<a href="http://www.blogjava.net/Files/yongboy/just_test.zip">下载just_test</a>。</p><img src ="http://www.blogjava.net/yongboy/aggbug/399203.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yongboy/" target="_blank">nieyong</a> 2013-05-13 11:16 <a href="http://www.blogjava.net/yongboy/archive/2013/05/13/399203.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>100万并发连接服务器笔记之Erlang完成1M并发连接目标</title><link>http://www.blogjava.net/yongboy/archive/2013/04/28/398558.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Sun, 28 Apr 2013 09:17:00 GMT</pubDate><guid>http://www.blogjava.net/yongboy/archive/2013/04/28/398558.html</guid><wfw:comment>http://www.blogjava.net/yongboy/comments/398558.html</wfw:comment><comments>http://www.blogjava.net/yongboy/archive/2013/04/28/398558.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/yongboy/comments/commentRss/398558.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yongboy/services/trackbacks/398558.html</trackback:ping><description><![CDATA[<h2>前言</h2>
<p>使用Erlang语言也写一个测试和前面大同小异的测试，在100万个并发连接用户情况下，就是想观察一下极显情况下的表现。<br />
这个测试使用了优秀的Erlang界的明星框架<strong><ahref="https: github.="" com="" extend=""  cowboy"="">cowboy</ahref="https:></strong>，加单易用的接口，避免了我们对HTTP栈再次进行闭门造车。</p>
<h2>测试Erlang服务器</h2>
<p>运行在VMWare Workstation 9中，64位Centos 6.4系统，分配14.9G内存左右，双核4个线程，服务器安装Erlang/OTP R16B，最新版本支持异步代码热加载，很赞。</p>
<h2>下载安装</h2>
<p>本系统已经提前安装JDK，只需要安装Erlang好了。</p>
<p>安装依赖</p>
<pre><code>
<div>yum install build-essential m4 <br />
yum install openssl <br />
yum install openssl-devel <br />
yum install unixODBC <br />
yum install unixODBC-devel <br />
yum -y install openssl make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel<br />
yum install xsltproc fop</div>
</code></pre>
<p>源代码安装</p>
<pre><code>#wget https://elearning.erlang-solutions.com/binaries/sources/otp_src_R16B.tar.gz
#tar xvf otp_src_R16B.tar.gz
cd otp_src_R16B
./configure --prefix=/usr/local/erlang --enable-hipe --enable-threads --enable-smp-support --enable-kernel-poll
make
make install
</code></pre>
<p>添加到环境变量中(/etc/profile)</p>
<pre><code>export ERL_HOME=/usr/local/erlang export PATH=$ERL_HOME/bin:$PATH
</code></pre>
<p>保存生效</p>
<pre><code>source /etc/profile
</code></pre>
<h2>测试进程创建</h2>
<p>这里拷贝《Erlang程序设计》一书提供的<strong>processes.erl</strong>源码，稍作修改。
<script src="https://gist.github.com/yongboy/5475628.js"></script></p>
<p>创建一百万个进程，看看大概花费多少时间。</p>
<pre><code>[yongboy@base erlang]$ erl +P 10240000
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V5.10.1 (abort with ^G)
1&gt; processes:max(200000).
Maxmium allowed process is 16777216 Process spawn time=1.1 (2.68) microseconds ok
2&gt; processes:max(200000).
Maxmium allowed process is 16777216 Process spawn time=1.7 (2.33) microseconds ok
3&gt; processes:max(200000).
Maxmium allowed process is 16777216 Process spawn time=1.55 (2.12) microseconds ok
4&gt; processes:max(1000000).
Maxmium allowed process is 16777216 Process spawn time=2.97 (3.967) microseconds ok
5&gt; processes:max(1000000).
Maxmium allowed process is 16777216 Process spawn time=2.4 (2.729) microseconds ok
6&gt; processes:max(1000000).
Maxmium allowed process is 16777216 Process spawn time=2.19 (2.735) microseconds ok
7&gt; processes:max(10000000).
Maxmium allowed process is 16777216 Process spawn time=3.328 (4.2777) microseconds ok
8&gt; processes:max(10000000).
Maxmium allowed process is 16777216 Process spawn time=3.144 (3.1361) microseconds ok
9&gt; processes:max(10000000).
Maxmium allowed process is 16777216 Process spawn time=3.394 (3.2051) microseconds ok
</code></pre>
<p>恩，创建1000万个进程，每一个进程花费3.4微秒(&#956;s)的CPU时间，相当于4.3微秒(&#956;s)的消耗时间(亦即消耗的真实时间),在一定量的区间内，其值变化，可以看做是一个常量。</p>
<h2>初始化小问题</h2>
<h3>Cowboy初始化需要事项</h3>
<p>我们做的简单程序，使用了非常受欢迎的cowboy框架，其启动函数:</p>
<pre><code>cowboy:start_http(my_http_listener, 100,
[{port, 8000}],
[{env, [{dispatch, Dispatch}]}]
),
</code></pre>
<p>Cowboy默认支持1024个连接，服务器端输出：</p>
<pre><code>online user 1122 online user 1123 </code></pre>
<p>停滞于此，后续的连接只能排队等候了。</p>
<p>这里设置支持无限个连接好了。</p>
<pre><code>{max_connections, infinity}
</code></pre>
<p>话说，Cowboy为Erlang世界的明星产品，绝对值得一试！</p>
<h3>Erlang默认创建进程限制</h3>
<p>Erlang在我的机器上默认允许创建的线程也是有限的：</p>
<pre><code>erlang:system_info(process_limit).
262144
</code></pre>
<p>才26万个，不够用。在启动脚本(<em>start.sh</em>)处添加允许创建的最大线程支持：</p>
<pre><code>#!/bin/sh
erl +K true +P 10240000 -sname testserver -pa ebin -pa deps/*/ebin -s htmlfilesimple\
-eval "io:format(\"Server start with port 8000 Success!~n\")."</code></pre>
<p>脚本启动后现在在erl shell中测试一下：</p>
<pre><code>erlang:system_info(process_limit).
16777216
</code></pre>
<p>数量完全够用了。</p>
<h3>开启erlang的epoll属性</h3>
<pre><code>+K true | false 是否开启kernel poll，就是epoll；
</code></pre>
<p>不开启，测试过程中，在内存完好情况下，经常会有连接失败情况。</p>
<h3>使用cowboy_req:compact降低内存占用</h3>
<p>一旦你从request对象中获取到足够的信息，以后不再获取其附加属性时，调用<strong>compact/1</strong>函数可去除无用属性，起到节省内存作用。 <br />
程序里面调用如下：</p>
<pre><code>init(_Any, Req, State) -&gt; NowCount = count_server:welcome(),
io:format("online user ~p :))~n", [NowCount]),
output_first(Req),
Req2 = cowboy_req:compact(Req),
{loop, Req2, State, hibernate}.
</code></pre>
<p>在本例中精测压缩内存效果不明显，因为 ，测试端输出的HTTP头部压根就没有几个。</p>
<h3>Cowboy无法处理没有header的HTTP请求</h3>
<p>这里需要牢记，也不能算是BUG，前面的<strong>client2.c</strong>源码，就未曾设置HTTP Header元数据，需要做些简单修改，修改之后的测试端程序文件名为<strong>client5.c</strong>,
可以到这里 <a href="https://gist.github.com/yongboy/5476214">下载client.c</a>。</p>
<h2>Cowboy处理长连接</h2>
<p>Cowboy很贴心的提供了<code>cowboy_loop_handler</code> behaviour。在init/3函数中，可以进入休眠状态，节省内存，消息到达时，被唤醒，值得一赞！
其定义如下：
<script src="https://gist.github.com/yongboy/5476302.js"></script>注意<strong>hibernate</strong>和<strong>timeout</strong>参数，按照实际需求返回即可。</p>
<p>htmlfile_handler示范代码如下：
<script src="https://gist.github.com/yongboy/5476350.js"></script></p>
<h2>100万并发连接达成</h2>
<p>测试过程跌跌撞撞的，虽然这中间因为内存问题抛出若干的异常，但也达到100W连接的数量</p>
<pre><code>online user 1022324 :))
online user 1022325 :))
online user 1022326 :))
online user 1022327 :))
online user 1022328 :))
online user 1022329 :))
online user 1022330 :))
online user 1022331 :))
online user 1022332 :))
online user 1022333 :))
online user 1022334 :))
online user 1022335 :))
online user 1022336 :))
online user 1022337 :))
online user 1022338 :))
</code></pre>
<p>可以看到状态信息
<img src="http: images.blogjava.="  "="" net="" blogjava_net="" yongboy=""  erlang_1m_state.png"="" alt="" /></p>
<p>算一下：
14987952K = 14636M <br />
14987952/1022338 = <strong>14.7K/Connection</strong></p>
<p>未启动时的内存情况：</p>
<pre><code> total used free shared buffers cached
Mem: 14806 245 14561 0 12 60
-/+ buffers/cache: 172 14634
Swap: 3999 0 3999
</code></pre>
<p>启动后的内存占用情况：</p>
<pre><code> total used free shared buffers cached
Mem: 14806 435 14370 0 12 60
-/+ buffers/cache: 363 14443
Swap: 3999 0 3999
</code></pre>
<p>用户量达到1022338数量后的内存一览：</p>
<pre><code> total used free shared buffers cached
Mem: 14806 14641 165 0 1 5
-/+ buffers/cache: 14634 172
Swap: 3999 1068 2931
</code></pre>
<p>可以看到，当前内存不够用了，需要虚拟内存配合了。</p>
<p>查看一下当前进程的内存占用</p>
<pre><code>ps -o rss= -p `pgrep -f 'sname testserver'`
4869520
</code></pre>
<p>这样算起来，系统为每一个进程持有 4869520/1022338 = <strong>4.8K</strong> 内存。
这个值只是计算物理内存，实际上连虚拟内存都占用了，估计在4.8K-6.8K之间吧。</p>
<p>和C语言相比，内存占用相当大，我虚拟机器分配的15G内存，也仅仅处理达到100万的连接，已经接近极限时，会发现陆陆续续的有连接失败。</p>
<h2>不得不说的代码热加载</h2>
<p>运行时系统的代码热加载功能，在这个实例中，通过vi修改了htmlfile_handler.erl文件，主要修改内容如下：</p>
<pre><code>io:format("online user ~p :))~n", [NowCount]),
......
io:format("offline user ~p :(( ~n", [NowCount]).
</code></pre>
<p>执行make，编译</p>
<pre><code>[root@base htmlfilesimple]# make
==&gt; ranch (get-deps)
==&gt; cowboy (get-deps)
==&gt; htmlfilesimple (get-deps)
==&gt; ranch (compile)
==&gt; cowboy (compile)
==&gt; htmlfilesimple (compile)
src/htmlfile_handler.erl:5: Warning: record status is unused
src/htmlfile_handler.erl:29: Warning: variable 'Reason' is unused
Compiled src/htmlfile_handler.erl
</code></pre>
<p>很好，非常智能的rebar，自动只编译了<strong>htmlfile_handler.erl</strong>一个文件，然后通知Erlang的运行环境进行代码热替换吧。</p>
<pre><code>(testserver@base)4&gt; code:load_file(htmlfile_handler). </code></pre>
<p>查看日志输出控制台，可以看到已经生效，同时也保存着到状态数据等。</p>
<p>非常利于运行时调试，即不伤害在线状态数据，又能即时修改，赞！但生产环境下，一般都是版本切换，OTP的版本切换，测试或马上修改bug时，着实有些复杂。</p>
<h2>小结</h2>
<p>和C相比，处理相同的事情（100万并发连接），及其简单，但Erlang会需要更多的内存，廉价的内存可以满足，只是我的搭建在Vmware中的虚拟机器已经达到了它所要求的极限。 <br />
完整的源代码，可点击这里<a href="http://www.blogjava.net/Files/yongboy/htmlfilesimple.zip">下载</a>。</p><img src ="http://www.blogjava.net/yongboy/aggbug/398558.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yongboy/" target="_blank">nieyong</a> 2013-04-28 17:17 <a href="http://www.blogjava.net/yongboy/archive/2013/04/28/398558.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>100万并发连接服务器笔记之1M并发连接目标达成</title><link>http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Thu, 11 Apr 2013 01:01:00 GMT</pubDate><guid>http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html</guid><wfw:comment>http://www.blogjava.net/yongboy/comments/397677.html</wfw:comment><comments>http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.blogjava.net/yongboy/comments/commentRss/397677.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yongboy/services/trackbacks/397677.html</trackback:ping><description><![CDATA[<h2>第四个遇到的问题：tcp_mem</h2><p>在服务端，连接达到一定数量，诸如50W时，有些隐藏很深的问题，就不断的抛出来。
通过查看<strong>dmesg</strong>命令查看，发现大量<strong>TCP: too many of orphaned sockets</strong>错误，也很正常，下面到了需要调整tcp socket参数的时候了。</p><p>第一个需要调整的是tcp_rmem，即TCP读取缓冲区，单位为字节，查看默认值</p><pre><code>cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 4161536
</code></pre><p>默认值为87380 byte &#8776; 86K，最小为4096 byte=4K，最大值为4064K。</p><p>第二个需要调整的是tcp_wmem，发送缓冲区，单位是字节，默认值</p><pre><code>cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4161536
</code></pre><p>解释同上</p><p>第三个需要调整的tcp_mem，调整TCP的内存大小，其单位是页，1页等于4096字节。系统默认值：</p><pre><code>cat /proc/sys/net/ipv4/tcp_mem
932448 1243264 1864896
</code></pre><p>tcp_mem(3个INTEGER变量)：low, pressure, high</p><ul><li>low：当TCP使用了低于该值的内存页面数时，TCP不会考虑释放内存。</li><li>pressure：当TCP使用了超过该值的内存页面数量时，TCP试图稳定其内存使用，进入pressure模式，当内存消耗低于low值时则退出pressure状态。</li><li>high：允许所有tcp sockets用于排队缓冲数据报的页面量，当内存占用超过此值，系统拒绝分配socket，后台日志输出&#8220;TCP: too many of orphaned sockets&#8221;。</li></ul><p>一般情况下这些值是在系统启动时根据系统内存数量计算得到的。
根据当前tcp_mem最大内存页面数是1864896，当内存为(1864896*4)/1024K=7284.75M时，系统将无法为新的socket连接分配内存，即TCP连接将被拒绝。</p><p>实际测试环境中，据观察大概在99万个连接左右的时候(零头不算)，进程被杀死，触发out of socket memory错误（dmesg命令查看获得）。每一个连接大致占用7.5K内存（下面给出计算方式），大致可算的此时内存占用情况（990000 * 7.5 / 1024K = 7251M)。</p><p>这样和tcp_mem最大页面值数量比较吻合，因此此值也需要修改。</p><p>三个TCP调整语句为:</p><pre><code>echo "net.ipv4.tcp_mem = 786432 2097152 3145728"&gt;&gt; /etc/sysctl.conf
echo "net.ipv4.tcp_rmem = 4096 4096 16777216"&gt;&gt; /etc/sysctl.conf
echo "net.ipv4.tcp_wmem = 4096 4096 16777216"&gt;&gt; /etc/sysctl.conf
</code></pre><p>备注： 为了节省内存，设置tcp读、写缓冲区都为4K大小，<code>tcp_mem</code>三个值分别为3G 8G 16G，<code>tcp_rmem</code>和<code>tcp_wmem</code>最大值也是16G。</p><h2>目标达成</h2><p>经过若干次的尝试，最终达到目标，1024000个持久连接。1024000数字是怎么得来的呢，两台物理机器各自发出64000个请求，两个配置为6G左右的centos测试端机器(绑定7个桥接或NAT连接)各自发出64000<em>7 = 448000。也就是
1024000 = (64000) + (64000) + (64000</em>7) + (64000*7), 共使用了16个网卡（物理网卡+虚拟网卡）。 <br />终端输出</p><pre><code>......
online user 1023990
online user 1023991
online user 1023992
online user 1023993
online user 1023994
online user 1023995
online user 1023996
online user 1023997
online user 1023998
online user 1023999
online user 1024000
</code></pre><p><strong>在线用户目标达到1024000个！</strong></p><h2>服务器状态信息</h2><p>服务启动时内存占用：</p><pre><code><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; total&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;used&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shared&nbsp;&nbsp;&nbsp;&nbsp;buffers&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cached<br /> &nbsp;&nbsp;&nbsp;&nbsp;Mem:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10442&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;271&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10171&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;78<br /> &nbsp;&nbsp;&nbsp;&nbsp;-/+ buffers/cache:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;171&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10271<br /> &nbsp;&nbsp;&nbsp;&nbsp;Swap:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127</div>
</code></pre><p>系统达到1024000个连接后的内存情况（执行三次 free -m 命令，获取三次结果）：</p><pre><code><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; total&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;used&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shared&nbsp;&nbsp;&nbsp;&nbsp;buffers&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cached<br /> &nbsp;&nbsp;&nbsp;&nbsp;Mem:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10442&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7781&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2661&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;78<br /> &nbsp;&nbsp;&nbsp;&nbsp;-/+ buffers/cache:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7680&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2762<br /> &nbsp;&nbsp;&nbsp;&nbsp;Swap:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127<br /> <br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;used&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shared&nbsp;&nbsp;&nbsp;&nbsp;buffers&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cached<br /> &nbsp;&nbsp;&nbsp;&nbsp;Mem:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10442&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7793&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2649&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;78<br /> &nbsp;&nbsp;&nbsp;&nbsp;-/+ buffers/cache:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7692&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2750<br /> &nbsp;&nbsp;&nbsp;&nbsp;Swap:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127<br /> <br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;used&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shared&nbsp;&nbsp;&nbsp;&nbsp;buffers&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cached<br /> &nbsp;&nbsp;&nbsp;&nbsp;Mem:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10442&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7804&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2638&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;79<br /> &nbsp;&nbsp;&nbsp;&nbsp;-/+ buffers/cache:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7702&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2740<br /> &nbsp;&nbsp;&nbsp;&nbsp;Swap:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8127</div>
</code></pre><p>这三次内存使用分别是7680,7692,7702，这次不取平均值，取一个中等偏上的值，定为7701M。那么程序接收1024000个连接，共消耗了 7701M-171M = 7530M内存， 7530M*1024K / 1024000 = 7.53K， 每一个连接消耗内存在为7.5K左右，这和在连接达到512000时所计算较为吻合。 <br />虚拟机运行Centos内存占用，不太稳定，但一般相差不大，以上数值，仅供参考。</p><p>执行top -p <serverpid>某刻输出信息：</serverpid></p><pre><code><div>&nbsp;&nbsp;&nbsp; top - 17:23:17 up 18 min,&nbsp;&nbsp;4 users,&nbsp;&nbsp;load average: 0.33, 0.12, 0.11<br /><div> &nbsp;&nbsp;&nbsp;&nbsp;Tasks:&nbsp;&nbsp;&nbsp;1 total,&nbsp;&nbsp;&nbsp;1 running,&nbsp;&nbsp;&nbsp;0 sleeping,&nbsp;&nbsp;&nbsp;0 stopped,&nbsp;&nbsp;&nbsp;0 zombie<br /> &nbsp;&nbsp;&nbsp;&nbsp;Cpu(s):&nbsp;&nbsp;0.2%us,&nbsp;&nbsp;6.3%sy,&nbsp;&nbsp;0.0%ni, 80.2%id,&nbsp;&nbsp;0.0%wa,&nbsp;&nbsp;4.5%hi,&nbsp;&nbsp;8.8%si,&nbsp;&nbsp;0.0%st<br /> &nbsp;&nbsp;&nbsp;&nbsp;Mem:&nbsp;&nbsp;10693580k total,&nbsp;&nbsp;6479980k used,&nbsp;&nbsp;4213600k free,&nbsp;&nbsp;&nbsp;&nbsp;22916k buffers<br /> &nbsp;&nbsp;&nbsp;&nbsp;Swap:&nbsp;&nbsp;8323056k total,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0k used,&nbsp;&nbsp;8323056k free,&nbsp;&nbsp;&nbsp;&nbsp;80360k cached<br /> <br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PID USER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PR&nbsp;&nbsp;NI&nbsp;&nbsp;VIRT&nbsp;&nbsp;RES&nbsp;&nbsp;SHR S %CPU %MEM&nbsp;&nbsp;&nbsp;&nbsp;TIME+&nbsp;&nbsp;COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2924 yongboy&nbsp;&nbsp;&nbsp;20&nbsp;&nbsp;&nbsp;0 82776&nbsp;&nbsp;74m&nbsp;&nbsp;508 R 51.3&nbsp;&nbsp;0.7&nbsp;&nbsp;&nbsp;3:53.95 server&nbsp;</div></div></code></pre><p>执行vmstate：</p><pre><code>vmstat
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r b swpd free buff cache si so bi bo in cs us sy id wa st
 0 0 0 2725572 23008 80360 0 0 21 2 1012 894 0 9 89 2 0 </code></pre><p>获取当前socket连接状态统计信息：</p><pre><code>cat /proc/net/sockstat
sockets: used 1024380
TCP: inuse 1024009 orphan 0 tw 0 alloc 1024014 mem 2
UDP: inuse 11 mem 1
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
</code></pre><p>获取当前系统打开的文件句柄：</p><pre><code>sysctl -a | grep file
fs.file-nr = 1025216 0 1048576
fs.file-max = 1048576
</code></pre><p>此时任何类似于下面查询操作都是一个慢，等待若干时间还不见得执行完毕。</p><pre><code>netstat -nat|grep -i "8000"|grep ESTABLISHED|wc -l <br />netstat -n | grep -i "8000" | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
</code></pre><p>以上两个命令在二三十分钟过去了，还未执行完毕，只好停止。</p><h2>小结</h2><p>本次从头到尾的测试，所需要有的linux系统需要调整的参数也就是那么几个，汇总一下：</p><pre><code><div>&nbsp;&nbsp;&nbsp;  echo "* - nofile 1048576" &gt;&gt; /etc/security/limits.conf<br /> <br /> &nbsp;&nbsp;&nbsp;&nbsp;echo "fs.file-max = 1048576" &gt;&gt; /etc/sysctl.conf<br /> &nbsp;&nbsp;&nbsp;&nbsp;echo "net.ipv4.ip_local_port_range = 1024 65535" &gt;&gt; /etc/sysctl.conf<br /> <br /> &nbsp;&nbsp;&nbsp;&nbsp;echo "net.ipv4.tcp_mem = 786432 2097152 3145728" &gt;&gt; /etc/sysctl.conf<br /> &nbsp;&nbsp;&nbsp;&nbsp;echo "net.ipv4.tcp_rmem = 4096 4096 16777216" &gt;&gt; /etc/sysctl.conf<br /> &nbsp;&nbsp;&nbsp;&nbsp;echo "net.ipv4.tcp_wmem = 4096 4096 16777216" &gt;&gt; /etc/sysctl.conf</div>
</code></pre><p>其它没有调整的参数，仅仅因为它们暂时对本次测试没有带来什么影响，实际环境中需要结合需要调整类似于SO_KEEPALIVE<em>、tcp</em>max_orphans等大量参数。<br /><br /></p><p>本文代表一次实践，不足之处，欢迎批评指正。</p><img src ="http://www.blogjava.net/yongboy/aggbug/397677.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yongboy/" target="_blank">nieyong</a> 2013-04-11 09:01 <a href="http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>100万并发连接服务器笔记之测试端就绪</title><link>http://www.blogjava.net/yongboy/archive/2013/04/10/397631.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Wed, 10 Apr 2013 03:07:00 GMT</pubDate><guid>http://www.blogjava.net/yongboy/archive/2013/04/10/397631.html</guid><wfw:comment>http://www.blogjava.net/yongboy/comments/397631.html</wfw:comment><comments>http://www.blogjava.net/yongboy/archive/2013/04/10/397631.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/yongboy/comments/commentRss/397631.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yongboy/services/trackbacks/397631.html</trackback:ping><description><![CDATA[<h2>重新编写测试端程序</h2>
<p>测试端程序需要增加绑定本机IP和本地端口的功能，以尽可能的向外发出更多的tcp请求。需要对<a href="https://gist.github.com/yongboy/5318994">client1.c</a>重构，增加参数传递。
下面是client2.c的代码
<script src="https://gist.github.com/yongboy/5324779.js"></script></p>
<p>若不指定端口，系统会随机挑选没有使用到的端口，可以节省些心力。</p>
<p>编译：</p>
<pre><code>gcc -o client2 client2.c -levent
</code></pre>
<p>参数解释</p>
<ul>
     <li>-h 要连接的服务器IP地址</li>
     <li>-p 要连接的服务器端口</li>
     <li>-m 本机IP地址需要绑定的随机端口数量</li>
     <li>-o 本机所有可用的IP地址列表，注意所有IP地址都应该可用</li>
</ul>
<p>运行：</p>
<pre><code>./client2 -h 192.168.190.230 -p 8000 -m 64000 -o 192.168.190.134,192.168.190.143,192.168.190.144,192.168.190.145,192.168.190.146,192.168.190.147,192.168.190.148,192.168.190.149,192.168.190.150,192.168.190.151
</code></pre>
<p>太长了，每次执行都要粘贴过去，直接放在一个client2.sh文件中，执行就很简单方便多了。</p>
<pre><code>#!/bin/sh
./client2 -h 192.168.190.230 -p 8000 -m 64000 -o 192.168.190.134,192.168.190.143,192.168.190.144,192.168.190.145,192.168.190.146,192.168.190.147,192.168.190.148,192.168.190.149,192.168.190.150,192.168.190.151
</code></pre>
<p>保存，赋值可运行:</p>
<pre><code>chmod +x client2.sh
</code></pre>
<p>启动测试：</p>
<pre><code>sh client2.sh
</code></pre>
<h2>第三个遇到的问题：fs.file-max的问题</h2>
<p>测试端程序client2.c在发出的数量大于某个值（大概为40万时）是，通过<strong>dmesg</strong>命令查看会得到大量警告信息：</p>
<pre><code>[warn] socket: Too many open files in system
</code></pre>
<p>此时，就需要检查<strong>/proc/sys/fs/file-max</strong>参数了。 </p>
<p>查看一下系统对fs.file-max的说明</p>
<pre><code> /proc/sys/fs/file-max
This file defines a system-wide limit on the number of open files for all processes. (See also setrlimit(2), which can be used by a process to set the per-process limit,
RLIMIT_NOFILE, on the number of files it may open.) If you get lots of error messages about running out of file handles, try increasing this value:
echo 100000 &gt; /proc/sys/fs/file-max
The kernel constant NR_OPEN imposes an upper limit on the value that may be placed in file-max.
If you increase /proc/sys/fs/file-max, be sure to increase /proc/sys/fs/inode-max to 3-4 times the new value of /proc/sys/fs/file-max, or you will run out of inodes.
</code></pre>
<p><strong>file-max</strong>表示系统所有进程最多允许同时打开所有的文件句柄数，系统级硬限制。Linux系统在启动时根据系统硬件资源状况计算出来的最佳的最大同时打开文件数限制，如果没有特殊需要，不用修改，除非打开的文件句柄数超过此值。</p>
<p>在为测试机分配4G内存时，对应的fs.file-max值为386562，很显然打开的文件句柄很受限，38万个左右。
很显然，无论是测试端还是服务端，都应该将此值调大些，一定要大于等于/etc/security/limits.conf送所设置的soft nofile和soft nofile值。 <br />
注意ulimit -n，仅仅设置当前shell以及由它启动的进程的资源限制。</p>
<p>备注：以上参数，具有包含和被包含的关系。 </p>
<p>当前会话修改，可以这么做：</p>
<pre><code>echo 1048576 &gt; /proc/sys/fs/file-max
</code></pre>
<p>但系统重启后消失。</p>
<p>永久修改，要添加到 /etc/sysctl.conf 文件中：</p>
<pre><code>fs.file-max = 1048576
</code></pre>
<p>保存并使之生效：</p>
<pre><code>sysctl -p
</code></pre>
<p>再测，就不会出现此问题了。 </p>
<p>一台6G内存机器测试机，分配7个网卡，可以做到不占用虚拟内存，对外发出64000 * 7 = 448000个对外持久请求。要完成100万的持久连接，还得再想办法。</p>
<p>最终测试端组成如下：</p>
<ul>
     <li>两台物理机器各自一个网卡，每个发出64000个请求</li>
     <li>两个6G左右的centos测试端机器(绑定7个桥接或NAT连接)各自发出64000*7 = 448000请求</li>
     <li>共使用了16个网卡（物理网卡+虚拟网卡）</li>
     <li>1M &#8776; 1024K &#8776; 1024000 = (64000) + (64000) + (64000*<em>7) + (64000</em>*7)</li>
     <li>共耗费16G内存，16个网卡(物理+虚拟)，四台测试机</li>
</ul>
<p>备注： <br />
下面就要完成1M持久连接的目标，但在服务端还会遇到最后一个问题。</p><img src ="http://www.blogjava.net/yongboy/aggbug/397631.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yongboy/" target="_blank">nieyong</a> 2013-04-10 11:07 <a href="http://www.blogjava.net/yongboy/archive/2013/04/10/397631.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>100万并发连接服务器笔记之处理端口数量受限问题</title><link>http://www.blogjava.net/yongboy/archive/2013/04/09/397594.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Tue, 09 Apr 2013 09:26:00 GMT</pubDate><guid>http://www.blogjava.net/yongboy/archive/2013/04/09/397594.html</guid><wfw:comment>http://www.blogjava.net/yongboy/comments/397594.html</wfw:comment><comments>http://www.blogjava.net/yongboy/archive/2013/04/09/397594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yongboy/comments/commentRss/397594.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yongboy/services/trackbacks/397594.html</trackback:ping><description><![CDATA[<h2>第二个遇到的问题：端口数量受限</h2>
<p>一般来说，单独对外提供请求的服务不用考虑端口数量问题，监听某一个端口即可。但是向提供代理服务器，就不得不考虑端口数量受限问题了。当前的1M并发连接测试，也需要在客户端突破6万可用端口的限制。</p>
<h2>单机端口上限为65536</h2>
<p>端口为16进制，那么2的16次方值为65536，在linux系统里面，1024以下端口都是超级管理员用户（如root）才可以使用，普通用户只能使用大于1024的端口值。 <br />
系统提供了默认的端口范围：</p>
<blockquote>
<p>cat /proc/sys/net/ipv4/ip_<em>local_</em>port_range <br />
32768	61000</p>
</blockquote>
<p>大概也就是共61000-32768=28232个端口可以使用，单个IP对外只能发送28232个TCP请求。 <br />
以管理员身份，把端口的范围区间增到最大：</p>
<blockquote>
<p>echo "1024 65535"&gt; /proc/sys/net/ipv4/ip_<em>local_</em>port_range</p>
</blockquote>
<p>现在有64511个端口可用. <br />
以上做法只是临时，系统下次重启，会还原。
更为稳妥的做法是修改<strong>/etc/sysctl.conf</strong>文件，增加一行内容</p>
<blockquote>
<p>net.ipv4.ip_<em>local_</em>port_range= 1024 65535</p>
</blockquote>
<p>保存，然后使之生效：</p>
<blockquote>
<p>sysctl -p </p>
</blockquote>
<p>现在可以使用的端口达到64510个（假设系统所有运行的服务器是没有占用大于1024的端口的，较为纯净的centos系统可以做到），要想达到50万请求，还得再想办法。</p>
<h2>增加IP地址</h2>
<p>一般假设本机网卡名称为 eth0，那么手动再添加几个虚拟的IP：</p>
<blockquote>
<p>ifconfig eth0:1 192.168.190.151 <br />
ifconfig eth0:2 192.168.190.152
......</p>
</blockquote>
<p>或者偷懒一些：</p>
<blockquote>
<p>for i in `seq 1 9`; do ifconfig eth0:$i 192.168.190.15$i up ; done</p>
</blockquote>
<p>这些虚拟的IP地址，一旦重启，或者 <strong>service network restart</strong> 就会丢失。 </p>
<p>为了模拟较为真实环境，在测试端，手动再次添加9个vmware虚拟机网卡，每一个网卡固定一个IP地址，这样省去每次重启都要重新设置的麻烦。</p>
<blockquote>
<p>192.168.190.134 <br />
192.168.190.143<br />
192.168.190.144<br />
192.168.190.145<br />
192.168.190.146<br />
192.168.190.147<br />
192.168.190.148<br />
192.168.190.149<br />
192.168.190.150<br />
192.168.190.151</p>
</blockquote>
<p>在server服务器端，手动添加桥接网卡和NAT方式网卡</p>
<pre><code>192.168.190.230
192.168.190.240
10.95.20.250
</code></pre>
<p>要求测试端和服务器端彼此双方都是可以ping通。</p>
<h2>网络四元组/网络五元组</h2>
<p>四元组是指的是</p>
<blockquote>
<p>{源IP地址，源端口，目的IP地址，目的端口}</p>
</blockquote>
<p>五元组指的是（多了协议）</p>
<blockquote>
<p>{源IP地址，目的IP地址，协议号，源端口，目的端口}</p>
</blockquote>
<div>在《UNIX网络编程卷1：套接字联网API（第3版）》一书中，是这样解释：</div>
<blockquote>一个TCP连接的套接字对(socket pari)是一个定义该连接的两个端点的四元组，即本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。套接字对唯一标识一个网络上的每个TCP连接。 <br />
<br />
...... <br />
<br />
标识每个端点的两个值（IP地址和端口号）通常称为一个套接字。</blockquote>
<p>
以下以四元组为准。在测试端四元组可以这样认为：</p>
<blockquote>
<p>{本机IP地址，本机端口，目的IP地址，目的端口}</p>
</blockquote>
<p>请求的IP地址和目的端口基本上是固定的，不会变化，那么只能从本机IP地址和本机端口上考虑，端口的范围一旦指定了，那么增加IP地址，可以增加对外发出的请求数量。假设系统可以使用的端口范围已经如上所设，那么可以使用的大致端口为64000个，系统添加了10个IP地址，那么可以对外发出的数量为 64000 * 10 = 640000，数量很可观。</p>
<h2>只有{源IP地址，源端口}确定对外TCP请求数量</h2>
<p>经测试，四元组里面，只有<strong>{源IP地址，源端口}</strong>才能够确定对外发出请求的数量，跟<strong>{目的IP地址，目的端口}</strong>无关。 </p>
<h2>测试环境</h2>
<p>在server端，并且启动./server两次，分别绑定8000端口和9000端口</p>
<pre><code>./server -p 8000
./server -p 9000
</code></pre>
<h2>本机IP、端口绑定测试程序</h2>
<p>这里写一个简单的测试绑定本机IP地址和指定端口的客户端测试程序。
<script src="https://gist.github.com/yongboy/5343986.js"></script></p>
<p>可以看到libevent-*/include/event2/http.h内置了对绑定本地IP地址的支持：</p>
<pre><code>/** sets the ip address from which http connections are made */
void evhttp_connection_set_local_address(struct evhttp_connection *evcon,
const char *address);
</code></pre>
<p>不用担心端口，系统自动自动随机挑选，除非需要特别指定：</p>
<pre><code>/** sets the local port from which http connections are made */
void evhttp_connection_set_local_port(struct evhttp_connection *evcon,
ev_uint16_t port);
</code></pre>
<p>编译</p>
<pre><code>gcc -o client3 client3.c -levent
</code></pre>
<p>client3运行参数为</p>
<ul>
     <li>-h 远程主机IP地址</li>
     <li>-p 远程主机端口</li>
     <li>-c 本机指定的IP地址(必须可用)</li>
     <li>-o 本机指定的端口(必须可用)</li>
</ul>
<p>测试用例，本机指定同样的IP地址和端口，但远程主机和IP不一样. <br />
在一个测试端打开一个终端窗口1，切换到 client3对应位置</p>
<pre><code>./client3 -h 192.168.190.230 -p 8000 -c 192.168.190.148 -o 4000
</code></pre>
<p>输出为</p>
<pre><code>remote host is 192.168.190.230
remote port is 8000
local ip is 192.168.190.148
local port is 4000
&gt;Chunks: 1 Bytes: 505
</code></pre>
<p>再打开一个测试端终端窗口2，执行：</p>
<pre><code>./client3 -h 192.168.190.240 -p 9000 -c 192.168.190.148 -o 4000
</code></pre>
<p>窗口2程序，无法执行，自动退出。 <br />
接着在窗口2终端继续输入：</p>
<pre><code>./client3 -h 192.168.190.230 -p 8000 -c 192.168.190.148 -o 4001
</code></pre>
<p>注意，和窗口1相比，仅仅改变了端口号为4001。但执行结果，和端口1输出一模一样，在等待接收数据，没有自动退出。</p>
<p>剩下的，无论怎么组合，怎么折腾，<strong>只要一对{本机IP,本机端口}被占用,也就意味着对应一个具体的文件句柄</strong>，那么其它程序将不能够再次使用。</p>
<h2>Java怎么绑定本地IP地址？</h2>
<p>java绑定就很简单，但有些限制，不够灵活，单纯从源码中看不出来，api doc可以告诉我们一些事情。
打开JDK<em>API</em>1_6<em>zh</em>CN.CHM，查看InetSocketAddress类的构造函数说明：</p>
<blockquote>
<p>public InetSocketAddress(InetAddress addr,
int port) <br />
根据 IP 地址和端口号创建套接字地址。 有效端口值介于 0 和 65535 之间。端口号 zero 允许系统在 bind 操作中挑选暂时的端口。 </p>
<p>null 地址将分配通配符 地址。 </p>
<p>参数： <br />
addr - IP 地址 <br />
port - 端口号 <br />
抛出： <br />
IllegalArgumentException - 如果 port 参数超出有效端口值的指定范围。</p>
<hr />
<p>public InetSocketAddress(String hostname,
int port) <br />
根据主机名和端口号创建套接字地址。 <br />
尝试将主机名解析为 InetAddress。如果尝试失败，则将地址标记为未解析。 </p>
<p>如果存在安全管理器，则将主机名用作参数调用其 checkConnect 方法，以检查解析它的权限。这可能会导致 SecurityException 异常。 </p>
<p>有效端口值介于 0 和 65535 之间。端口号 zero 允许系统在 bind 操作中挑选暂时的端口。 </p>
<p>参数：
hostname - 主机名 <br />
port - 端口号 <br />
抛出： <br />
IllegalArgumentException - 如果 port 参数超出有效端口值的范围，或者主机名参数为 null。 <br />
SecurityException - 如果存在安全管理器，但拒绝解析主机名的权限。 <br />
另请参见： <br />
isUnresolved()</p>
</blockquote>
<p>InetSocketAddress的两个构造函数都支持，看情况使用。注意int port传递值为0，即可做到系统随机挑选端口。追踪一下源代码，发现最终调用</p>
<pre><code>private native void socketBind(InetAddress address, int port) throws IOException;
</code></pre>
<p>如何查看socketBind的原始C代码，我就不清楚了，您若知晓，希望指教一下。
构造一个InetSocketAddress对象：</p>
<pre><code>SocketAddress localSocketAddr = new InetSocketAddress("192.168.190.143", 0);
</code></pre>
<p>然后传递给需要位置即可。诸如使用netty连接到某个服务器上，在connect时指定远方地址，以及本机地址</p>
<blockquote>
<p>ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress)
Attempts a new connection with the specified remoteAddress and the specified localAddress.</p>
</blockquote>
<p>Netty 客户端连接API见：
<ahref="http: docs.jboss.="" org="" netty=""  3.="" 2="" api="" jboss="" bootstrap=""  clientbootstrap.html"=""><a href="http://docs.jboss.org/netty/3.2/api/org/jboss/netty/bootstrap/ClientBootstrap.html">http://docs.jboss.org/netty/3.2/api/org/jboss/netty/bootstrap/ClientBootstrap.html</a></ahref="http:></p>
<h2>Linux支持绑定本机IP、端口原理</h2>
<p>说是原理，有些牵强，因为linux C提供了如何绑定函数，框架或者高级语言再怎么封装，在linux平台下面，需要这么调用：</p>
<pre><code>struct sockaddr_in clnt_addr;
....
clnt_addr.sin_family = AF_INET;
clnt_addr.sin_addr.s_addr = INADDR_ANY; //绑定本机IP地址
clnt_addr.sin_port = htons(33333); //绑定本机端口
if (bind(sockfd, (struct sockaddr *) &amp;clnt_addr,
sizeof(clnt_addr)) &lt; 0) error("ERROR on binding");
if (connect(sockfd,(struct sockaddr *) &amp;serv_addr,sizeof(serv_addr)) &lt; 0) error("ERROR connecting");
.......
</code></pre>
<p>构造一个clnt_addr结构体，本地IP或者端口赋值，在connect之前，先bind，就这么简单。</p>
<p>更完整例子，可以参考 <a href="http://stackoverflow.com/questions/4852256/need-a-complete-snippet-example-of-binding-tcp-client-socket">http://stackoverflow.com/questions/4852256/need-a-complete-snippet-example-of-binding-tcp-client-socket</a></p>
<p>
</p>
<div>有关端口的更详细解释，请参考《UNIX网络编程卷1：套接字联网API（第3版）》2.9节 端口号部分。</div>
<p>&nbsp;</p>
<p>有关端口的问题，到此为止，下一篇，回到测试。</p><img src ="http://www.blogjava.net/yongboy/aggbug/397594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yongboy/" target="_blank">nieyong</a> 2013-04-09 17:26 <a href="http://www.blogjava.net/yongboy/archive/2013/04/09/397594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>100万并发连接服务器笔记之准备篇</title><link>http://www.blogjava.net/yongboy/archive/2013/04/09/397559.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Tue, 09 Apr 2013 01:50:00 GMT</pubDate><guid>http://www.blogjava.net/yongboy/archive/2013/04/09/397559.html</guid><wfw:comment>http://www.blogjava.net/yongboy/comments/397559.html</wfw:comment><comments>http://www.blogjava.net/yongboy/archive/2013/04/09/397559.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/yongboy/comments/commentRss/397559.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yongboy/services/trackbacks/397559.html</trackback:ping><description><![CDATA[<h2>前言</h2>
<p>测试一个非常简单服务器如何达到100万（1M=1024K连接）的并发连接，并且这些连接一旦连接上服务器，就不会断开，一直连着。 <br />
环境受限，没有服务器，刚开始都是在自己的DELL笔记本上测试，凭借16G内存，和优秀的vmware workstation虚拟机配合，另外还得外借别人虚拟机使用，最终还得搭上两台2G内存的台式机（安装centos），最终才完成1M并发连接任务。 </p>
<ul>
     <li>测试程序也很简陋，一个C语言所写服务器程序，没有任何业务存在，收到请求后发送一些头部，不断开连接</li>
     <li>测试端程序也是使用C语言所写，发送请求，然后等待接收数据，仅此而已</li>
     <li>服务器端/测试端内存都受限(8G不够使用)，要想完成1024K的目标，需要放弃一些东西，诸如业务不是那么完整</li>
     <li>一台分配10G内存Centos服务器，两台分配6G内存Centos测试端，两台2G内存Centos测试端</li>
     <li>假如热心的您可以提供丰富的服务器资源，那就再好不过了。</li>
     <li>理论上200万的并发连接（IO密集型），加上业务，40G-50G的内存大概能够保证</li>
</ul>
<h2>说明</h2>
<p>以前也做过类似的工作，量不大，没记录下来，一些压力测试和调优，随着时间流逝，早已忘记。这次是从零开始，基本上所有过程都会记录，一步一步，每一步都会遇到问题，并且给出相关解决问题的方法，最终完成目标。 <br />
为了方便，服务器端程序和客户端测试程序，都是使用C语言，不用像JAVA一样需要预先指定内存，感觉麻烦。使用较为原始的语言来写，可以避免不必要的调优工作。这中间，可能会穿插Java代码的思考方式。</p>
<p>可能需要懂点Linux，C，Java，假如您有更好的做法，或者建议，请直接告知，谢谢。</p>
<h2>Linux系统</h2>
<p>测试端和服务器端都选用较为熟悉的64位Centos 6.4，32位系统最多支持4G内存，太受限。IO密集型应用，对CPU要求不是很高。另外服务器确保安装上gcc，那就可以开工了。 <br />
所有端系统一旦安装完之后，默认不做任何设置。</p>
<h3>服务器端程序</h3>
<p>服务器端程序依赖<a href="http://libev.schmorp.de/">libev</a>框架，需要提前编译，然后存放到相应位置。下面是具体服务器端代码： <br />
<script src="https://gist.github.com/yongboy/5318930.js"></script></p>
<p>编译</p>
<pre><code>gcc server.c -o server ../include/libev.a -lm
</code></pre>
<p>运行</p>
<pre><code>./server -p 8000
</code></pre>
<p>在源码中默认指定了8000端口，可以通过-p进行指定新的端口。
开启了8000端口进行监听请求，http协议处理类似于htmlfile chunked块编码传输。</p>
<h3>测试服务器端程序</h3>
<p>测试程序使用libevent框架，因其使用简单，提供丰富易用接口，但需要提前下载，手动安装：</p>
<pre><code>wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar xvf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure --prefix=/usr
make
make install
</code></pre>
<p>注意make和make install需要root用户。</p>
<h3>测试端程序</h3>
<p>client1.c 源码：</p>
<script src="https://gist.github.com/yongboy/5318994.js"></script>
<p>备注：这部分代码参考了<a href="http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3">A Million-user Comet Application with Mochiweb, Part 3</a> ，根据需要有所修改。</p>
<p>编译</p>
<pre><code>gcc -o client1 client1.c -levent
</code></pre>
<p>运行</p>
<pre><code>./client1
</code></pre>
<p>可能在64位系统会遇到找不到<strong>libevent-2.0.so.5</strong>情况，需要建立一个软连接</p>
<pre><code>ln -s /usr/lib/libevent-2.0.so.5 /lib64/libevent-2.0.so.5
</code></pre>
<p>即可自动连接IP地址为192.168.190.133:8000的服务器端应用。 </p>
<h2>第一个遇到的问题：文件句柄受限</h2>
<p><strong>测试端程序输出</strong></p>
<p>看看测试端程序client1输出的错误信息：</p>
<pre><code>Chunks: 798 Bytes: 402990 Closed: 0
Req: 192.168.190.133 -/test/900
Req: 192.168.190.133 -/test/1000
Chunks: 998 Bytes: 503990 Closed: 0
[warn] socket: Too many open files
[warn] socket: Too many open files
[warn] socket: Too many open files
</code></pre>
<p><strong>服务器端程序输出</strong></p>
<p>服务器端最后一条日志为</p>
<pre><code>online user 1018
</code></pre>
<p>两边都遇到了文件句柄打开的情况。 <br />
在服务器端查看已经连接，并且端口号为8000的所有连接数量：</p>
<pre><code>netstat -nat|grep -i "8000"|wc -l <br />1019
</code></pre>
<p>但与服务器端输出数量对不上，增加所有已经建立连接的选项：</p>
<pre><code>netstat -nat|grep -i "8000"|grep ESTABLISHED|wc -l <br />1018
</code></pre>
<p>那么剩下的一条数据到底是什么呢？</p>
<pre><code>netstat -nat|grep -i "8000"|grep -v ESTABLISHED
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN </code></pre>
<p>也就是server.c监听的端口，数量上对的上。 </p>
<p>在测试服务器端，查看测试进程打开的文件句柄数量</p>
<pre><code>lsof -n|grep client1|wc -l
1032
</code></pre>
<p>再次执行</p>
<pre><code>ulimit -n
1024
</code></pre>
<p>也是就是client1应用程序共打开了<em>1032</em>个文件句柄，而不是<em>1024</em>，为什么？ <br />
把当前进程所有打开的文件句柄保存到文件中，慢慢研究
lsof -n|grep client1 &gt; test<em>conn</em>finfo.txt </p>
<p>导出的文件可以参考：
<ahref="https: gist.github.="" com="" yongboy=""  5260773"="">https://gist.github.com/yongboy/5260773<br />
除了第一行，我特意添加上供友善阅读的头部列定义，也就是1032行信息，但是需要注意头部：</ahref="https:></p>
<pre><code>
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
client1 3088 yongboy cwd DIR 253,0 4096 800747 /home/yongboy/workspace/c_socket.io_server/test
client1 3088 yongboy rtd DIR 253,0 4096 2 /test_conn
client1 3088 yongboy txt REG 253,0 9697 799991 /home/yongboy/workspace/c_socket.io_server/test/test_conn_1
client1 3088 yongboy mem REG 253,0 156872 50404 /lib64/ld-2.12.so
client1 3088 yongboy mem REG 253,0 1922152 78887 /lib64/libc-2.12.so
client1 3088 yongboy mem REG 253,0 145720 76555 /lib64/libpthread-2.12.so
client1 3088 yongboy mem REG 253,0 47064 69491 /lib64/librt-2.12.so
client1 3088 yongboy mem REG 253,0 968730 26292 /usr/lib/libevent-2.0.so.5.1.9
client1 3088 yongboy 0u CHR 136,2 0t0 5 /dev/pts/2
client1 3088 yongboy 1u CHR 136,2 0t0 5 /dev/pts/2
client1 3088 yongboy 2u CHR 136,2 0t0 5 /dev/pts/2
client1 3088 yongboy 3u REG 0,9 0 4032 anon_inode
client1 3088 yongboy 4u unix 0xffff88007c82f3c0 0t0 79883 socket
client1 3088 yongboy 5u unix 0xffff880037c34380 0t0 79884 socket
client1 3088 yongboy 6u IPv4 79885 0t0 TCP 192.168.190.134:58693-&gt;192.168.190.133:irdmi (ESTABLISHED)
client1 3088 yongboy 7u IPv4 79889 0t0 TCP 192.168.190.134:58694-&gt;192.168.190.133:irdmi (ESTABLISHED)
client1 3088 yongboy 8u IPv4 79891 0t0 TCP 192.168.190.134:58695-&gt;192.168.190.133:irdmi (ESTABLISHED)
client1 3088 yongboy 9u IPv4 79893 0t0 TCP 192.168.190.134:58696-&gt;192.168.190.133:irdmi (ESTABLISHED)
</code></pre>
<p>可以看到文件句柄是从0u开始，0u上面的8个（5个mem + 3个启动）进程，1032 - 8 = 1024个文件句柄，这样就和系统限制的值吻合了。 </p>
<p>root用户编辑/etc/security/limits.conf文件添加：</p>
<pre><code>* soft nofile 1048576
* hard nofile 1048576
</code></pre>
<ul>
     <li>soft是一个警告值，而hard则是一个真正意义的阀值，超过就会报错。</li>
     <li>soft 指的是当前系统生效的设置值。hard 表明系统中所能设定的最大值</li>
     <li>nofile - 打开文件的最大数目</li>
     <li>星号表示针对所有用户，若仅针对某个用户登录ID，请替换星号</li>
</ul>
<p>注意： <br />
<strong>1024K x 1024 = 1048576K = 1M，1百万多一点。</strong></p>
<p>备注：测试端和服务器端都需要作此设置，保存退出，然后reboot即可生效。 </p>
<p>第一个问题，就这样克服了。再次运行 <strong>/client1</strong>测试程序，就不会出现受打开文件句柄的限制。但大概在测试端打开对外28200个端口时，会出现程序异常，直接退出。</p>
<blockquote>
<p>段错误</p>
</blockquote>
<p>这个也是程序没有处理端口不够用的异常，但可以通过增加端口进行解决。 </p>
<p>备注：
但测试端单机最多只能打开6万多个连接，是一个问题，如何克服，下一篇解决此问题，并且还会遇到文件句柄的受限问题。</p><img src ="http://www.blogjava.net/yongboy/aggbug/397559.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yongboy/" target="_blank">nieyong</a> 2013-04-09 09:50 <a href="http://www.blogjava.net/yongboy/archive/2013/04/09/397559.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>