作者简介:☕️大家好,我是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);
}
}
}
}
}
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,无法返回线程池,导致服务端处理请求的效率降低。
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);
}
}
}
}
}
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,为我们开发者提供了更容易使用和管理的方式来构建高性能的网络应用