Netty入门指南之传统通信的问题

作者简介 :☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏Netty应用专栏_Aomsir的博客-CSDN博客

文章目录

参考文献

前言

前一篇文章中,我学到了与Netty有关的基础知识,了解NIO这个非阻塞式IO,那今天我们来聊聊传统的网络通信开发方式以及它所存在的问题,也就是使用 Socket的方式。Socket是阻塞式的IO,我们要做通信肯定得涉及多线程或者线程池的方式,这两种方式对于Socket都不友好,都有问题,如下详细分析一下。

注意:由于我们平常开发都是面向Tomcat开发的,很少会有机会能够接触Socket编程

多线程版

下面是多线程版网络通信的情况。从图中可以明显看出,随着客户端请求服务端的增加,服务端为处理这些请求不断创建新线程,而这一过程缺乏充分的限制,会无节制的进行创建。每次虚拟机创建线程都需要与操作系统进行通信,这会耗费时间和占用内存,导致内存使用量不断上升。此外,当所创建的线程数量超过了CPU核心数时,CPU就不得不进行轮转处理,这将导致CPU占用率飙升。

java 复制代码
public class AomsirServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket(8080);

            Socket socket = null;
            while (true) {
                socket = serverSocket.accept();
                new Thread(new AomsirServerHandler(socket)).start();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

/**
 * 用于处理通信请求的线程
 */
class AomsirServerHandler implements Runnable {
    private Socket socket;

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

    public void run() {
        BufferedReader bufferedReader = null;
        try {

            bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            while (true) {
                String line = bufferedReader.readLine();
                if (line != null) {
                    System.out.println("line = " + line);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
java 复制代码
public class AomsirClient {
    public static void main(String[] args) {
        Socket socket = null;
        PrintWriter printWriter = null;
        try {
            socket = new Socket("127.0.0.1", 8080);
            printWriter = new PrintWriter(socket.getOutputStream());
            printWriter.write("send date to server ");
            printWriter.flush();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }
}

线程池版

在上述多线程版网络通信中,服务端以一种无限制的方式为每个请求创建新线程,这导致高并发下线程数量无限增加。为了解决这一问题,我们提出了一种解决方案,即使用线程池。这是一种基于池化思想的方法,通过在服务端启动时创建固定数量的线程(例如N个),来限制后续线程的创建。这N个线程将专用于后续请求的处理,不再创建新的线程。当一个请求完成处理后,线程将被放回线程池,以供后续请求使用。如果请求数量超过了线程池的线程数量,后续请求将进入队列等待。这一方法有效地解决了无节制创建线程的问题。

然而,尽管线程池解决了线程创建问题,它引入了新的潜在问题,即阻塞问题。举例来说,如果线程1分配给了客户端A,但客户端1在某一时刻发生阻塞,无法继续处理请求,线程1将不得不一直等待客户端A,无法返回线程池,导致服务端处理请求的效率降低。

java 复制代码
public class AomsirServer1 {

    // 创建线程池
    private static ExecutorService executorService;
    
    // 初始化线程池
    static{
        executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),20,
                         120L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1000));
    }

    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket(8080);

            Socket socket = null;
            while (true) {
                socket = serverSocket.accept();
                //new Thread(new SunsServerHandler(socket)).start();
                //线程池
                executorService.execute(new AomsirServerHandler(socket));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}


/**
 * 服务端处理客户端请求的线程
 */
class AomsirServerHandler implements Runnable {
    private Socket socket;

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

    public void run() {
        BufferedReader bufferedReader = null;
        try {

            bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            while (true) {
                String line = bufferedReader.readLine();
                if (line != null) {
                    System.out.println("line = " + line);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
java 复制代码
public class AomsirClient1 {
    public static void main(String[] args) {
        Socket socket = null;
        PrintWriter printWriter = null;
        try {
            socket = new Socket("127.0.0.1", 8080);
            printWriter = new PrintWriter(socket.getOutputStream());
            printWriter.write("send date to server ");
            printWriter.flush();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }
}

总结

在客户端-服务器结构中,使用BIO(阻塞I/O)进行网络通信时,无论是无限制地创建线程,还是通过线程池限制线程创建,都难以避免一个共同的问题:当客户端连接到服务器后,在一段时间内不进行通信,线程将被空闲浪费,导致资源的低效利用。

为了解决这个问题,我们可以采用NIO(非阻塞I/O)来处理网络通信。NIO允许服务器同时管理多个连接,而不需要为每个连接创建一个单独的线程。这使得服务器能够更高效地处理大量连接,减少了资源浪费。Netty是一个常见的工具,它底层使用了NIO,为我们开发者提供了更容易使用和管理的方式来构建高性能的网络应用

相关推荐
-$_$-几秒前
【黑马点评】 使用RabbitMQ实现消息队列——1.Docker与RabbitMQ环境安装
分布式·docker·rabbitmq
冷琴19967 分钟前
基于java+springboot的酒店预定网站、酒店客房管理系统
java·开发语言·spring boot
好好学习的人8 分钟前
SQL第12课——联结表
数据库·sql
程序员古德15 分钟前
系统架构设计师论文《论NoSQL数据库技术及其应用》精选试读
数据库·nosql
青云交24 分钟前
大数据新视界 --大数据大厂之 DataFusion:超越传统的大数据集成与处理创新工具
数据库·内存管理·apache hive·数据集成·大数据处理·datafusion·查询处理·powercenter
s_little_monster27 分钟前
【QT】QT入门
数据库·c++·经验分享·笔记·qt·学习·mfc
daiyang123...33 分钟前
IT 行业的就业情况
java
dreamlike_ocean36 分钟前
即将到来的Netty4.2版本模型的变化
netty
weixin_453965001 小时前
master节点k8s部署]33.ceph分布式存储(四)
分布式·ceph·kubernetes
爬山算法1 小时前
Maven(6)如何使用Maven进行项目构建?
java·maven