JavaNIO设计客户端和服务端通信

本文介绍了Java NIO的基础知识,包括阻塞与非阻塞、同步与异步的区别,以及IO事件的轮询技术——多路复用。详细讲解了Java的BIO、NIO和AIO模型,强调NIO的同步非阻塞特性,并通过Selector和Channel阐述了NIO的工作原理。最后,提到了AIO的异步非阻塞特性以及在实际网络通信中的应用。
摘要由CSDN通过智能技术生成

学习NIO之前我们先看下面几个知识点:
1.阻塞和非阻塞:阻塞和非阻塞是进程在访问数据的时候,数据内是否准备就绪的一种处理方式,
当数据没有准备的时候:
阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
非阻塞:当我们的进程访问我们的数据缓冲区的时候,数据没有准备好的时候直接返回,
不需要等待,数据有的时候,也直接返回。

2.同步和异步的方式
同步和异步都是基于应用程序和操作系统处理IO时间锁采用的方式,比如同步应用程序直接参与IO读写的操作。
2.1异步:所有的IO读写交给操作系统去处理同步的方式在处理IO事件的时候,
必须阻塞在某个方法上面等待我们的IO时间完成(阻塞IO事件或则通过轮询IO时间的方式)
对于异步来说,所有的IO读写都交给了操作系统,这个时候,我们可以去其他的事情,并不
需要去完成真正的IO操作,当操作完成IO后给我们的应用程序一个通知就可以。
2.2同步:1.阻塞到IO事件,阻塞到read或则write。这个时候我们就不能完全做自己的事情。
让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。

3.IO事件的轮询,多路复用技术(select模式)
3.1读写事件交给一个单独线程来处理,这个完成IO事件的注册功能,还有就是不断的去轮询。
我们的读写缓冲区,看是否有数据准备好。通知我们相应的读写线程。这样的话,以前的读写线程就可以
做其他的事情,这个时候阻塞的不是所有的IO线程。阻塞的select这个线程。
Cilent ----- ----> Select管家 ---- ------> Server
解释:当客人来的时候,就给管家说,我来了,管家得到这个注册信息后,给Server说,我这里
有一个或则多个客人,Server你去给某人A这件东西,给另外人B这样东西,这个时候客人是可以
去做自己的事情,比如看看花园等等,当管家知道Server给他任务后,他就去找对应的某人,告诉他
Server给他某样东西。(根据我们的注册信息)

3.2Java IO模型
BIO :jdk1.4以前我们使用都是BIO,阻塞IO。阻塞到我们的读写方法,阻塞到线程上提供线程
对于线程的开销本来就是性能的浪费。
NIO:jdk1.4 Linux多路复用技术(selet模式),实现IO事件的轮询方式 :同步非阻塞的模式
对于这种方式目前是主流的网络通信模式。
Mina,netty mina2.0 netty5.0–网络通信框架。比我们直接写NIO要容易些,并且代码可读性更好。
AIO:jdk1.7后(NIO2)才是实现真正的异步Aio,学习linux epoll模式
Aio使用的比较少。
小结: 1.BIO阻塞的IO
2.NIO select+非阻塞 实现同步非阻塞
3.AIOA 异步非阻塞IO
4.NIO AIO 原理的解读
对于网络通信而言NIO,AIO并没有改变网络通信的基本步骤,只是在其原来的基础上(serverscoket,socket)做了一个改进。
3.3Socket <–建立连接需要三次握手–> ServerSocket
对于三次握手的方式,建立稳定的连接性能开销比较大,解决方案从思想上来说比较容易
就是减少连接的次数,对我们的读写通信管道进行一个抽象。
对于读和写采用抽象的管道的概念:
3.4Channel:是一个在TCP连接之间抽象,一个TCP连接可以对应多个管道,而不是以前的方式只有
一个通信信道,减少了TCP连接的次数。
UDP:采用相同方式,也是抽象成管道。
3.5NIO模型原理:
通过我们的selctor(选择器)就相对于一个管家,管理所有的IO事件,
connection accept 客户端和服务端的读写。–IO事件都交给我们的selctor管理
selctor(选择器)如何进行管理IO事件
当IO事件注册给我们的选择器的时候,选择器会给他们分配一个ket(可以简单的理解成一个事件的标签)
当我们的IO事件完成后悔通过key值来找到相应的管道,然后通过管道发送数据和接受数据等操作。
对于读和写数据的缓冲区:
通过byteBuffer类,提供很多读写的方法:put() get()方法
服务端:ServerSocketChannel
客户端:SocketChanner
选择器:Selector selector=Select.open();这样我们就打开了我们的选择器

