草庐IT

Java NIO Socket学习

dk168 2023-03-28 原文

前言

这周学习尼恩编著的《Netty、Redis、ZooKeeper高并发实战》, 这本书写的很不错,通过十几个例子带领大家去体会高并发如何实现, 这周我看了最基础的JavaNOI部分,读书的时候好像明白了作者写的内容,但是又体会不深,非得自己动手写一些书上得例子,有时候还要改动下例子,才能体会深刻,得出自己得结论。下面我们进入例子演示。

Blocking IO

首先我们会来一个Blocking IO的例子,也就是同步阻塞方式,在Java中,默认创建的socket都是阻塞的。
先来看Server端代码, 写一个继承ServerSocket的类

@Slf4j
public class BlockReceiveServer  extends ServerSocket {
    public BlockReceiveServer() throws Exception
    {
        super(SERVER_PORT);
    }

    public void startServer() throws Exception
    {
        while (true)
        {
            // server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的

            log.debug("server listen at:" + SERVER_PORT);
            Socket socket = this.accept();
            /**
             * 我们的服务端处理客户端的连接请求是同步进行的, 每次接收到来自客户端的连接请求后,
             * 都要先跟当前的客户端通信完之后才能再处理下一个连接请求。 这在并发比较多的情况下会严重影响程序的性能,
             * 为此,我们可以把它改为如下这种异步处理与客户端通信的方式
             */
            // 每接收到一个Socket就建立一个新的线程来处理它
            new Thread(new Task(socket)).start();
        }
    }

this.accept()是阻塞的,也就是说没有请求来的时候,它是停在这里的,有请求来了后,我们起一个新的线程来出来它。 这里定义了一个Task来出来客户端请求。

 class Task implements Runnable
    {

        private Socket socket;

        private DataInputStream dis;

        public Task(Socket socket)
        {
            this.socket = socket;
        }

        @Override
        public void run()
        {
            try
            {
                dis = new DataInputStream(socket.getInputStream());
                log.debug("start receive");
                for (int i = 0;i < 1000 ;i++)
                {
                    dis.readUTF();
                }

                log.debug("finish receive" );
            } catch (Exception e)
            {
                e.printStackTrace();
            } finally
            {

                IOUtil.closeQuietly(dis);
                IOUtil.closeQuietly(socket);

            }
        }
    }

书中的例子是传送文件,我这里把它改为接收客户端传过来的文本,就是客户端写一千次,服务端读一千次,这个也是写死的,因为阻塞,没有读完,你是不能进入下一个环节的,不然会出错。

客户端代码

@Slf4j
public class BlockSendClient extends Socket
{
    private Socket client;
    private DataOutputStream outputStream;

    /**
     * 构造函数<br/>
     * 与服务器建立连接
     *
     * @throws Exception
     */
    public BlockSendClient() throws Exception
    {
        super(SOCKET_SERVER_IP
                , SERVER_PORT);
        this.client = this;
    }

    /**
     * 向服务端传输文件
     *
     * @throws Exception
     */
    public void sendFile() throws Exception
    {
        try
        {
            outputStream = new DataOutputStream(client.getOutputStream());

            for (int i = 0;i < 1000 ;i++)
            {
                outputStream.writeUTF(String.valueOf(i));
                outputStream.flush();
                Thread.sleep(1000);
            }
            log.debug("======== file transfer success ========");
        } catch (Exception e)
        {
            e.printStackTrace();
        } finally
        {
            IOUtil.closeQuietly(outputStream);
            IOUtil.closeQuietly(client);

        }
    }

客户端就是继承了socket,然后往socket里面发送1000次数字。
为了体现效果,我把客户端也让它多线程的去和服务端连接。 这里启动50个线程去和服务端连接,如果启动100个,会报“java.net.ConnectException: Connection refused: connect”, 但是你多启动几个客户端去连接,是不会有问题的。


 public static void main(String[] args)
    {
        for(int i =0;i<50;i++){
            new Thread(new Task()).start();
        }
    }

class Task  implements Runnable
{

