C10K 问题

C10K 问题: http://www.kegel.com/c10k.html
我们使用BIO的时候,来一个连接就抛出一个线程。被抛出的独立的线程进行阻塞,等待接收已连接的client发来的数据,这样不会影响其他client继续连接。每个线程自己忙自己的。
但是随着连接数的变大,抛出的线程越多,由于线程之间的切换,系统的性能会越来越低。

举一个例子

一个客户端可以通过2个不同的ip,与服务端创建2*65000个连接。

C10Kclient.java

package com.bjmashibing.system.io;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;public class C10Kclient {public static void main(String[] args) {LinkedList<SocketChannel> clients = new LinkedList<>();InetSocketAddress serverAddr = new InetSocketAddress("192.168.150.11", 9090);for (int i = 10000; i < 65000; i++) {  // 一个客户端可以通过2个不同的ip,与服务端创建2*65000个连接try {SocketChannel client1 = SocketChannel.open();SocketChannel client2 = SocketChannel.open();/*linux中你看到的连接就是:client...port: 10508client...port: 10508*/client1.bind(new InetSocketAddress("192.168.150.1", i));  // 这台机器上的第一个ip//  192.168.150.1:10000   192.168.150.11:9090client1.connect(serverAddr);boolean c1 = client1.isOpen();clients.add(client1);client2.bind(new InetSocketAddress("192.168.110.100", i));  // 这台机器上的第二个ip//  192.168.110.100:10000  192.168.150.11:9090client2.connect(serverAddr);boolean c2 = client2.isOpen();clients.add(client2);} catch (IOException e) {e.printStackTrace();}}System.out.println("clients " + clients.size());try {System.in.read();} catch (IOException e) {e.printStackTrace();}}
}

关于为什么需要在Linux上单独配一个路由条目

在Linux上单独配一个路由条目

因为物理机的192.168.110.100和虚拟机的192.168.150.0不是直连关系
192.168.150.2是虚拟机所在网络的网关,用于完成网络地址转换。
来自192.168.110.100的网络包在返回给客户端的时候,经过NAT地址转换,目标ip被改成了192.168.150.2,Windows收到之后不知道这个ip应该发给谁了,导致三次握手的第二次回的包被windows丢弃了,没有连接上。

关于为什么连接的速度并不快?(一秒钟仅能够建立4个连接左右)

为什么BIO慢?
创建一个连接的过程如下图所示。

  • accept系统调用,是一个阻塞的循环过程,这个过程耗费时间。
  • 抛出一个线程的速度比较慢。

    这就是整个BIO的弊端。想要调优,就要解决阻塞的问题,但阻塞是由内核提供给我们的API决accept receive定的。

NIO 的引入

NIO的N是啥意思呢?有两个角度可以理解

  • Non-Blocking IO (操作系统中)
  • New IO (JDK中)

Linux中的文件描述符是输入输出双向的。
Java中ServerSocketChannel也是淡化了输入、输出的概念,把输入、输出合在一起了。

一段NIO的代码
SocketNIO.java

package com.bjmashibing.system.io;import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;public class SocketNIO {public static void main(String[] args) throws Exception {LinkedList<SocketChannel> clients = new LinkedList<>();ServerSocketChannel ss = ServerSocketChannel.open();ss.bind(new InetSocketAddress(9090));ss.configureBlocking(false); //设置false时,是非阻塞。OS层面的NONBLOCKING,不会阻塞地去等待连接。//        ss.setOption(StandardSocketOptions.TCP_NODELAY, false);
//        StandardSocketOptions.TCP_NODELAY
//        StandardSocketOptions.SO_KEEPALIVE
//        StandardSocketOptions.SO_LINGER
//        StandardSocketOptions.SO_RCVBUF
//        StandardSocketOptions.SO_SNDBUF
//        StandardSocketOptions.SO_REUSEADDRwhile (true) {//接受客户端的连接,不会阻塞Thread.sleep(1000);SocketChannel client = ss.accept(); //开始接收客户端。不会阻塞等待连接,如果得不到连接,则返回 -1,NULL// ss.accept();方法调用内核了,有下面这些情况:// 1,没有客户端连接进来,返回值是啥呢?在 BIO 的时候一直卡着,但是在NIO的时候不会卡着,返回的是-1,NULL// 2、有客户端的连接,ss.accept();返回的是这个客户端的fd 5(OS层面),Client对象(java层面)if (client == null) {System.out.println("null.....");// 可以不处理它,不要把这段当做性能消耗去思考。} else {client.configureBlocking(false); //重点// socket有两个角度:// 1、服务端的listen socket<连接请求三次握手后,往我这里扔,我去通过accept,得到后面的连接的socket>// 2、服务端接受客户端连接进来之后,形成的连接socket<连接后的数据读写使用的>int port = client.socket().getPort();System.out.println("client...port: " + port);clients.add(client);}//执行读取行为:遍历已连接的客户端去读写数据,这个过程不会阻塞ByteBuffer buffer = ByteBuffer.allocateDirect(4096);  //直接内存分配,可以在堆里分配,也可以在堆外分配for (SocketChannel c : clients) {   //串行化!!!!  多线程!!int num = c.read(buffer);  // 返回值 >0  -1  0  不会阻塞if (num > 0) {buffer.flip();byte[] aaa = new byte[buffer.limit()];buffer.get(aaa);String b = new String(aaa);System.out.println(c.socket().getPort() + " : " + b);buffer.clear();}}}}
}

运行起来

ss.configureBlocking(true);阻塞状态下

ss.configureBlocking(false);非阻塞状态下

非阻塞了,这有什么意义?
假设这时候有两个客户端连接进来了,其中一个的示例是下面这个样子的:


曾经我们是需要抛出一个线程,把这个线程扔出去,让它自己去读取数据。
现在不需要抛线程了,完全在一个线程中执行,只不过是无数个循环。而在没有连接的时候,循环也不会被阻塞,依然继续循环,从而让循环后半部分的读取数据的逻辑就有机会在当前循环当中被执行到

再用C10K压测一下

用C10K压测一下NIO的性能(单客户端10W连接):大约每秒钟能建立50个左右的连接
现在使用NIO的瓶颈是:当连接进来很多客户端时,for (SocketChannel c : clients)遍历每一个客户端去读取数据的过程耗费了性能。


另外,报错超出文件描述符的数量,这个是可以设置的:ulimit -SHn 500000(软硬openfile,改成50万)

注:为啥ulimit -n 1024,但是连接数超过了1024呢?
这个理论是对的,只不过要看用户。权限对root来说等于虚设,很多资源的约束在root用户也是放开的。而且在公司里,生产环境肯定是非root用户启动程序。

网络与IO知识扫盲(四):C10K问题、BIO的弊端与NIO的引入相关推荐

  1. 网络与IO知识扫盲(三):从系统调用的角度,剖析 Socket 的连接过程、BIO 的连接过程

    Socket的连接过程.TCP的一些参数 前置知识 用到的命令 netstat -natp 查看网络连接和占用的端口 tcpdump -nn -i eth0 port 9090 开监听抓取数据包 ls ...

  2. 网络与IO知识扫盲(六):多路复用器

    NIO存在的问题 NIO的优势:可以通过一个或几个线程来解决N个IO连接的处理 NIO存在C10K问题:当客户端连接的数量达到10K时,单线程每循环一次所有的fd,成本是O(n)复杂度,每一次循环都会 ...

  3. 网络与IO知识扫盲(一):Linux虚拟文件系统,文件描述符,IO重定向

    系统IO原理 在 Linux 中: VFS(Virtual Filesystem Switch):虚拟文件系统,是一个目录树.树上不同的节点可以映射到物理的文件地址,可以挂载. 相当于一个解耦层,在具 ...

  4. 网络与IO知识扫盲(五):从 NIO 到多路复用器

    NIO 的优劣 优势:相比 BIO 来说,NIO 可以通过1个或几个线程,来解决 N 个 IO 连接的处理 弊端:当有大量文件描述符存在时,不管你用多少个线程,都是O(n)复杂度的recv调用,需要用 ...

  5. 网络与IO知识扫盲(七):仿照Netty工作架构图,手写多路复用模型

    Netty工作架构图 从图上看来: 一个线程在 Boss Group 中负责接收 另外两个线程在 Worker Group 中由接收之后的连接分配过去,负责读写 根据上图模型,仿照Netty手写一个多 ...

  6. 多实例多进程网络编程PHP,php socket网络编程基础知识(四):多进程

    标签:status   传递   windows   返回   修改   队列   _for   响应   关联 说明 php在web编程时是不需要考虑多进程的,但整个php流程是涉及到多进程的,只不 ...

  7. Lwip从入门到放弃之(一)---基础网络知识扫盲

    Lwip从入门到放弃之-基础网络知识扫盲(一) 由于工作中用到了有关Lwip的有关知识,本人作为一个网络通信协议的门外汉,打算系统的学习一下以太网通讯的有关知识.而Lwip作为一款开源的轻量级TCP/ ...

  8. 网络知识扫盲,一文搞懂 DNS

    在找工作面试的过程中,面试官非常喜欢考察基础知识,除了数据结构与算法之外,网络知识也是一个非常重要的考察对象. 而网络知识,通常是很抽象,不容易理解的,有很多同学就在这里裁了跟头.为了更好地通过面试, ...

  9. android进阶3step2:Android App通信 ——端口号IP等网络基础知识扫盲

    网络操作基础知识 一.IP 地址和端口号 1) IP 地址用于在网络中唯一标识一台机器(通信实体),是一个 32 位整数,通常 用 4 个 0-255 的十进制数标识; 2) 端口号用于唯一标识通信实 ...

最新文章

  1. vectornator安卓_Vectornator Pro
  2. 转 Python爬虫入门一之综述
  3. 后端需要掌握的技术_何小伟:软件测试需要掌握的技术?
  4. Cell重磅发现:人类胎儿全身组织中都含有活细菌,这些细菌激发了胎儿的免疫发育...
  5. Linux的Application 内存模型---
  6. 修改时无论改成什么,值总是默认为1
  7. JDK 安装 Java环境变量配置
  8. kux-mp4转码Python3脚本
  9. 苹果电脑 / Mac 开机密码忘记了应该如何操作?
  10. cad.net 图层隐藏 IsHidden 用法 eDuplicateRecordName 报错
  11. 将微信聊天记录转成txt文件的最实用方法
  12. Android app 内存分配
  13. Error creating bean with name 'servletEndpointRegistrar' defined in class path resource
  14. 【Moasure魔尺】优秀景观设计师们悄悄在用的测量设备
  15. SEM测试成像原理与消像散
  16. 蜂窝物联网技术一览-Cat-1,Cat-0,Cat-M1,NB-IoT
  17. TIBCO Spotfire: JavaScript可视化框架 - JSViz
  18. 可通过HTTP获取远端WWW服务信息
  19. MapReduce 运行原理(万字长篇 原理 + 案例)
  20. 如何撰写项目的解决方案

热门文章

  1. CH - 6901 骑士放置(二分图最大独立集-二分图最大匹配+奇偶拆点)
  2. 数据分析实战-PUBG数据集EDA
  3. 习题11-7 奇数值结点链表 (20 分) -链表
  4. 最简单的基于FFmpeg的AVDevice例子(读取摄像头)
  5. Web服务器面临的五种应用层DOS威胁
  6. C++设计模式之代理模式
  7. GetModuleHandle,AfxGetInstanceHandle使用区别
  8. Android 根证书管理与证书验证
  9. MySQL(一): 数据类型、库的操作、表的操作
  10. 从零开始玩转JMX(四)——Apache Commons Modeler Dynamic MBean