BIO和NIO的原理分析

BIO和NIO的原理分析

1、什么是BIO和NIO?
BIO是同步阻塞的网络传输协议,
NIO是同步非阻塞的网络传输协议。
同步阻塞:比如你去大保健要叫十三号技师,刚好十三号技师在忙,你就在那什么事也不干,傻傻的等,直到十三号技师忙完了,才会给你服务,这个过程就是同步阻塞。
同步非阻塞:你去大保健也要叫十三号技师,十三号技师在忙,你就可以在那玩玩手机,和前台小姐姐聊聊人生什么的,直到十三号技师忙完了,才会给你服务,这个过程就是同步非阻塞。

BIO实现的思路:
客户端和服务器传输数据,需要用到ServerSocket,Socket,输入输出流,端口号,地址等,

举个例子:你每天加班腰疼,有一天你想去大保健,你肯定去地图上搜索然后知道按摩中心的地址,那么你相对于按摩中心来说,你就是客户端,按摩中心就是服务端,你到了按摩中心,前台小姐姐热情的接待了你,前台小姐姐肯定不能为你服务了,他肯定会安排技师帮你服务,比如十三号技师吧,然后会把你带到一个没人打扰的房间里进行不可描述的服务。
至此那么上面提到的ServerSocket就好比是按摩中心,Socket才是真正的服务者也就是十三号技师,那个房间就好比是一个线程,输入输出流就好比是一个传输数据的通道。
在这里插入图片描述

服务端代码:
package cn.enjoyedu.bio;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
*@author Mark老师 享学课堂 https://enjoy.ke.qq.com
*
*类说明:Bio通信的服务端
*/
public class Server {

public static void main(String[] args) throws IOException {
    //服务端启动必备,(new出按摩中心)
    ServerSocket serverSocket = new ServerSocket();
    //表示服务端在哪个端口上监听(好比接待来按摩服务的客户)
    serverSocket.bind(new InetSocketAddress(10001));
    System.out.println("Start Server ....");
    try{
        while(true){ //死循环店名一直开着,接待广大客户
            new Thread(new ServerTask(serverSocket.accept())).start();//new Thread好比一个房间,new ServerTask线程里的任务,serverSocket.accept()接收客户的动作
        }
    }finally {
        serverSocket.close();//你完事了,对你的任务关闭
    }
}

//每个和客户端的通信都会打包成一个任务,交个一个线程来执行
private static class ServerTask implements Runnable{

    private Socket socket = null;//13号技师
    public ServerTask(Socket socket){//构造方法
        this.socket = socket;
    }