    @Override
    public void run() {
        try
        {
            BlockSendClient client = new BlockSendClient(); // 启动客户端连接
            client.sendFile();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

我们可以看到服务端的输出

我们打开多少客户端,那么服务端就必须要打开50倍的线程数来连接。 而线程能开的数量是有限制的,一个Java线程大约占1M的空间。 对于高并发,这种方式肯定是不可取的。

Java NIO

我们来看下如何用Java NOI 来写一个和上面一样的功能。先来看下服务端代码

@Slf4j
public class NioDiscardServer {

    public static void startServer() throws IOException {

        // 1、创建一个 Selector选择器
        Selector selector = Selector.open();

        // 2、获取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 3.设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 4、绑定监听端口
        serverSocket.bind(new InetSocketAddress(SERVER_PORT));
        log.info("server start success");

        // 5、将通道注册到选择器上,并注册的IO事件为:“接收新连接”
        SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //  sk.interestOps(SelectionKey.OP_ACCEPT) ;

        // 6、轮询感兴趣的I/O就绪事件(选择键集合)
        while (selector.select() > 0) {
            // 7、获取选择键集合
            Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();

            while (selectedKeys.hasNext()) {
                // 8、获取单个的选择键,并处理
                SelectionKey selectedKey = selectedKeys.next();

                // 9、判断key是具体的什么事件
                if (selectedKey.isAcceptable()) {
                    log.info("new connection coming" + selectedKey.channel());
                    ServerSocketChannel server = (ServerSocketChannel) selectedKey.channel();
                    SocketChannel socketChannel = server.accept();
                    // 10、若选择键的IO事件是“连接就绪”事件,就获取客户端连接

                    // 11、切换为非阻塞模式
                    socketChannel.configureBlocking(false);
                    socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);

                    // 12、将该通道注册到selector选择器上
                    SelectionKey channleSk=        socketChannel.register(selector,
                            SelectionKey.OP_READ | SelectionKey.OP_WRITE  | SelectionKey.OP_CONNECT);


                }
                if (selectedKey.isWritable()) {
                    //log.info("write ready:" + selectedKey.channel());
                }
                if (selectedKey.isConnectable()) {
                    log.info("client connect success:" + selectedKey.channel());

                }
                if (selectedKey.isReadable()) {
                    log.info("read ready:" + selectedKey.channel());

                    // 13、若选择键的IO事件是“可读”事件,读取数据
                    SocketChannel socketChannel = (SocketChannel) selectedKey.channel();

                    // 14、读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int length = 0;
                    while ((length = socketChannel.read(byteBuffer)) > 0) {

                        byteBuffer.flip();

                        log.info(new String(byteBuffer.array(), 0, length));

                        byteBuffer.clear();

                    }

                    if(length == -1)
                    {
                        socketChannel.close();
                    }
                }

                // 15、移除选择键
                selectedKeys.remove();
            }
        }
        // 7、关闭连接
        serverSocketChannel.close();
    }

    public static void main(String[] args) throws IOException {
        startServer();
    }
}

客户端代码如下


@Slf4j
public class NioDiscardClient {
    public  void startClient(int clientNum) throws IOException {
        InetSocketAddress address =
                new InetSocketAddress(SOCKET_SERVER_IP,
                        SERVER_PORT);

        // 1、获取通道(channel)
        SocketChannel socketChannel = SocketChannel.open(address);
        // 2、切换成非阻塞模式
        socketChannel.configureBlocking(false);
        //不断的自旋、等待连接完成,或者做一些其他的事情
        while (!socketChannel.finishConnect()) {

        }

        log.info("client connect success");
        for(int i = 0;i<1000;i++) {
            // 3、分配指定大小的缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put((clientNum + "hello" + i).getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);

            ThreadUtil.sleepSeconds(1);
        }
        log.info("client write success");

        socketChannel.shutdownOutput();
        socketChannel.close();
    }

    public static void main(String[] args) throws IOException {
        for(int i =0;i<50;i++){
            new Thread(new NoiTask(i)).start();
        }
    }

}

class NoiTask  implements Runnable
{
    private int clientNum;
    public NoiTask(int clientNum)
    {
        this.clientNum = clientNum;
    }

    @Override
    public void run() {
        try
        {
            NioDiscardClient client = new NioDiscardClient(); // 启动客户端连接
            client.startClient(clientNum); // 传输文件
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

代码这里不解释了。 这里服务端和客户端不需要约定多少内容要发送,
服务端有段代码需要注意
while ((length = socketChannel.read(byteBuffer)) > 0),这段很容易就退出循环,因为read是异步的,很容易就返回0, 让循环中断, 开始的时候socketChannel.close()没有加上条件,服务端打印出几次后,就close了,客户端就再写不进去了。报“java.io.IOException: An established connection was aborted by the software in your host machine”,因为服务端关闭了。
服务端的输出我们可以看出,它只启动了一个main线程,不论多少个客户端连接过来。

我再用CMD启动多个进行客户端进行测试的时候,发现它会启用300多M的内存,后面多加几个客户端,它也不会增长,保持再这个数字上面。 前面的例子中,内存使用的要小,但是增加客户端后,内存使用量会增加,但是也不是很明显。 这里面为什么会用这么多内存,我不是太明白,也许代码有什么缺陷在这里,暂时不细究了。

总结

我在使用NIO的时候,发现它在单机测试性能的时候不比传统的阻塞式的优秀,甚至还慢一些。它的应用场景是在量大的时候,高并发的时候。具体NIO的原理我也解释不清楚,只能从应用的层面来用例子实践一下,后面看能不能再深入下它的原理,书中讲述的很好,但是我只能说大致理解,谈不上深入。

有关Java NIO Socket学习的更多相关文章

  1. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  3. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  4. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  5. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  6. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  7. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  8. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  9. ruby-on-rails - 这个 C 和 PHP 程序员如何学习 Ruby 和 Rails? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它

  10. jquery - 使用 Rails 3 学习 Ajax 的资源 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。有没有学习Ajax(jQuery)和Rails3的好资源?

随机推荐