Netty-NIO

文章目录


一、NIO-Selector

1.处理accept

java 复制代码
//1.创建selector,管理多个channel
Selector selector = Selector.open();
ByteBuffer buffer = ByteBuffer.allocate(16);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
//2.建立selector和channel的联系(注册)
//SelectionKey就是将来事件发生后,通过它可以知道事件和哪个channel的事件
//四个事件:
//accept 会在有连接请求时触发
//connect 是客户端,连接建立后触发
//read 可读事件
//write 可写事件
SelectionKey sscKey = ssc.register(selector, 0, null);
sscKey.interestOps(SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
while(true){
	//3.select方法,没有事件发生,线程阻塞,有事件,线程才会恢复运行
	selector.select();
	//4.处理事件,selectedKeys内部包含了所有发生的事件
	Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
	while(iter.next()){
		SelectionKey key = iter.next();
		ServerSocketChannel channel = (ServerSocketChannel)key.channel();
		SocketChannel sc = channel.accept();
	}
}

2.cancel

java 复制代码
//1.创建selector,管理多个channel
Selector selector = Selector.open();
ByteBuffer buffer = ByteBuffer.allocate(16);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
//2.建立selector和channel的联系(注册)
SelectionKey sscKey = ssc.register(selector, 0, null);
sscKey.interestOps(SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
while(true){
	//3.select方法,没有事件发生,线程阻塞,有事件,线程才会恢复运行
	//select在事件未处理时,它不会阻塞,事件发生后要么处理,要么取消,不能置之不理
	selector.select();
	//4.处理事件,selectedKeys内部包含了所有发生的事件
	Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
	while(iter.next()){
		SelectionKey key = iter.next();
		key.cancel();
	}
}

3.处理read

用完key必须要remove

java 复制代码
//1.创建selector,管理多个channel
Selector selector = Selector.open();
ByteBuffer buffer = ByteBuffer.allocate(16);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
//2.建立selector和channel的联系(注册)
SelectionKey sscKey = ssc.register(selector, 0, null);
sscKey.interestOps(SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
while(true){
	//3.select方法,没有事件发生,线程阻塞,有事件,线程才会恢复运行
	selector.select();
	//4.处理事件,selectedKeys内部包含了所有发生的事件
	//selector会在发生事件后,向集合中加入key,但不会删除
	Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
	while(iter.next()){
		SelectionKey key = iter.next();
		//处理key时,要从selectedKeys集合中删除,否则下次处理就会有问题
		iter.remove();
		//5.区分事件类型
		if(key.isAcceptable()){ //如果是accept
			ServerSocketChannel channel = (ServerSocketChannel)key.channel();
			SocketChannel sc = channel.accept();
			sc.configureBlocking(false);
			SelectionKey sckey = sc.register(selector, 0, null);
			scKey.interestOps(SelectionKey.OP_READ);
		}elseif(key.isReadable()){
			//拿到触发事件的channel
			ServerSocketChannel channel = (ServerSocketChannel)key.channel();
			ByteBuffer buffer = ByteBuffer.allocate(16);
			channel.read(buffer);
			buffer.flip();
			debugRead(buffer);
		}
	}
}

4.处理客户端断开

java 复制代码
//1.创建selector,管理多个channel
Selector selector = Selector.open(); 
ByteBuffer buffer = ByteBuffer.allocate(16); 
ServerSocketChannel ssc = ServerSocketChannel.open(); 
ssc.configureBlocking(false);
//2.建立selector和channel的联系(注册)
SelectionKey sscKey = ssc.register(selector, 0, null); sscKey.interestOps(SelectionKey.OP_ACCEPT); 
ssc.bind(new InetSocketAddress(8080)); 
while(true){ 
	//3.select方法,没有事件发生,线程阻塞,有事件,线程才会恢复运行 
	selector.select(); 
	//4.处理事件,selectedKeys内部包含了所有发生的事件 
	//selector会在发生事件后,向集合中加入key,但不会删除 
	Iterator<SelectionKey> iter = selector.selectedKeys.iterator(); 
	while(iter.next()){ 		
		SelectionKey key = iter.next(); 
		//处理key时,要从selectedKeys集合中删除,否则下次处理就会有问题 
		iter.remove(); 
		//5.区分事件类型 
		if(key.isAcceptable()){ 
			//如果是accept 
			ServerSocketChannel channel = (ServerSocketChannel)key.channel(); 
			SocketChannel sc = channel.accept();
		 	sc.configureBlocking(false); 
		 	SelectionKey sckey = sc.register(selector, 0, null); 
		 	scKey.interestOps(SelectionKey.OP_READ); 
		 }elseif(key.isReadable()){ 
		 	try{ 
			 	//拿到触发事件的channel 
			 	ServerSocketChannel channel = (ServerSocketChannel)key.channel(); 
			 	ByteBuffer buffer = ByteBuffer.allocate(16); 
			 	int read = channel.read(buffer);//如果是正常断开,read的方法的返回值是-1 
			 	if(read == -1){ 
			 		key.cancel(); 
			 	}else{ 
			 	buffer.flip(); 
			 	debugRead(buffer); 
		 		} 
		 	}catch(IOException e){ 
		 		e.printStackTrace();
		 	 	//因为客户端断开了,因此需要将key取消(从selector 的keys集合中真正删除key) 
		 	 	key.cancel();
			}
		}
	}
}

5. 处理消息的边界

  1. 固定消息长度,数据包大小一样,服务器按预定长度读取,缺点是浪费带宽
  2. 按分隔符拆分,缺点是效率低
  3. TLV格式,Type类型,Length长度,Value数据,可以方便获取消息大小,分配合适的buffer,缺点是buffer需要提前分配,如果内容过大,影响server吞吐量
    • Http1.1是TLV格式
    • Http2.0是LTV格式
java 复制代码
private static void split(ByteBuffer source){
	source.flip();
	for(int i = 0; i < source.limit(); i++){
		//找到一条完整消息
		if(source.get(i) == '\n'){
			int length = i + 1 -source.position();
			//把这条完整消息存入新的ByteBuffer
			ByteBuffer target = ByteBuffer.allocate(length);
			//从source读,向target写
			for(int j = 0; j < length; j++){
				target.put(source.get());
			}
			debugAll(target);
		}
	}
	source.compact();
}

public static void main(){
	//1.创建selector,管理多个channel
	Selector selector = Selector.open(); 
	ByteBuffer buffer = ByteBuffer.allocate(16); 
	ServerSocketChannel ssc = ServerSocketChannel.open(); 
	ssc.configureBlocking(false);
	//2.建立selector和channel的联系(注册)
	SelectionKey sscKey = ssc.register(selector, 0, null); 		
	sscKey.interestOps(SelectionKey.OP_ACCEPT); 
	ssc.bind(new InetSocketAddress(8080)); 
	while(true){ 
		//3.select方法,没有事件发生,线程阻塞,有事件,线程才会恢复运行 
		selector.select(); 
		//4.处理事件,selectedKeys内部包含了所有发生的事件 
		//selector会在发生事件后,向集合中加入key,但不会删除 
		Iterator<SelectionKey> iter = selector.selectedKeys.iterator(); 
		while(iter.next()){ 		
			SelectionKey key = iter.next(); 
			//处理key时,要从selectedKeys集合中删除,否则下次处理就会有问题 
			iter.remove(); 
			//5.区分事件类型 
			if(key.isAcceptable()){ 
				//如果是accept 
				ServerSocketChannel channel = (ServerSocketChannel)key.channel(); 
				SocketChannel sc = channel.accept();
			 	sc.configureBlocking(false); 
			 	ByteBuffer buffer = ByteBuffer.allocate(16); //attachment附件
			 	//将一个byteBuffer作为附件关联到selectionKey上
			 	SelectionKey sckey = sc.register(selector, 0, buffer); 
			 	scKey.interestOps(SelectionKey.OP_READ); 
			 }elseif(key.isReadable()){ 
			 	try{ 
				 	//拿到触发事件的channel 
				 	ServerSocketChannel channel = (ServerSocketChannel)key.channel(); 
				 	//获取selectionKey上关联的附件
				 	ByteBuffer buffer = (ByteBuffer)key.attatchment();
				 	int read = channel.read(buffer);//如果是正常断开,read的方法的返回值是-1 
				 	if(read == -1){ 
				 		key.cancel(); 
				 	}else{ 
				 		split(buffer);
				 		if(buffer.position() == buffer.limit()){
				 			//扩容
				 			ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
				 			buffer.flip();
				 			newBuffer.put(buffer);//复制
				 			key.attach(newbuffer);//替换掉key上原有的buffer
				 		}
			 		} 
			 	}catch(IOException e){ 
			 		e.printStackTrace();
			 	 	//因为客户端断开了,因此需要将key取消(从selector 的keys集合中真正删除key) 
			 	 	key.cancel();
				}
			}
		}
	}
}

6. 写入内容过多的问题

java 复制代码
//服务器
public static void main(){
	ServerSocketChannel ssc = ServerSocketChannrl.open();
	ssc.configureBlocking(false);
	Selector selector = Selector.open();
	ssc.register(selector, SelectionKey.OP_ACCEPT);
	ssc.bind(new InetSocketAddress(8080));
	while(trye){
		selector.select();
		Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
		while(iter.hasNext()){
			SelectionKey key = iter.next();
			iter.remove();
			if(key.isAcceptable()){
				SocketChannel sc = ssc.accept();
				sc.configureBlocking(false);
				//1.向客户端发送大量数据
				StringBuilder sb = new StringBuilder();
				for(int i = 0; i < 3000000; i++){
					sb.append("a");
				}
				BytrBuffer buffer = Charset.defaultCharset().encode(sb.toString());
				//不符合非阻塞模式
				while(buffer.hasRemaining()){
					//2.返回值代表实际写入的字节数
					//不能一次性写完
					//write == 0 缓冲区满,写不了
					int write = sc.write(buffer);
					System.out.println(write):
				}
			}
		}
	}
}

//客户端
public static void main(){
	SocketChannel sc = SocketChannel.open();
	sc.connect(new InetSocketAddress("localhost",8080));
	//3.接收数据
	int count = 0;
	while(true){
		ByteBuffer buffer = ByteBuffer.allocate(1024*1024);
		count += sc.read(buffer);
		System.out.println(count);
		buffer.clear();
	}
}

7. 处理可写事件

java 复制代码
//服务器
public static void main(){
	ServerSocketChannel ssc = ServerSocketChannrl.open();
	ssc.configureBlocking(false);
	Selector selector = Selector.open();
	ssc.register(selector, SelectionKey.OP_ACCEPT);
	ssc.bind(new InetSocketAddress(8080));
	while(trye){
		selector.select();
		Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
		while(iter.hasNext()){
			SelectionKey key = iter.next();
			iter.remove();
			if(key.isAcceptable()){
				SocketChannel sc = ssc.accept();
				sc.configureBlocking(false);
				SelectionKey sckey = sc.register(selector, 0, null);
				sckey.interestOps(SelectionKey.OP_READ);
				//1.向客户端发送大量数据
				StringBuilder sb = new StringBuilder();
				for(int i = 0; i < 3000000; i++){
					sb.append("a");
				}
				BytrBuffer buffer = Charset.defaultCharset().encode(sb.toString());
				//2.返回值代表实际写入的字节数
				//不能一次性写完
				//先写一次
				int write = sc.write(buffer);
				System.out.println(write):
				//3.判断是否有剩余内容
				while(buffer.hasRemaining()){
					//4.关注可写事件
					sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
					//sckey.interestOps(sckey.interestOps() | SelectionKey.OP_WRITE);
					//5.把未写完的数据挂到sckey上
					sckey.attach(buffer);
				}
			}elseif(key.isWritable())[
				ByteBuffer buffer = (ByteBuffer) key.attachment();
				SocketChannel sc = (SocketChannel)key.channel();
				int write = sc.write(buffer);
				System.out.println(write):
				//6.清理操作,内存释放
				if(!buffer.haeRemaining()){
					key.attach(null);//需要清除buffer
					key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);//不需关注可写事件
				}
			}
		}
	}
}
相关推荐
带带老表学爬虫28 分钟前
java数据类型转换和注释
java·开发语言
千里码aicood35 分钟前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
cyt涛41 分钟前
MyBatis 学习总结
数据库·sql·学习·mysql·mybatis·jdbc·lombok
彭于晏68943 分钟前
Android广播
android·java·开发语言
程序员-珍1 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
Rookie也要加油1 小时前
01_SQLite
数据库·sqlite
liuxin334455661 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
2401_857297911 小时前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
福大大架构师每日一题2 小时前
23.1 k8s监控中标签relabel的应用和原理
java·容器·kubernetes
金灰2 小时前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5