本系列主要介绍java网络编程的模型,沿着模型的进化线结合案例分析学习。本文主要介绍基于OIO的网络编程模型。
基于BIO(OIO)的网络编程模型
BIO(OIO)是指阻塞输入输出流(旧输入输出流),基于阻塞输入输出流的网络编程初始具有以下特点:
1、服务端启动后会阻塞直到监测到有客户端连接;
2、客户端发起连接请求,获取输入流读取数据,如果没有数据可读取将会一直处于阻塞状态;
3、所有连接处理都在一个线程中进行,因此在连接数较多时,连接请求需要排队等候;
案例——服务端:
package javanio.oionet;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
/**
* @author 54353
* OIO网络模型服务端:
* 向客户端写入服务端接收时间
*/
public class TestServer {
public static void main(String[] args) {
try (ServerSocket serverSocket=new ServerSocket(12121)){
while(true) {//1 循环等待连接
//2 accept()阻塞直到有连接进来,返回对等端socket对象
Socket client=serverSocket.accept();
System.out.println("接收到客户端请求!!");
Writer bWriter=new OutputStreamWriter(client.getOutputStream(),"ASCII");
Date date=new Date();
//3 输出流中写入时间
while(true) {//确保任务不停进行
bWriter.write(date.toString()+'\r'+'\n');
//4 确保数据写入
bWriter.flush();
System.out.println("数据传输完成!");
}
}
} catch (Exception e) {
System.out.println("端口可能被占用!");
}
}
}
案例——客户端
package javanio.oionet;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class TestClient2 {
public static void main(String[] args) {
Socket socket=null;
try {
socket=new Socket("127.0.0.1", 12121);
InputStreamReader reader=new InputStreamReader(socket.getInputStream(),"ASCII");
System.out.println("读取服务端传送过来的数据!");
for(int c=reader.read();c!=-1;c=reader.read()) {
System.out.print((char)c);
}
} catch (UnknownHostException e) {
System.out.println("请求的主机地址不存在!");
}catch (Exception e) {
e.printStackTrace();
}finally {
if (socket!=null) {
try {
socket.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
}
这里客户端同样的demo有三个,启动服务端,启动客户端后,服务端输出结果如下:
server.png
客户端2输出结果如下:
client2.png
客户端3输出结果如下:
client3.png
TestClient2,TestClient3启动后,TestClient2不断输出服务端写入的时间,TestClient3却没有输出任何的时间数据。这是因为所有客户端连接共享一个服务端线程,服务端在不断向TestClient2输出数据,TestClient3必须等到TestClient2的请求结束,服务端向其输出数据后才能输出时间数据。这也是本文一开始提到的第三个特点。
针对这种模式存在的缺陷,基于OIO提出了多线程的模型,也即是服务端针对每一个请求开启一个新的线程去处理数据:
服务端的改进:
package javanio.oionet;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
/**
* @author 54353
* OIO网络模型服务端:
* 向客户端写入服务端接收时间
*/
public class TestServer2 {
static class Task implements Runnable{
private Socket client;
public Task(Socket client) {
this.client=client;
}
@Override
public void run() {
System.out.println("接收到客户端请求!!");
Writer bWriter;
try {
bWriter = new OutputStreamWriter(client.getOutputStream(),"ASCII");
Date date=new Date();
//4 输出流中写入时间
while(true) {//任务不断进行
bWriter.write(date.toString()+'\r'+'\n');
//5 确保数据写入
bWriter.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try (ServerSocket serverSocket=new ServerSocket(12122)){
while(true) {//1 循环等待连接
//2 accept()阻塞直到有连接进来,返回对等端socket对象
Socket client=serverSocket.accept();
//3 一旦有连接进来,开启新的线程处理连接
new Thread(new Task(client)).start();
}
} catch (Exception e) {
System.out.println("端口可能被占用!");
}
}
}
在运用多线程处理连接后,输出结果下:
服务端输出
server2.png
几个客户端输出结果相同
client2.png
client3.png
由此可见,运用多线程OIO模型可以解决,多请求同时发生时的排队等候问题,可以带来更好的客户体验。但是如果线程数过多,对内存的消耗大,同时线程数过多,线程轮转带来的消耗也会非常大,因此肆无忌惮的使用多线程去处理并发请求问题只能算是一种蛮干,对于高并发场景,更是会带来灾难性危害。
关于Reactor模式
Reactor模式也即响应模式,就本文所涉及的网络编程而言,只有获取到客户端的连接请求后,服务端才执行后续的程序。对于OIO阻塞输入输出流,显然还存在一个明显缺陷:如果一直未接收到客户端请求,服务端一直处于阻塞状态,后面与接入无关的代码也需要等待,这显然会浪费很多的CPU时间,因此从这点考虑,多线程的OIO模式仍存在进步空间。
网友评论