4.Selectionkey:可以通过它来判断IO事件是否已经就绪。
key.Accptable:是否可以接受客户端的连接
key.connctionable:是否可以连接服务端
key.isreadable():缓冲区是否可读
key.iswriteable:缓冲区是否可写

5.如何获得事件keys值
Selecionkey keys=selector.selectedkeys();
6.如何注册

channel.regist(Selctor,Selectionkey_OP_Write);
channel.regist(Selctor,Selectionkey_OP_Read);

7.AIO

服务端:AsynchronousServerSocketChannel
客户端:AsynchronousSocketChannel
用户处理器:CompletionHandler接口,这个接口实现应用程序向操作系统发起IO请求
当完成后去处理具体的逻辑,否则做自己该做的事情。
下面来看一下应用:
服务端程序代码:

package com.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.Set;
import java.util.Iterator;


public class NIOServer {

	private int flag=1;
	//缓冲区大小
	private int blockSize=4096;
	//发送数据缓冲区
	private ByteBuffer sendbuffer=ByteBuffer.allocate(blockSize);
	//接受数据缓冲区
	private ByteBuffer receivebuffer=ByteBuffer.allocate(blockSize);
	//选择器
	private Selector selector;
	//初始化服务端对象
	public NIOServer(int port) throws IOException {
		//ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。
		ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
		//设置是否 为非阻塞
		serverSocketChannel.configureBlocking(false);
		//获取Socket
		ServerSocket serverSocket=serverSocketChannel.socket();
		//绑定ip和端口
		serverSocket.bind(new InetSocketAddress(port));
		//打开选择器
		selector=Selector.open();
		//注册 打开与接受客户端的连接
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		System.out.println("Server start->"+port);
	}
	//监听客服端连接
	public void listen() throws IOException {
		while(true) {
			//轮询选择器
			selector.select();
			//遍历选择器    返回一个Set集合
			Set<SelectionKey> selectedKeys=selector.selectedKeys();
			Iterator<SelectionKey> itetor=selectedKeys.iterator();
			while(itetor.hasNext()) {
				SelectionKey selectionKey=itetor.next();
				itetor.remove();
				//业务逻辑
				handleKey(selectionKey);
			}
		}
	}
	public void handleKey(SelectionKey selectionKey) throws IOException {
		//服务端
		ServerSocketChannel server=null;
		//客户端
		SocketChannel client=null;
		String reciveText;
		String sendText;
		int count=0;
		//是否与客服端连接
		if(selectionKey.isAcceptable()) {
			server=(ServerSocketChannel)selectionKey.channel();
			client=server.accept();
			client.configureBlocking(false);
			//读事件
			client.register(selector,SelectionKey.OP_READ);	
		}else if(selectionKey.isReadable()) {
			client=(SocketChannel) selectionKey.channel();
			//内容读到缓冲区
			receivebuffer.clear();
			count=client.read(receivebuffer);


			if(count>0) {
				//从缓存中取出内容体
				reciveText=new String(receivebuffer.array(),0,count);
				System.out.println("服务端接受到客服端的信息:"+reciveText);
				//注册 写事件
				client.register(selector,SelectionKey.OP_WRITE);
			}
			selectionKey.cancel();
		}else if(selectionKey.isWritable()) {
			//清除缓冲区	
			sendbuffer.clear();
			client=(SocketChannel) selectionKey.channel();
			sendText="msg send to client"+flag++;
			sendbuffer.put(sendText.getBytes());
			//flip()使缓冲区为一系列新的通道写入或相对获取 操作做好准备
			sendbuffer.flip();
			//发送数据
			client.write(sendbuffer);
			System.out.println("服务端发送数据给客服端:"+sendText);
			
		}
		
	}
	public static void main(String[] args) throws IOException {
		int port=6005;
		NIOServer server=new NIOServer(port);
		server.listen();
	}
	
}

