bio模型(block-io)
bio模型又叫同步阻塞io
二狗在向老师体温的时候,其他三位同学只能等待!二狗的问题解答完成之后,才能继续解答其他同学。
同步:老师在同一时间只能给一位同学解答问题
阻塞:如果二狗突然陷入沉思,没有和老师说话,那么老师只能等待。
阻塞的过程是不占用系统资源的
当客户端发起请求,服务端接收到请求开始调用操作系统api,如果无数据则阻塞(不占用系统资源),对于服务端来说其他客户端请求也接收不到,当有数据的时候,开始读数据,并响应给服务端。
package bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* bio
*/
public class Server {
private static int port = 8588;
public static void main(String[] args) {
ServerSocket serverSocket =null;
try {
serverSocket=new ServerSocket(port);
System.out.println("服务端启动,端口为:".concat(String.valueOf(port)));
while (true){
System.out.println("开始阻塞,等待客户端连接");
//阻塞 等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("接收到一个客户端连接请求");
InputStream in = socket.getInputStream();
System.out.println("开始读取客户端交互内容");
int len = -1;
byte[] buff = new byte[1024];
//阻塞 等待客户端发送数据 ,为啥是阻塞的 读取文件流的时候好像不是阻塞的呢
while ((len=in.read(buff))!=-1){
//todo 为啥还多了个1:
String str = new String(buff,0,len);
System.out.println("读取到客户端输入的内容:".concat(str));
}
in.close();
socket.close();
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package bio;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
private static int port = 8588;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入客户端编号:");
String number = scanner.next();
Socket socket = null;
try{
System.out.println("客户端".concat(number).concat("开始连接服务"));
socket=new Socket("127.0.0.1",port);
System.out.println("客户端".concat(number).concat("连接成功"));
OutputStream outputStream = socket.getOutputStream();
while (true){
System.out.println("客户端".concat(number).concat("请输入相对服务端说的话(quit表示退出)"));
String str = scanner.next();
if(str.equalsIgnoreCase("quit")){
break;
}
//什么带着客户端编号:,因为这里
outputStream.write(number.concat(":").concat(str).getBytes());
}
System.out.println("客户端".concat(number).concat("连接中断"));
outputStream.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
上面的代码,服务端在第一个客户端连接不死的情况下,只能处理第一个连接的客户端,其他客户端只能等待,不合理,所以出现下图方式
上面的实现方式不合理,所以做出调整,准备多个老师,每个老师负责以为同学的问题,安排课代表为大家安排老师
我们的ServerSocket只负责接收客户端连接请求,接收到客户端连接请求之后,就创建一个新的线程,并将socket交给新线程,由新线程处理客户端交流请求,ServerSocket就可以继续监听客户端连接请求
客户端代码不变
package bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* bio
*/
public class Server1 {
private static int port = 8588;
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("服务端启动,端口为:".concat(String.valueOf(port)));
while (true) {
System.out.println("开始阻塞,等待客户端连接");
//阻塞 等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("接收到一个客户端连接请求");
new Thread() {
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
System.out.println("开始读取客户端交互内容");
int len = -1;
byte[] buff = new byte[1024];
//阻塞 等待客户端发送数据 ,为啥是阻塞的 读取文件流的时候好像不是阻塞的呢
while ((len = in.read(buff)) != -1) {
//todo 为啥还多了个1:
String str = new String(buff, 0, len);
System.out.println("读取到客户端输入的内容:".concat(str));
}
in.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
serverSocket.accept()没有客户端来连接一直阻塞不动,
inputStream.read(bytes)客户端没有发来数据一直阻塞,代码不会往下执行。
如果一个客户端来连接之后,这个客户端没有发来数据代码则一直卡在inputStream.read(bytes)不动,serverSocket.accept()方法得不到调用,那么此时有客户端来连接,是不会接受连接的,必须要等到第一个连接上来的客户端发死了之后才可以。
1.上面说了这么多就是为了证明Bio的编码方式必须是一个线程对应一个客户端连接才能同时处理多个客户端请求。
2.假设有10万个客户端,那么服务器就要开10万个线程。那么服务器的线程是非常宝贵的,而且10万个线程一般会使服务器炸掉。对于一些链接上来但是并没有向服务器发来数据的客户端,我们服务器给它开一个是资源的浪费。所以才出现了NIo。
nio模型(new-io/no-block-io)
在客户端数量多的情况下,bio线程数量的增加会让我们的系统压力大增,所以nio出现(new-io/no-block-io)
多路复用体现在我们io模型中就是一个线程处理多个读写请求
socket底层是如何实现io操作的?
最终我们知道,网络监听,接收请求,从io进行数据读写都是由操作系统内核完成的
nio要解决的问题:
1.让accept()和read()不阻塞,和程序架构没关系,只是个api
2.单线程解决并发问题(多路复用)
只有一个老师,课代表用一个表格记录所有同学的问题,任何同学想提问,都找课代表,课代表告诉老师,老师处理学生的问题,如果解答过程中没有思考好,老师不会等待,继续解答其他同学的问题,课代表会不断的询问所有同学是不是思考好了,如果所有的同学都没思考好,老师就休息。
**不阻塞:**老师不等待学生思考
如何解决不阻塞
不让等待客户端连接阻塞,不让等待客户端输入数据阻塞。
sum公司提供了另外一套接口,实现不阻塞的socket
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入客户端编号:");
int no = scanner.nextInt();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",8585));
ByteBuffer buffer = ByteBuffer.allocate(1024);
String str = "";
while (true){
if(!socketChannel.finishConnect()){
Thread.sleep(100);
continue;
}
System.out.println("客户端"+no+"请输入要发送的内容");
str = scanner.next();
if(str.equalsIgnoreCase("quit")){
break;
}
byte[] bytes = (no + ":" + str).getBytes();
buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
socketChannel.write(buffer);
}
}
}
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* 因为
* SocketChannel accept = serverSocketChannel.accept();
* 如果没有客户端请求链接,就返回null,所以程序一下子就over了
* 怎么办呢?循环监听
*
*/
public class Server {
public static void main(String[] args) {
try {
//创建ServerSocketChannel通道,绑定监听端口为8585
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8585));
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//开始监听
System.out.println("开始监听.....");
SocketChannel accept = serverSocketChannel.accept();
//如果没有人链接,就返回null
System.out.println("accept:"+(accept));
if (accept!=null){
System.out.println("接受到请求...");
accept.configureBlocking(false);
//读取数据?
}
System.out.println("OVER");
} catch (IOException e) {
e.printStackTrace();
}
}
}
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* 为什么会阻塞?线程在等待资源,等待客户端输入数据 cpu资源就释放出来了
* 怎么让线程不阻塞,ServerSocketChannel ServerSocket通道,可读可写
* 读不到数据怎么办????????????
*/
public class Server1 {
public static void main(String[] args) {
try {
//创建ServerSocketChannel通道,绑定监听端口为8585
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8585));
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//循环监听
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开始监听
System.out.println("开始监听.....");
//在非阻塞模式下,可以立即返回null
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("socketChannel:" + (socketChannel));
if (socketChannel != null) {
System.out.println("接受到请求...");
socketChannel.configureBlocking(false);
//非阻塞,一旦read不到数据,直接就进入下一次循环,
// SocketChannel socketChannel = serverSocketChannel.accept();
//又产生了一个新的socketChannel,原来的socketChannel就找不到了
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(byteBuffer);
System.out.println(len);
}
System.out.println("OVER");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
/**
* 自己用java实现了一个多路复用
* 存在的弊端
* 1.SocketChannel accept = serverSocketChannel.accept();一直站着资源
* 如果一直没有请求进来, while (true)则一直循环,浪费资源
* 2.:10000个人打开了百度,只有10个人在活动,
* for (int i = 0; i < socketChannels.size(); i++)
* 循环10000次,只有10个有用请求要交互,浪费资源
*/
public class Server2 {
public static void main(String[] args) {
//用来存储SocketChannel对象
List<SocketChannel> socketChannels = new ArrayList<>();
try {
//创建ServerSocketChannel通道,绑定监听端口为8585
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8585));
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//循环监听
while (true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开始监听
System.out.println("开始监听.....");
//在非阻塞模式下,可以立即返回null
SocketChannel accept = serverSocketChannel.accept();
System.out.println("accept:"+(accept));
if (accept!=null){
System.out.println("接受到请求...");
accept.configureBlocking(false);
//把所有的请求放在一个集合中
socketChannels.add(accept);
System.out.println("socketChannels:size"+socketChannels.size());
}
//每次遍历所有的socketChannels,看看能不能读取数据
for (int i = 0; i < socketChannels.size(); i++) {
//和socket是一样
SocketChannel sc = socketChannels.get(i);
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
//读到多少字节 可以知道是否是阻塞状态
int read = sc.read(buffer);
if(read>0){
buffer.flip();
String str = new String(buffer.array(), 0, buffer.limit());
System.out.println("监听到的数据为"+str);
//但是如果集合中的事件特别难,该模式不适用
}
}catch (Exception e){
System.out.println(e.getLocalizedMessage());
socketChannels.remove(sc);
}
}
System.out.println("OVER");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package nio;
import com.sun.org.apache.bcel.internal.generic.Select;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
/**
* 让轮训器去阻塞,轮训器判断是否有请求时间发生,是否有读数据的事件发生,是否有写数据的事件发生
* 如果都没有,轮训器就阻塞等待,让出cpu资源
* 任何一个事件发生(请求,读,写),轮训器就被打通,这时主线程开始处理事件
* 存在的问题,一个cpu8核如何合理利用
*/
public class Server3 {
public static void main(String[] args) {
try {
//创建ServerSocketChannel通道,绑定监听端口为8585
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8585));
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//创建一个多路复用器
Selector selector = Selector.open();
//注册选择器,设置选择器的操作类型,将serverSocketChannel注册到selector中,监听的事件是ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环监听
while (true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始监听.....");
//选择事件这里会阻塞,如果没有任何事件处理该方法处理阻塞状态
int select = selector.select();
//取出所有可以处理的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
System.out.println("处理事件长度为"+selectionKeys.size());
SelectionKey next = iterator.next();
//删除key防止重复处理
iterator.remove();
if(next.isReadable()){
System.out.println("读请求事件");
SocketChannel channel =(SocketChannel) next.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = channel.read(byteBuffer);
System.out.println(new String(byteBuffer.array(),0,read));
}else if(next.isAcceptable()){
System.out.println("监听事件");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
}else if(next.isWritable()){
System.out.println("写数据事件");
}
}
System.out.println("OVER");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
javaio中读写阻塞的初衷是什么?
一阻塞就释放cpu资源
新问题
上面的程序中 存在的弊端
- 1.SocketChannel accept = serverSocketChannel.accept();一直站着资源
如果一直没有请求进来, while (true)则一直循环,浪费资源 - 2.:10000个人打开了百度,只有10个人在活动,
for (int i = 0; i < socketChannels.size(); i++)
循环10000次,只有10个有用请求要交互,浪费资源
进行阻塞,避免不必要的轮训
解决问题:
让轮训器去阻塞,轮训器判断是否有请求时间发生,是否有读数据的事件发生,是否有写数据的事件发生,如果都没有,轮训器就阻塞等待,让出cpu资源, 任何一个事件发生(请求,读,写),轮训器就被打通,这时主线程开始处理事件
jvm提供的多路复用器
Selector SelectableChannel
int select = selector.select();调用了操作系统的api,事件轮训操作是由操作系统内核完成的,而且socket本身也是通过os内核实现的。
一个请求(连接、读、写)ServerSocketChannel过来了,注册到selector中,selector底层掉操作系统的轮训方法,将所有的可操作的事件写入Set selectionKeys = selector.selectedKeys();,通知应用程序,处理数据,应用程序开始io处理(连接,读,写)。
AIO模型(异步io模型)
aio是javaio模型中的一种,作为nio改进和增强,随jdk1.7更新继承在nio包中,因此aio也被称作nio2.0,区别于传统bio(同步阻塞模型,jdk1.4之前就存在,nio在jdk1.4版本之后存在)的阻塞式读写,aio提供了从建立连接到读、写的全异步操作,aio可用于异步的文件读写和网络通信
山炮提问之后,留一个电话,然后可以继续和二狗玩耍,老师解决好问题以后,打电话通知山炮过来拿结果,老师和山炮是异步操作
一个请求(连接、读、写)ServerSocketChannel过来了,注册到selector中,selector底层掉操作系统的轮训方法,注册的时候就会创建一个可被毁掉的监听程序(子线程wait状态),主线程该干嘛干嘛去,操作系统把数据准备好后,唤醒子线程,子线程开始io处理(连接,读,写)
package aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Scanner;
/**
*
*/
public class Client {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入客户端编号");
int number = scanner.nextInt();
AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open();
asynchronousSocketChannel.connect(new InetSocketAddress("127.0.0.1",8585));
ByteBuffer byteBuffer= ByteBuffer.allocate(1024);
String str = "";
while (true){
System.out.println("客户端编号"+number+"请输入要发送的内容");
str= scanner.next();
//客户端输入的内容为什么会带着客户端编号,是因为这里(number + str)
byte[] bytes = (number + str).getBytes();
byteBuffer = ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes);
byteBuffer.flip();
asynchronousSocketChannel.write(byteBuffer);
}
}
}
package aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* 异步阻塞
* 除第一个连接外,其他的会阻塞
*/
public class Server {
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
asynchronousServerSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8585));
while (true) {
System.out.println("开始监听");
Future<AsynchronousSocketChannel> future = asynchronousServerSocketChannel.accept();
//阻塞
AsynchronousSocketChannel asynchronousSocketChannel = future.get();
System.out.println("接收到请求。。。");
ByteBuffer allocate = ByteBuffer.allocate(1024);
while (asynchronousServerSocketChannel.isOpen()) {
//阻塞
Future<Integer> read = asynchronousSocketChannel.read(allocate);
Integer len = read.get();
if (len > 0) {
System.out.println("读取到数据" + new String(allocate.array(), 0, len));
allocate.clear();
}
}
}
}
}
package aio;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* 异步非阻塞
*/
public class Server1 {
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
final AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
asynchronousServerSocketChannel.bind(new InetSocketAddress("127.0.0.1",8585));
System.out.println("开始监听");
asynchronousServerSocketChannel.accept(null,
new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel result, Void attachment) {
//继续监听
asynchronousServerSocketChannel.accept(null,this);
//接收到新的客户端连接时调用,result就是和客户端的连接对话,此时通过result和客户端进行通信
System.out.println("accept completed");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (result.isOpen()){
Future<Integer> read = result.read(byteBuffer);
try {
Integer len = read.get();
if(len>0){
try {
System.out.println(new String(byteBuffer.array(),0,len,"utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
byteBuffer.clear();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("accept失败时回调");
}
});
//让程序阻塞,也说明了是异步的
System.in.read();
}
}