NIO同步非阻塞IO

一、NIO概述

1.首先介绍一下BIO(同步阻塞IO)

BIO-JDK1.0 - 同步阻塞式IO-BlockingIO

在执行ACCEPT CONNECT READ WRITE 四中操作时都会产生阻塞

Accept:客户端未连接
Connect:连接超时(connection reset);连接拒绝connection refuse
Read:服务器端读取数据,但是客户端未写入数据,产生阻塞
Write:客户端写入数据,服务器端未读取,当写入数据数量达到缓冲区极限,产生阻塞

在平常开发当中并不是问题 甚至因为这样的模型直观而简单 应用的场景非常广泛

但是在高并发的场景下 这样的阻塞式IO可能会造成如下问题:

在服务器开发中 需要在服务器端通过少量线程处理多个客户端请求 这就要求 在少量的线程应该可以灵活的切换处理不同客户端 但传统的BIO阻塞式的工作方式 一旦阻塞了线程 线程就被挂起 无法继续执行 无法实现这样的功能

2.NIO - JDK4.0 - 同步非阻塞式IO - NonBlockingIO/NewIO
    操作:    Accepet、  Connect、 Read、Write(非阻塞的)
    可以随时让线程切换所处理的客户端 从而可以实现高并发服务器的开发
    需求:用少量的线程来处理多个客户端请求
    腾讯QQ聊天案例
        多人连接服务器,通过一个中心选择器进行管理,当多人需要聊天发送消息时(排队处理),提供线程,一个客户占用线程没发消息,导致其他人消息发不出去(BIO)
        使用NIO用少量的线程来处理多个客户端请求,让线程可以随时切换所处理的客户端

两者特点对比:

BIO:同步阻塞式IO 面向流 操作字节或字符 单向传输数据
NIO:同步非阻塞式IO 面向通道 操作缓冲区 双向传输数据

二、开源的NIO结构的服务器框架(了解)

        MINA
        Netty(更好)
    IO方式:
        阻塞/非阻塞:
讨论的是线程的角度,当执行某些操作不能立即完成时,线程是否被挂起,失去cpu争夺权无法继续执行直到阻塞结束或被唤醒 
        同步/异步:讨论的是参与通信双方的工作机制,是否需要互相等待对方的执行
            同步:
              
 通信过程中 一方在处理通信,另一方 要等待对方执行不能去做其他无关的事
            异步:
                
通信过程中 一方在处理通信,另一方 可以不用等待对方而可以去做其他无关的事 直到对方处理通信完成 再在适合的时候继续处理通信过程
    三种IO机制的区别!!!

 BIOjdk1.0 同步阻塞式IO面向流操作字节或字符单向传输数据
 NIOjdk4.0 同步非阻塞式IO 面向通道操作缓冲区双向传输数据
AIO jdk7.0异步非阻塞式IO大量使用回调函数异步处理通信过程 

三、粘包问题

概念:通过socket发送多段数据时 底层的tcp协议 会自动根据需要 将数据 拆分或合并 组成数据包后发送给接受者 ,接受者收到数据后 无法直接通过tcp协议本身判断数据的边界,这个问题就称之为粘包问题.

产生原因:

1.本质上是因为tcp协议是传输层的协议 本身没有对会话控制提供相应的能力

2.socket开发网络程序时相当于在自己实现会话层、表示层和应用层的功能,所以需要自己来相办法解决粘包问题
举例:aaa  bbbbb  cccc
    TCP属于传输层,底层靠数据包传输,只负责传输数据,不负责分配数据格式,结果为ccccbbbbbaaa
    导致不能明确数据的结构进行分段

解决方案:
    a.只发送固定长度的数据
        通信的双发约定每次发送数据的长度,每次只发送固定长度的数据,接收数据方 每次都按照固定长度获取数据
        缺点:
            不够灵活,只适合每次传输的数据都有固定长度的场景
    b. 约定分隔符
        通信双方约定一个特殊的分隔符用来表示数据的边界,接收方收到数据时,不停读取,以分隔符为标志,区分数据的边界
        缺点:
            如果数据本身就包含分隔符字符,则需要对数据进行预处理将数据本身包含的分隔符进行转义,相对来说比较麻烦
    c. 数据分头和体,在头信息中描述数据长度或格式
        通过头信息中传递数据长度或格式信息,在接收方法接收数据时,先读取头信息,在根据头信息来决定如何获取后续数据

 

协议:
    公共协议:HTTP FTP SMTP POP3等等,需按照协议来通信,约束较大
    通信问题解决:真正在开发过程中,如果需要开发底层网络通信机制,可以根据需要选择公有协议 或 自定义私有协议来解决通信规范问题

四、NIO通信案例

服务端:

服务端:
package cn.tedu.nio.channel;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerSocketChannelDemo01 {
	public static void main(String[] args) throws Exception {
		//1.创建ServerSockentChannel对象
		ServerSocketChannel ssc = ServerSocketChannel.open();
		//2.绑定指定端口
		ssc.bind(new InetSocketAddress(44444));
		//3.设置非阻塞模式
		ssc.configureBlocking(false);
		//4.接收客户端连接
		SocketChannel sc = null;
		while(sc == null){
			sc = ssc.accept();
		}
		sc.configureBlocking(false);
		//5.读取数据
		ByteBuffer buf = ByteBuffer.allocate(5);
		while(buf.hasRemaining()){
			sc.read(buf);
		}
		//6.获取数据打印
		byte[] arr = buf.array();
		String str = new String(arr);
		System.out.println(str);
		
		//5.关闭通道
		sc.close();
		ssc.close();
	}

}

客户端:

客户端:
	package cn.tedu.nio.channel;
	
	import java.net.InetSocketAddress;
	import java.nio.ByteBuffer;
	import java.nio.channels.SocketChannel;
	
	public class SocketChannelDemo01 {
		public static void main(String[] args) throws Exception {
			//1.创建客户端SocketChannel
			SocketChannel sc = SocketChannel.open();
			//2.配置启用非阻塞模式
			sc.configureBlocking(false);
			//3.连接服务器
			boolean isConn = sc.connect(new InetSocketAddress("127.0.0.1", 44444));
			if(!isConn){
				while(!sc.finishConnect()){
				}
			}
			
			//4.发送数据到服务器
			ByteBuffer buf = ByteBuffer.wrap("abcde".getBytes());
			while(buf.hasRemaining()){
				sc.write(buf);
			}
			
			//5.关闭通道
			sc.close();
		}
	}

五、少量线程处理多客户端请求案例

客户端:

客户端:
	package cn.tedu.nio.channel;
	
	import java.net.InetSocketAddress;
	import java.nio.ByteBuffer;
	import java.nio.channels.SocketChannel;
	
	public class SocketChannelDemo01 {
		public static void main(String[] args) throws Exception {
			//1.创建客户端SocketChannel
			SocketChannel sc = SocketChannel.open();
			//2.配置启用非阻塞模式
			sc.configureBlocking(false);
			//3.连接服务器
			boolean isConn = sc.connect(new InetSocketAddress("127.0.0.1", 44444));
			if(!isConn){
				while(!sc.finishConnect()){
				}
			}
			
			//4.发送数据到服务器
			ByteBuffer buf = ByteBuffer.wrap("abcde".getBytes());
			while(buf.hasRemaining()){
				sc.write(buf);
			}
			
			//5.关闭通道
			sc.close();
		}
	}

服务端:

	package cn.tedu.nio.selector;
	
	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 ServerSocketDemo01 {
		public static void main(String[] args) throws Exception {
			//0.创建选择器
			Selector selc = Selector.open();
			//1.创建代表服务器的ServerSocketChannel对象
			ServerSocketChannel ssc = ServerSocketChannel.open();
			//2.设置为非阻塞模式
			ssc.configureBlocking(false);
			//3.设置监听的端口
			ssc.bind(new InetSocketAddress(44444));
			//4.将ssc注册到选择器中关注ACCEPT操作
			ssc.register(selc, SelectionKey.OP_ACCEPT);
			
			//5.通过选择器选择就绪的键
			while(true){
				selc.select();//尝试到注册的键集中来寻找就绪的键 如果一个就绪的键都找不到 就进入阻塞 直到找到就绪的键 返回就绪的键的个数
				
				//6.获取就绪的键的集合
				Set<SelectionKey> keys = selc.selectedKeys();
				
				//7.遍历处理就绪的键 代表的操作
				Iterator<SelectionKey> it = keys.iterator();
				while(it.hasNext()){
					//--获取到就绪的键 根据键代表的操作的不同 来进行不同处理
					SelectionKey key = it.next();
					
					if(key.isAcceptable()){
						//--发现了Accept操作 
						//--获取通道
						ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
						//--完成Accept操作
						SocketChannel sc = sscx.accept();
						//--在sc上注册读数据的操作
						sc.configureBlocking(false);
						sc.register(selc, SelectionKey.OP_READ);
					}else if(key.isConnectable()){
						
					}else if(key.isWritable()){
						
					}else if(key.isReadable()){
						//--发现了Read操作
						//--获取就绪的通道
						SocketChannel scx = (SocketChannel) key.channel();
						//--完成读取数据的操作
						ByteBuffer buf = ByteBuffer.allocate(10);
						while(buf.hasRemaining()){
							scx.read(buf);
						}
						String msg = new String(buf.array());
						System.out.println("[收到来自客户端的消息]:"+msg);
					}else{
						throw new RuntimeException("未知的键,见了鬼了~");
					}
					
					//8.移除处理完的键
					it.remove();
				}
			}
		}
	}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值