客户端代码:

package com.nio;

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.SocketChannel;
import java.util.Set;



import java.util.Iterator;

public class NIOClient {

	private static int flag=1;
	//缓冲区大小
	private static int blockSize=4096;
	//发送数据缓冲区
	private static ByteBuffer sendbuffer=ByteBuffer.allocate(blockSize);
	//接受数据缓冲区
	private static ByteBuffer receivebuffer=ByteBuffer.allocate(blockSize);
	//设置ip和地址
	private final static InetSocketAddress serverAddress=new InetSocketAddress("127.0.0.1", 6005);
	
	
	public static void main(String[] args) throws IOException {

		//打开服务端Channel
		SocketChannel socketChannel=SocketChannel.open(); 
		//设置为非阻塞
		socketChannel.configureBlocking(false);
		//连接这个通道的插座
		socketChannel.connect(serverAddress);
		//打开选择器
		Selector selector=Selector.open();
		//把channel注册到选择器上 是否与服务端连接
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
		
		
		Set<SelectionKey> selectionKeys;
		Iterator<SelectionKey> iterator;
		SelectionKey selectionKey = null;
		SocketChannel client = null;
		int count=0;
		//接受数据
		String receiveTest;
		//发送数据
		String sendText;
		while(true) {
			//
			selector.select();
			selectionKeys=selector.selectedKeys();
			iterator=selectionKeys.iterator();
			
			while(iterator.hasNext()) {
			selectionKey=iterator.next();
			
			//是否可以连接服务器
			if(selectionKey.isConnectable()) {
				System.out.println("clent connet");
				client=(SocketChannel) selectionKey.channel();
				System.out.println("starting 。。。。。");
				//告诉是否该通道的连接操作正在进行中。 
				if(client.finishConnect()) {
//					//完成了一个套接字通道的连接的过程。 
//					client.finishConnect();
					System.out.println("客服端完成连接操作");
					//清空缓存
					sendbuffer.clear();
					//将内容写到缓存
					sendbuffer.put("Hello ,Server".getBytes());
					//flip()使缓冲区为一系列新的通道写入或相对获取 操作做好准备
					sendbuffer.flip();
					//从给定的缓冲区写入该通道的一个字节序列。 
					client.write(sendbuffer);
					
					client.register(selector, SelectionKey.OP_READ);
				}							
				//
			}	if(selectionKey.isWritable()) {
				sendbuffer.clear();
				client=(SocketChannel) selectionKey.channel();
				sendText="Msg send to Server"+flag++;
				sendbuffer.put(sendText.getBytes());
				sendbuffer.flip();
				client.write(sendbuffer);
				System.out.println("客服端发送数据给服务端"+sendText);
				client.register(selector, SelectionKey.OP_READ);
				
			}
			if(selectionKey.isReadable()) {
				client=(SocketChannel) selectionKey.channel();
				receivebuffer.clear();
				client.read(receivebuffer);
				count=client.read(receivebuffer);
				if(count>0) {
					receiveTest=new String(receivebuffer.array(), 0, count);
					System.out.println("客户端接受到服务端的数据"+receiveTest);
					
				}
				client.register(selector,SelectionKey.OP_WRITE);
			}	
			selectionKeys.clear();
			//iterator.remove();
				
		}
	}	
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值