工作机制
Java BIO是Java传统的IO编程,类和接口在java.io下。
同步阻塞型IO,如果大量的连接都处于阻塞状态,会造成服务器大量线程资源浪费,使用线程池能做改良。
//服务端
1.启动ServerSocket注册端口
2.调用accept监听客户端的Socket请求
3.从Socket中获取字节输入流或输出流
//客户端
1.通过Socket对象请求与服务端的连接
2.从Socket管道中获取字节输入流或者输出流进行数据的读写操作
BIO阻塞案例
客户端
public static void main(String[] args) {
Socket socket = null;
try {
//创建Socket对象请求服务端的连接
socket = new Socket("127.0.0.1",8192);
OutputStream os = socket.getOutputStream();
//把Socket字节输入流包装成打印流
PrintStream ps = new PrintStream(os);
//ps.print("你好!服务端。");没有换行-阻塞
ps.println("你好!服务端。");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();//关闭客户端套接字
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
public static void main(String[] args) {
ServerSocket ss = null;
try {
//定义一个ServerSocket对象进行服务端代码注册
ss = new ServerSocket(8192);
//监听客户端请求,获取客户端套接字
Socket socket = ss.accept();
//从Socket管道中得到字节输入流对象
InputStream is = socket.getInputStream();
//利用转换流和字符处理流封装客户端传过来的流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
//服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态
if ((line = br.readLine()) != null) {
System.out.println(line);
}
socket.close();//断开连接
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO多客户端
案例分析:
启动服务端,服务端可以接受多个客户端的请求。需要引入多线程处理服务端,实现为一个客户端请求分配
一个线程。
程序存在缺陷:
1.线程之间上下文切换会影响性能。
2.每个线程都会占用CPU资源和栈空间。
3.可能存在无意义的客户端连接占用资源。
4.服务端并发抗压能力弱。
客户端
public static void main(String[] args) {
Socket socket = null;
try {
//创建Socket对象请求服务端的连接
socket = new Socket("127.0.0.1",8192);
OutputStream os = socket.getOutputStream();
//把Socket字节输入流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入信息...");
String msg = scanner.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();//关闭客户端套接字
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
public static void main(String[] args) {
ServerSocket ss = null;
try {
//定义一个ServerSocket对象进行服务端代码注册
ss = new ServerSocket(8192);
//不断的监听客户端的请求
while (true) {
//监听客户端请求,获取客户端套接字
Socket socket = ss.accept();
//创建线程对象接收当前客户端的Socket对象
new Thread(() -> {
try {
//从Socket管道中得到字节输入流对象
InputStream is = socket.getInputStream();
//利用转换流和字符处理流封装客户端传过来的流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
//服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态
while ((line = br.readLine()) != null) {
System.out.println(line);
}
socket.close();//断开连接
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
伪异步IO
采用线程池和任务队列实现将客户端的Socket封装成一个Task(Runnable对象)交给后端的线程池进行处理。
JDK线程池维护一个消息队列和N个活跃线程,对消息队列中的Socket任务进行处理,由于线程可以设置
消息队列的大小和线程池的最大线程数,资源可控可以很好的避免服务器在高并发情况下宕机。
存在问题:
1.伪异步IO采用线程池实现,避免了线程数量开销大的问题,但底层由于使用同步阻塞IO模型,无法从根本上解决。
2.当服务器线程池被占满,其他任务会进入任务队列进行等待(被阻塞),如果有一个线程断开了与服务器的
连接,那么服务器会抛出异常,然后任务队列中的下一个任务抢到线程,连接上服务器。
服务端
//服务端主程序
public static void main(String[] args) {
ServerSocket ss = null;
try {
//创建服务端套接字对象,注册端口
ss = new ServerSocket(8192);
//循环监听客户端的Socket请求
//初始化一个线程池对象
HandlerSocketServerPool pool
= new HandlerSocketServerPool(3,10);
while (true) {
Socket socket = ss.accept();
//把socket封装成任务添加入任务队列
ServerRunnableTarget target = new ServerRunnableTarget(socket);
pool.execute(target);//线程池一旦有空闲线程就触发run方法执行任务
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端线程池类
public class HandlerSocketServerPool {
//创建一个线程池的成员变量用于存储一个线程池对象
private ExecutorService executorService;
//创建这个类的对象的时候就需要初始化线程池对象
/**
* ThreadPoolExecutor(int corePoolSize, 核心线程数
* int maximumPoolSize, 最大线程数
* long keepAliveTime, 线程空闲时间
* TimeUnit unit, 时间单位
* BlockingQueue<Runnable> workQueue) 任务队列
*/
public HandlerSocketServerPool(int maxThreadNum, int queueSize) {
executorService = new ThreadPoolExecutor(3,
maxThreadNum,
120,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueSize));
}
//提供一个方法来提交任务给线程池的任务队列来暂存,等待线程池处理
public void execute(Runnable target) {
executorService.execute(target);//如果未达到最大线程数就执行run方法处理任务
}
}
//服务端任务包装类 - 把Socket包装成Task放入任务队列
public class ServerRunnableTarget implements Runnable {
//接收Socket对象
private Socket socket;
public ServerRunnableTarget(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//处理接收到的Socket通信需求,从Socket管道中得到字节输入流对象
InputStream is = socket.getInputStream();
//利用转换流和字符处理流封装客户端传过来的流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
//服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
public static void main(String[] args) {
Socket socket = null;
try {
//创建Socket对象请求服务端的连接
socket = new Socket("127.0.0.1",8192);
OutputStream os = socket.getOutputStream();
//把Socket字节输入流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入信息...");
String msg = scanner.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();//关闭客户端套接字
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件上传
服务端
public static void main(String[] args) {
ServerSocket ss = null;
try {
//创建服务端套接字监听客户端请求
ss = new ServerSocket(8192);
while (true) {
Socket socket = ss.accept();
//交给独立的线程来处理与这个客户端的文件通信需求
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端线程处理类
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
OutputStream os = null;
try {
//得到一个数据输入流读取客户端发送过来的请求
DataInputStream dis = new DataInputStream(socket.getInputStream());
//读取客户端的文件类型
String suffix = dis.readUTF();
String filePath = UUID.randomUUID()+suffix;
//把客户端传过来的数据流写到指定位置
os = new FileOutputStream(new File(filePath));
byte[] buffer = new byte[1024];
int len;
while ((len = dis.read(buffer)) != -1) {
os.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();//关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端
public static void main(String[] args) {
Socket socket = null;
try {
//创建客户端Socket,指定主机地址和端口号
socket = new Socket("127.0.0.1",8192);
//把字节流包装成数据输出流
DataOutputStream dos =
new DataOutputStream(socket.getOutputStream());
dos.writeUTF(".png");//把文件后缀上传给服务器
String filePath = "C:\\Users\\ms674\\Desktop\\File\\img1.png";
InputStream is = new FileInputStream(new File(filePath));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
dos.write(buffer,0,len);
}
dos.flush();//刷新缓冲区
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}