    @Override
    public void run() {
        //实例化与客户端通信的输入输出流
        try(ObjectInputStream inputStream =
                new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream outputStream =
                new ObjectOutputStream(socket.getOutputStream())){

            //接收客户端的输出,也就是服务器的输入
            String userName = inputStream.readUTF();
            System.out.println("Accept client message:"+userName);

            //服务器的输出,也就是客户端的输入
            outputStream.writeUTF("Hello,"+userName);
            outputStream.flush();//强制刷新入主内存
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            try {
                socket.close();//完事了,13号技师对你说拜拜
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

}

客户端代码:
package cn.enjoyedu.bio;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
*@author Mark老师 享学课堂 https://enjoy.ke.qq.com
*
*类说明:Bio通信的客户端
*/
public class Client {

public static void main(String[] args) throws IOException {
    //客户端启动必备
    Socket socket = null;//在网络通讯里,Socket 才是真正服务的
    //实例化与服务端通信的输入输出流
    ObjectOutputStream output = null;
    ObjectInputStream input = null;
    //服务器的通信地址
    InetSocketAddress addr
            = new InetSocketAddress("127.0.0.1",10001);//在地图上搜索按摩中心的地址

    try{
        socket = new Socket();
        socket.connect(addr);//连接服务器,按照地图上的地址去按摩中心

        output = new ObjectOutputStream(socket.getOutputStream());
        input = new ObjectInputStream(socket.getInputStream());

        /*向服务器输出请求*/
        output.writeUTF("Mark");
        output.flush();//强制刷新

        //接收服务器的输出
        System.out.println(input.readUTF());
    }finally{
        if (socket!=null) socket.close();//关闭socket和输入输出流
        if (output!=null) output.close();
        if (input!=null) input.close();

    }
}

}

NIO实现的思路:
其实和BIO差不多,NIO的三大组件,Selector,Channel,Buffer。

在这里插入图片描述
服务端:
在这里插入图片描述
客户端:
在这里插入图片描述

服务器代码:
package bigdata.studynio;

public class NioServer {

public static void main(String[] args) {
	int port = 8080;//定义端口号
	if(args != null && args.length < 0){
		
		//port = Integer.valueOf(args[0]);	
	}
	//创建服务器线程
	NioServerWork nioServerWork = new NioServerWork(port);
	new Thread(nioServerWork, "server").start();
}

}

服务端业务代码
package bigdata.studynio;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioServerWork implements Runnable {

//多路复用器 Selector会对注册在其上面的channel进行;轮询,当某个channel发生读写操作时,
//就会处于相应的就绪状态,通过SelectionKey的值急性IO 操作
private Selector selector;//多路复用器
private ServerSocketChannel channel;
private volatile boolean stop;
/**
 * @param port
 * 构造函数
 */
public NioServerWork(int port) {
	try {
		selector = Selector.open();//打开多路复用器,支持连接,读,写,接收连接四个事件
		channel = ServerSocketChannel.open();//打开socketchannel,相当于BIO的ServerSocket
		channel.configureBlocking(false);//配置通道为非阻塞的状态
		channel.socket().bind(new InetSocketAddress(port), 1024);//和BIO一样,通过socket绑定端口
		channel.register(selector, SelectionKey.OP_ACCEPT);//将通道channel在多路复用器selector上注册为接收操作
		stop=true;
		System.out.println("NIO 服务启动 端口: "+ port);
		
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}		
}


@Override
public void run() {//线程的Runnable程序
	System.out.println("NIO 服务  run()");
	while(stop){
		try {
			selector.select(1000); //当前网络通道上去获取事件,阻塞1000毫秒,在1000毫秒后返回
			//获取多路复用器的事件值SelectionKey,并存放在迭代器中
			Set<SelectionKey> selectedKeys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = selectedKeys.iterator();//因为可能有多个事件
			SelectionKey key =null;
			//System.out.println("NIO 服务  try");
			while(iterator.hasNext()){
				System.out.println("NIO 服务  iterator.hasNext()");
				key = iterator.next();
				iterator.remove();//获取后冲迭代器中删除此值,不删除的话下次还会获取到
				try {
					handleinput(key);//根据SelectionKey的值进行相应的读写操作				
				} catch (Exception e) {
					if(key!=null){
						key.cancel();
						if(key.channel()!=null)
							key.channel().close();							
					}
				}									
			}							
		} catch (IOException e) {
			System.out.println("NIO 服务  run  catch IOException");
			e.printStackTrace();
			System.exit(1);
		}
	}		
}

/**
 * @param key
 * @throws IOException
 * 根据SelectionKey的值进行相应的读写操作
 */
private void handleinput(SelectionKey key) throws IOException {
	System.out.println("NIO 服务  handleinput");
	if(key.isValid()){//判断所传的SelectionKey值是否可用
		if(key.isAcceptable()){//在构造函数中注册的key值为OP_ACCEPT,,在判断是否为接收操作
			ServerSocketChannel  ssc = (ServerSocketChannel)key.channel();//获取key值所对应的channel
			SocketChannel sc = ssc.accept();//接收连接
			sc.configureBlocking(false);//设置非阻塞
			sc.register(selector, SelectionKey.OP_READ);//并把这个通道注册为OP_READ			
		}
		if(key.isReadable()){//判断所传的SelectionKey值是否为OP_READ,通过上面的注册后,经过轮询后就会是此操作
			SocketChannel sc = (SocketChannel)key.channel();//获取key对应的channel
			ByteBuffer readbuf = ByteBuffer.allocate(1024);//开始缓冲区
			int readbytes = sc.read(readbuf);//从channel中读取byte数据并存放readbuf
			if(readbytes > 0){
			   /*将ByteBuffer 缓冲区当前的limit设置为position,position=0,
                用于后续对缓冲区的读取操作,读写切换*/
				readbuf.flip();
				byte[] bytes = new byte[readbuf.remaining()];//将可读的内容放到数组
				readbuf.get(bytes); /*将缓冲区可读字节数组复制到新建的数组中*/
				/把读取的数据转换成string
				String message = new String(bytes,"UTF-8");	//把读取的数据转换成string
                System.out.println("服务器收到消息:"+message);
                /*处理数据*/
                String result = Const.response(message);
                /*发送应答消息*/
                doWrite(ssc ,result);
			}else if (readbytes < 0){
				key.cancel();
				sc.close();					
			}else{}				
		}			
	}		
}
/**
 * @param sc
 * @param currenttime
 * @throws IOException
 * 服务器的业务操作,将当前时间写到通道内
 */
private void dowrite(SocketChannel sc,String response) throws IOException {
	byte[] bytes = response.getBytes();
    ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
    buffer.put(bytes);
    buffer.flip();
    sc.write(buffer);	
}

}
}

客户端代码
package bigdata.studynio;

public class NioClient {

public static void main(String[] args) {
	
	int port = 8080;
	if(args !=null && args.length > 0){
		try {
			//port = Integer.valueOf(args[0]);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	//创建客户端线程
	new Thread(new NioClientWork("127.0.0.1",port),"client").start();
	
}

}

客户端具体业务处理:
package bigdata.studynio;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioClientWork implements Runnable {

private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;

/**
 * @param string
 * @param port
 * 构造函数
 */
public NioClientWork(String string, int port) {
	this.host = string == null ? "127.0.0.1":string;
	this.port = port;
	try {
		selector= Selector.open();//打开多路复用器
		socketChannel=SocketChannel.open();//打开socketchannel
		socketChannel.configureBlocking(false);
		System.out.println("NIO 客户端启动 端口: "+ port);
	} catch (IOException e) {
		e.printStackTrace();
		System.exit(1);
	}
	
}

/* (non-Javadoc)
 * @see java.lang.Runnable#run()
 */
@Override
public void run() {
	try {
		doConnect();//客户端线程需要连接服务器
	} catch (Exception e) {
		e.printStackTrace();
		System.exit(1);
	}
	while(!stop){
		
		try {
			selector.select(1000);//最大阻塞时间1s
			//获取多路复用器的事件值SelectionKey,并存放在迭代器中
			Set<SelectionKey> selectedKeys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = selectedKeys.iterator();
			SelectionKey key =null;
			while (iterator.hasNext()) {
				key = iterator.next();
				iterator.remove();
				try {
					handleinput(key);//获取多路复用器的事件值SelectionKey,并存放在迭代器中					
				} catch (Exception e) {
					if(key == null){
						key.cancel();
						if(socketChannel ==null)
							socketChannel.close();							
					}
				}					
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}			
	}
	if(selector !=null){
		try {
			selector.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
/**
 * @throws IOException
 * 线程连接服务器,并注册操作
 * 
 */
private void doConnect() throws IOException {
	if(socketChannel.connect(new InetSocketAddress(host, port))){//检测通道是否连接到服务器 
		System.out.println("NIO 客户端 idoConnect OP_READ ");
		socketChannel.register(selector, SelectionKey.OP_READ);//如果已经连接到了服务器,就把通道在selector注册为OP_READ
		dowrite(socketChannel);
	}else{
		System.out.println("NIO 客户端 doConnect OP_CONNECT ");
		socketChannel.register(selector, SelectionKey.OP_CONNECT);//如果客户端未连接到服务器,则将通道注册为OP_CONNECT操作
	}	
}

/**
 * @param key
 * @throws IOException
 * 根据SelectionKey的值进行相应的读写操作
 */
private void handleinput(SelectionKey key) throws IOException {
	//System.out.println("NIO 客户端 handleinput ");
	if(key.isValid()){//判断所传的SelectionKey值是否可用
		//System.out.println("NIO 客户端 isValid() ");
		SocketChannel sc = (SocketChannel) key.channel();
		if(key.isConnectable()){//一开始的时候,客户端需要连接服务器操作,所以检测是否为连接状态
			System.out.println("NIO 客户端 isConnectable ");
			if(sc.finishConnect()){//是否完成连接
				System.out.println("NIO 客户端 finishConnect ");
				dowrite(sc);//向通道内发送数据,就是“查询时间” 的命令,读写通道与通道注册事件类型无关,注册事件只是当有事件来了,就会去处理相应事件
				sc.register(selector, SelectionKey.OP_READ);//如果完成了连接,就把通道注册为 OP_READ操作,用于接收服务器出过来的数据
			}else{
				System.out.println("NIO 客户端 not finishConnect ");
				System.exit(1);				
			}
		}
		if(key.isReadable()){//根据上面注册的selector的通道读事件,进行操作
			System.out.println("NIO 客户端 isReadable() ");
			ByteBuffer readbuf = ByteBuffer.allocate(1024);
			int readbytes = sc.read(readbuf);//获取通道从服务器发过来的数据,并反序列化
			if(readbytes > 0){
				readbuf.flip();
				byte[] bytes=new byte[readbuf.remaining()];
				readbuf.get(bytes);
				String string = new String(bytes, "UTF-8");
				System.out.println("时间是: " + string);
				this.stop=true;	//操作完毕后,关闭所有的操作				
			}else if (readbytes < 0){
				key.cancel();
				sc.close();
				
			}else{}			
		 }				
	}		
}
private void dowrite(SocketChannel sc) throws IOException {
	byte[] req = "查询时间".getBytes();
	ByteBuffer writebuf = ByteBuffer.allocate(req.length);
	writebuf.put(req);
	writebuf.flip();
	sc.write(writebuf);
	if(!writebuf.hasRemaining()){
		System.out.println("向服务器发送命令成功 ");
	}	
}

}

总结:
服务端:
1:服务端绑定端口号,在selector注册接收事件,
2:在while循环去接收客户端的信息,获取selector所有的事件set并遍历,r第一遍接收对端数据用ServerSocketChannel开辟channel(通道),accept()方法接收客户端连接,利用SocketChannel在selector注册读写事件,第二遍处理对端数据用SocketChannel开辟channel(通道),利用SocketChannel在selector注册读写事件,并通过ByteBuffer缓冲区读写数据
客户端:
1:通过ip地址连接服务器,在selector注册连接或者读写事件
2:在while循环去往服务端发送信息,获取selector所有的事件set并遍历,SocketChannel开辟channel(通道),利用SocketChannel在selector注册读写事件,先连接再处理对端数据,并通过ByteBuffer缓冲区读写数据;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值