几个概念
阻塞与非阻塞
阻塞:往往需要等待缓冲区中数据准备好过后才处理其他事情,否则一直等待。
非阻塞:当我们进程访问数据缓冲区时,如果数据没有准备好则直接返回,不会的等待。数据准备好了,也直接返回。
同步和异步
同步和异步都是基于应用程序和操作系统处理IO事件所采用的方式。比如同步:是应用程序要直接参与IO读写的操作。异步:所有的IO读写交给操作系统去处理,应用程序只需要等待通知。
同步方式在处理IO事件的时候,必须阻塞在某个方法上面等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件的方式),对于异步来说,所有的IO读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的IO操作,当操作完成IO后,会给我们的应用程序一个通知。
同步:阻塞到IO事件,阻塞到read或则write。这个时候我们就完全不能做自己的事情。让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。
BIO和NIO*
JavaNIO和BIO之间第一个最大的区别是,BIO是面向流的,NIO是面向缓冲区的。
BIO是阻塞的,当一个线程调用read()和write()方法时,该线程会被阻塞,直到数据被读取或完全写入。
NIO是非阻塞的,一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,没有数据可用,就不会获取。直至数据变得可读之前该线程可以做其他事情,而不是一直阻塞等待。
非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)
代码实现
BIO服务端
//同步阻塞IO模型
public class BIOServer {
//服务端网络IO模型的封装对象
ServerSocket server;
//服务器
public BIOServer(int port){
try {
server = new ServerSocket(port);
System.out.println("BIO服务已启动,监听端口是:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 开始监听,并处理逻辑
* @throws IOException
*/
public void listen() throws IOException{
//循环监听
while(true){
//等待客户端连接,阻塞方法
//Socket数据发送者在服务端的引用
Socket client = server.accept();
System.out.println(client.getPort());
//对方法数据给我了,读 Input
InputStream is = client.getInputStream();
//网络客户端把数据发送到网卡,机器所得到的数据读到了JVM内中
byte [] buff = new byte[1024];
int len = is.read(buff);
if(len > 0){
String msg = new String(buff,0,len);
System.out.println("收到" + msg);
}
}
}
public static void main(String[] args) throws IOException {
new BIOServer(8080).listen();
}
}
BIO客服端
public class BIOClient {
public static void main(String[] args) throws UnknownHostException, IOException {
//要和谁进行通信,服务器IP、服务器的端口
//一台机器的端口号是有限
Socket client = new Socket("localhost", 8080);
//输出 O write();
//不管是客户端还是服务端,都有可能write和read
OutputStream os = client.getOutputStream();
//生成一个随机的ID
String name = UUID.randomUUID().toString();
System.out.println("客户端发送数据:" + name);
//传说中的101011010
os.write(name.getBytes());
os.close();
client.close();
}
}
NIO服务端
public class NIOServerDemo {
private int port = 8080;
//准备两个东西
//轮询器 Selector 大堂经理
private Selector selector;
//缓冲区 Buffer 等候区
private ByteBuffer buffer = ByteBuffer.allocate(1024);
//初始化完毕
public NIOServerDemo(int port){
//初始化大堂经理,开门营业
try {
this.port = port;
ServerSocketChannel server = ServerSocketChannel.open();
//我得告诉地址
//IP/Port
server.bind(new InetSocketAddress(this.port));
//BIO 升级版本 NIO,为了兼容BIO,NIO模型默认是采用阻塞式
server.configureBlocking(false);
//大堂经理准备就绪,接客
selector = Selector.open();
//在门口翻牌子,正在营业
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}
public void listen(){
System.out.println("listen on " + this.port + ".");
try {
//轮询主线程
while (true){
//大堂经理再叫号
selector.select();
//每次都拿到所有的号子
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
//不断地迭代,就叫轮询
//同步体现在这里,因为每次只能拿一个key,每次只能处理一种状态
while (iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
//每一个key代表一种状态
//没一个号对应一个业务
//数据就绪、数据可读、数据可写 等等等等
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//具体办业务的方法,坐班柜员
//每一次轮询就是调用一次process方法,而每一次调用,只能干一件事
//在同一时间点,只能干一件事
private void process(SelectionKey key) throws IOException {
//针对于每一种状态给一个反应
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//这个方法体现非阻塞,不管你数据有没有准备好
//你给我一个状态和反馈
SocketChannel channel = server.accept();
//一定一定要记得设置为非阻塞
channel.configureBlocking(false);
//当数据准备就绪的时候,将状态改为可读
key = channel.register(selector,SelectionKey.OP_READ);
}
else if(key.isReadable()){
//key.channel 从多路复用器中拿到客户端的引用
SocketChannel channel = (SocketChannel)key.channel();
int len = channel.read(buffer);
if(len > 0){
buffer.flip();
String content = new String(buffer.array(),0,len);
key = channel.register(selector,SelectionKey.OP_WRITE);
//在key上携带一个附件,一会再写出去
key.attach(content);
System.out.println("读取内容:" + content);
}
}
else if(key.isWritable()){
SocketChannel channel = (SocketChannel)key.channel();
String content = (String)key.attachment();
channel.write(ByteBuffer.wrap(("输出:" + content).getBytes()));
channel.close();
}
}
public static void main(String[] args) {
new NIOServerDemo(8080).listen();
}
}