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


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

    参考文献

    前言

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

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

    多线程版

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

    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);
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    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();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    线程池版

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

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

    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);
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    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();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    总结

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

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

  • 相关阅读:
    虚拟内存系统【页面置换算法】
    服务器网站崩溃怎么解决
    Linux 常用文件和目录操作 (cat less head tail等)
    基于Java的剧本杀预约系统设计与实现(源码+lw+部署文档+讲解等)
    栓Q八股文: C++ 14/17 新特性
    Linux--线程(与进程区别)
    模板、外观、观察者、建造者
    Redis 访问控制列表(ACL)
    vue-router配置
    动态页面调研及设计方案
  • 原文地址:https://blog.csdn.net/qq_43266723/article/details/134245730