高并发基础(一)

NIO

一、概述

  1. jdk1.4出现的同步式非阻塞流 ;
  2. 由三大组件组成:Buffer 缓冲区(存储数据)、Channel 通道(传输数据)、Selector 多路复用选择器;

二、Buffer 缓冲区

  Buffer缓冲区用于数据的存储,低层是基于数组实现的,JDK提供了缓冲区的父类 – Buffer,是一个抽象类。Buffer只能存储除boolean以外的其他七种基本类型的数据,boolean以及其他类型的数据需要转化为其中的一种才能进行传输,最常用的是ByteBuffer。

Buffer缓冲区的四个重要位置

  1. capacity:容量位,用于标记缓冲区容量的大小;
  2. limit:限制位,用于限制position所能达到的最大的下标;
  3. position:操作位,指向要操作的下标;
  4. mark:标记位,可以标记正确写入到某个位置。
      Buffer存在几个重要的位置变量capacity、limit、position和mark,make的初始值为-1,默认不开启make。
    四个位置的大小排序应该是capacity>=limit>=position>=mark。

Buffer缓冲区的四个常用的操作

  1. flip:反转缓冲区,写数据结束后需要进行该操作
   public final Buffer flip() {
           limit = position;
           position = 0;
           mark = -1;
           return this;
       }
  1. clear:清空缓冲区,将缓冲区回归到最原始的状态(数据未被擦除)
    public final Buffer clear() {
           position = 0;
           limit = capacity;
           mark = -1;
           return this;
       }
  1. reset:重置缓冲区,将操作位移动到标记位
    public final Buffer reset() {
           int m = mark;
           if (m < 0)
               throw new InvalidMarkException();
           position = m;
           return this;
       }
  1. rewind:重绕缓冲区,比翻转少了对限制位的操作,重新开始读写
   public final Buffer rewind() {
           position = 0;
           mark = -1;
           return this;
       }

Buffer的两种常用创建方式

  1. 指定初始化大小
	ByteBuffer byteBuffer = ByteBuffer.allocate(10);
  1. 指定初始化内容,初始化后操作位仍然为0
	ByteBuffer byteBuffer = ByteBuffer.wrap(array[]);

三、Channel 管道

  Channel用于数据的双向传输,本身面向缓冲区操作,所传输的数据必须放到缓冲区当中,通道默认是阻塞的,可以手动设置为非阻塞 。

Channel的初始化

  Channel初始化的时候不能够使用new,否则会有很多需要实现的抽象方法,我们需要使用channel本身自带的方法去创建Channel。

	// 创建客户端的通道
	SocketChannel sc = SocketChannel.open();

Channel的阻塞设置

  Channel默认是阻塞的,可以手动设置为非阻塞。

	// 创建客户端的通道
	SocketChannel sc = SocketChannel.open();
	// Channel默认是阻塞的,可以手动设置为非阻塞
	sc.configureBlocking(false);

四、Selector 选择器

  多个客户端与多个服务器之间在早期存在惊群现象,及客户端的一个请求试图发送给所有服务器,所有服务器试图争抢一个请求,这会大大增加服务器的负担,Selector解决了这一问题。在使用Selector时,要求选择器所选择的通道必须是非阻塞的,Selector提供了三种针对服务端事件:

  1. ACCEPT:可接受事件
  2. READ:可读事件
  3. WRITE:可写事件
      客户端样例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {

	public static void main(String[] args) throws IOException {

		SocketChannel sc = SocketChannel.open();

		sc.connect(new InetSocketAddress("localhost", 8090));
		sc.write(ByteBuffer.wrap("hello".getBytes()));
		ByteBuffer buffer = ByteBuffer.allocate(10);
		sc.read(buffer);
		System.out.println(new String(buffer.array(), 0, buffer.position()));
		while(true);

	}
	
}

  服务端样例

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 Server {

	public static void main(String[] args) throws IOException {
		// 开启服务器端的通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.bind(new InetSocketAddress(8090));
		// 要求选择器所选择的通道必须是非阻塞的
		ssc.configureBlocking(false);

		// 获取到选择器
		Selector selc = Selector.open();
		// 把通道注册到选择器上让选择器去管理这个通道
		// 表示当前服务器在选择器上注册并且注册了一个可接受的事件
		ssc.register(selc, SelectionKey.OP_ACCEPT);
		
		while (true) {
			// 选择注册到选择器上的服务器
			selc.select();
			// 获取注册的事件
			// 这个集合中存储的应该是accept/read/write事件
			Set<SelectionKey> set = selc.selectedKeys();
			// 遍历这些事件然后按照不同的事件来进行处理
			Iterator<SelectionKey> it = set.iterator();
			while (it.hasNext()) {
				SelectionKey key = (SelectionKey) it.next();
				// 接受连接
				if (key.isAcceptable()) {
					// 从这个事件中获取到对应的通道
					ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
					SocketChannel sc = sscx.accept();
					while (sc == null)
						sc = sscx.accept();
					System.out.println("连接成功~~~");
					sc.configureBlocking(false);
					// 表示同时注册了可读和可写事件
					sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
				}
				// 可读
				if (key.isReadable()) {
					SocketChannel sc = (SocketChannel) key.channel();
					// 读取数据
					ByteBuffer buffer = ByteBuffer.allocate(1024);
					sc.read(buffer);
					System.out.println(new String(buffer.array(), 0, buffer.position()));
				}
				// 可写
				if (key.isWritable()) {
					SocketChannel sc = (SocketChannel) key.channel();
					sc.write(ByteBuffer.wrap("hi".getBytes()));
				}
			}
			// 事件在处理完成之后需要移除
			it.remove();
		}
	}
}

五、NIO与BIO对比

一、BIO的缺点
1. 阻塞:导致效率降低
2. 一对一连接:导致在客户端大量连入的情况下,会服务器资源的紧张
3. 当客户端连入之后如果不做任何操作也会导致连接一直保持,导致服务器资源的浪费
4. 流式传输

二、NIO的优点
1. 可以手动设置为非阻塞
2. 一对多连接:可以利用一个或者少量的服务器线程来处理大量的请求
3. 每一个请求过来都需要对应的事件来能被处理
4. 采用缓冲区来存储数据,可以对缓冲区进行定点操作

三、NIO的缺点
NIO适用于短任务场景不适合于长任务场景,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值