文章目录
- 多线程版网络编程
- 客户端
- MyServerThread
- 服务端
- 线程池版的网络编程
- 客户端
- MyServerThread
- 服务端
- 总结
传统网络通信中的开发方式及问题
多线程版网络编程
下面先写一个多线程版网络编程的版本代码:
客户端
public static void main(String[] args) throws IOException {
Socket socket;
for (int i = 0; i < 5; i++) {
socket = new Socket("127.0.0.1", 8899);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
printWriter.write("hello , 我是客户端 " + i);
printWriter.flush();
socket.close();
}
}
MyServerThread
public class MyServerThread implements Runnable {
private Socket socket;
public MyServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
while( true ){
String s = bufferedReader.readLine();
if( s == null ){
break;
}
System.out.println(Thread.currentThread().getName() + " "+s);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8899);
Socket socket = null;
while (true){
socket = serverSocket.accept();
new Thread( new MyServerThread(socket) ).start();
}
}
上面的代码存在的问题:
- 线程创建开销;
- 内存占用高,不能无限制的创建线程;
- CPU使用率高;
线程池版的网络编程
客户端
public static void main(String[] args) throws IOException {
Socket socket;
for (int i = 0; i < 30; i++) {
socket = new Socket("127.0.0.1", 8899);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
printWriter.write("hello , 我是客户端 " + i);
printWriter.flush();
socket.close();
}
}
MyServerThread
public class MyServerThread implements Runnable {
private Socket socket;
public MyServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
while( true ){
String s = bufferedReader.readLine();
if( s != null ){
System.out.println(Thread.currentThread().getName() + " "+s);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
public class Server {
static ThreadPoolExecutor threadPoolExecutor;
// 线程池
static {
threadPoolExecutor = new ThreadPoolExecutor(5, 10, 120,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(20));
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8899);
Socket socket = null;
while (true){
socket = serverSocket.accept();
//new Thread( new MyServerThread(socket) ).start();
threadPoolExecutor.execute( new MyServerThread(socket) );
}
}
}
上面的代码是以线程池的方式解决之前的问题. 解决了线程开销问题和CPU占用率高的问题.
新的问题: 因为阻塞, 造成有限线程资源的浪费;
总结
与 Java NIO(New Input/Output)相比,传统网络编程(主要指的是基于阻塞 I/O 的模型)有一些明显的问题和局限性:
- 线程管理开销
传统网络编程: 每个客户端连接通常会分配一个独立的线程。对于高并发场景,这种方法会导致线程数量急剧增加,从而增加线程上下文切换的开销和系统资源消耗。
NIO: 通过使用选择器(Selectors)和非阻塞 I/O,可以用少量的线程处理大量的连接,显著降低了线程管理的开销。 - 阻塞 I/O
传统网络编程: 使用阻塞 I/O 模型时,线程在等待数据读写时会被阻塞,这可能导致资源的浪费和响应时间的增加。
NIO: 支持非阻塞 I/O 模型,使线程可以在等待 I/O 操作完成的同时执行其他任务,从而提高了效率。 - 扩展性
传统网络编程: 处理大量并发连接时,系统可能会遇到性能瓶颈和资源限制,特别是在处理大量 I/O 操作时。
NIO: 设计用于支持大规模的网络应用,可以高效地管理和调度大量的连接,具有更好的扩展性。 - 资源利用效率
传统网络编程: 在处理高并发连接时,由于线程阻塞和切换的开销,系统资源(如 CPU 和内存)的利用效率较低。
NIO: 通过减少线程的数量和避免线程阻塞,提高了系统资源的利用效率。 - 编程复杂性
传统网络编程: 编写和管理基于阻塞 I/O 的网络代码相对简单,但在高并发场景下可能难以管理和扩展。
NIO: 虽然提供了更高效的 I/O 模型,但其 API 更复杂,需要开发者理解选择器、通道和缓冲区的工作机制,增加了编程难度。 - 错误处理
传统网络编程: 错误处理通常较为简单,但在处理大量并发连接时,可能会遇到性能瓶颈和资源耗尽问题。
NIO: 错误处理可能更为复杂,需要处理各种异步事件和状态,增加了调试和维护的难度。 - 适应性
传统网络编程: 在设计上可能不容易适应高并发和大规模数据传输的需求。
NIO: 设计目标包括高并发和高性能,能够更好地适应现代网络应用的需求。
传统网络编程在处理高并发和大规模应用时通常面临性能瓶颈和资源管理问题,而 NIO 通过非阻塞 I/O 和选择器机制提供了更高效的解决方案。虽然 NIO 的 API 更复杂,但在高负载和高性能需求的场景下,它能够显著改善传统阻塞 I/O 的不足。