aio和nio和bio实践

网络传输中分为BIO,AIO,NIO。

其中BIO是blocking阻塞io读取,AIO异步io读取,NIO解决高并发的异步io读取。

BIO

网络传输数据的方式,客户端发送请求(等待服务端返回数据)----服务端接收请求,完成业务。返回信息到客户端。

这个就是早期传统的请求方式(ServerSocket)

AIO

异步非阻塞,可以不等待服务端就可以去干其他的事情。

 

NIO,多路复用,AIO虽然可以异步,但是每次操作时,都是通过一个线程来完成的。

下面是代码:

BIOServer

package com.ty.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

	public static void main(String[] args) {
		int port = genPort(args);
		
		ServerSocket server = null;
		
		try{
			server = new ServerSocket(port);
			System.out.println("server started!");
			while(true){
				Socket socket = server.accept();
				
				new Thread(new Handler(socket)).start();
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(server != null){
				try {
					server.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			server = null;
		}
	}
	
	static class Handler implements Runnable{
		Socket socket = null;
		public Handler(Socket socket){
			this.socket = socket;
		}
		@Override
		public void run() {
			BufferedReader reader = null;
			PrintWriter writer = null;
			try{
				reader = new BufferedReader(
						new InputStreamReader(socket.getInputStream(), "UTF-8"));
				writer = new PrintWriter(
						new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
				String readMessage = null;
				while(true){
					System.out.println("server reading... ");
					if((readMessage = reader.readLine()) == null){
						break;
					}
					System.out.println(readMessage);
					Thread.sleep(5000);
					writer.println("服务端返回的数据: " + readMessage);
					writer.flush();
				}
			}catch(Exception e){
				e.printStackTrace();
			}finally{
				if(socket != null){
					try {
						socket.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				socket = null;
				if(reader != null){
					try {
						reader.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				reader = null;
				if(writer != null){
					writer.close();
				}
				writer = null;
			}
		}
		
	}
	
	private static int genPort(String[] args){
		if(args.length > 0){
			try{
				return Integer.parseInt(args[0]);
			}catch(NumberFormatException e){
				return 9999;
			}
		}else{
			return 9999;
		}
	}
	
}

BIOclient

package com.ty.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class Client {
	public static void main(String[] args) {
		String host = null;
		int port = 0;
		if(args.length > 2){
			host = args[0];
			port = Integer.parseInt(args[1]);
		}else{
			host = "127.0.0.1";
			port = 9999;
		}
		
		Socket socket = null;
		BufferedReader reader = null;
		PrintWriter writer = null;
		Scanner s = new Scanner(System.in);
		try{
			socket = new Socket(host, port);
			String message = null;
			
			reader = new BufferedReader(
					new InputStreamReader(socket.getInputStream(), "UTF-8"));

			writer = new PrintWriter(
					socket.getOutputStream(), true);
			while(true){
				message = s.nextLine();
				if(message.equals("exit")){
					break;
				}
				writer.println(message);
				writer.flush();
				System.out.println("数据已经发送给服务端,等待服务端返回信息");
				System.out.println(reader.readLine());
				System.out.println("我想干点其他事情,但是我被阻塞了,得等reader.readLine()方法有结果后,我才能执行");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(socket != null){
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			socket = null;
			if(reader != null){
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			reader = null;
			if(writer != null){
				writer.close();
			}
			writer = null;
		}
	}
}

 AIO

package com.ty.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class AIOServer {

	// 线程池, 提高服务端效率。
	private ExecutorService service;
	// 线程组
	// private AsynchronousChannelGroup group;
	// 服务端通道, 针对服务器端定义的通道。
	private AsynchronousServerSocketChannel serverChannel;
	
	public AIOServer(int port){
		init(9999);
	}
	
	private void init(int port){
		try {
			System.out.println("server starting at port : " + port + " ...");
			// 定长线程池
			service = Executors.newFixedThreadPool(4);
			/* 使用线程组
			group = AsynchronousChannelGroup.withThreadPool(service);
			serverChannel = AsynchronousServerSocketChannel.open(group);
			*/
			// 开启服务端通道, 通过静态方法创建的。
			serverChannel = AsynchronousServerSocketChannel.open();
			// 绑定监听端口, 服务器启动成功,但是未监听请求。
			serverChannel.bind(new InetSocketAddress(port));
			System.out.println("server started.");
			// 开始监听 
			// accept(T attachment, CompletionHandler<AsynchronousSocketChannel, ? super T>)
			// AIO开发中,监听是一个类似递归的监听操作。每次监听到客户端请求后,都需要处理逻辑开启下一次的监听。
			// 下一次的监听,需要服务器的资源继续支持。
			serverChannel.accept(this, new AIOServerHandler());
			try {
				TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new AIOServer(9999);
	}

	public ExecutorService getService() {
		return service;
	}

	public void setService(ExecutorService service) {
		this.service = service;
	}

	public AsynchronousServerSocketChannel getServerChannel() {
		return serverChannel;
	}

	public void setServerChannel(AsynchronousServerSocketChannel serverChannel) {
		this.serverChannel = serverChannel;
	}
	
}

 

package com.ty.aio;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;

public class AIOServerHandler implements CompletionHandler<AsynchronousSocketChannel, AIOServer> {

	/**
	 * 业务处理逻辑, 当请求到来后,监听成功,应该做什么。
	 * 一定要实现的逻辑: 为下一次客户端请求开启监听。accept方法调用。
	 * result参数 : 就是和客户端直接建立关联的通道。
	 *  无论BIO、NIO、AIO中,一旦连接建立,两端是平等的。
	 *  result中有通道中的所有相关数据。如:OS操作系统准备好的读取数据缓存,或等待返回数据的缓存。
	 */
	@Override
	public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
		// 处理下一次的客户端请求。类似递归逻辑
		attachment.getServerChannel().accept(attachment, this);
		doRead(result);
	}

	/**
	 * 异常处理逻辑, 当服务端代码出现异常的时候,做什么事情。
	 */
	@Override
	public void failed(Throwable exc, AIOServer attachment) {
		exc.printStackTrace();
	}
	
	/**
	 * 真实项目中,服务器返回的结果应该是根据客户端的请求数据计算得到的。不是等待控制台输入的。
	 * @param result
	 */
	private void doWrite(AsynchronousSocketChannel result){
		try {
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			System.out.print("enter message send to client > ");
			Scanner s = new Scanner(System.in);
			String line = s.nextLine();
			buffer.put(line.getBytes("UTF-8"));
			// 重点:必须复位,必须复位,必须复位
			buffer.flip();
			// write方法是一个异步操作。具体实现由OS实现。 可以增加get方法,实现阻塞,等待OS的写操作结束。
			result.write(buffer);
			// result.write(buffer).get(); // 调用get代表服务端线程阻塞,等待写操作完成
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}/* catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}*/
	}
	
	private void doRead(final AsynchronousSocketChannel channel){
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		/*
		 * 异步读操作, read(Buffer destination, A attachment, 
		 *                    CompletionHandler<Integer, ? super A> handler)
		 * destination - 目的地, 是处理客户端传递数据的中转缓存。 可以不使用。
		 * attachment - 处理客户端传递数据的对象。 通常使用Buffer处理。
		 * handler - 处理逻辑
		 */
		channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {

			/**
			 * 业务逻辑,读取客户端传输数据
			 * attachment - 在completed方法执行的时候,OS已经将客户端请求的数据写入到Buffer中了。
			 *  但是未复位(flip)。 使用前一定要复位。
			 */
			@Override
			public void completed(Integer result, ByteBuffer attachment) {
				try {
					System.out.println(attachment.capacity());
					// 复位
					attachment.flip();
					System.out.println("from client : " + new String(attachment.array(), "UTF-8"));
					System.out.println("我已收到客户端的信息;我要休息5秒,再返回信息");
					Thread.sleep(5000);
					doWrite(channel);
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			@Override
			public void failed(Throwable exc, ByteBuffer attachment) {
				exc.printStackTrace();
			}
		});
	}

}

 

package com.ty.aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AIOClient {
	
	private AsynchronousSocketChannel channel;
	
	public AIOClient(String host, int port){
		init(host, port);
	}
	
	private void init(String host, int port){
		try {
			// 开启通道
			channel = AsynchronousSocketChannel.open();
			// 发起请求,建立连接。
			channel.connect(new InetSocketAddress(host, port));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void write(String line){
		try {
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			buffer.put(line.getBytes("UTF-8"));
			buffer.flip();
			System.out.println("已经发送数据给服务端,正在等待服务端返回数据");
			channel.write(buffer);

			System.out.println("在AIO中,数据发送给服务端后,可以立即去干其他的事情,不会被阻塞");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
	
	public void read(){
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		try {
			System.out.println("准备开始读数据");
			// read方法是异步方法,OS实现的。get方法是一个阻塞方法,会等待OS处理结束后再返回。
			channel.read(buffer).get();
			// channel.read(dst, attachment, handler);
			buffer.flip();
			System.out.println("from server : " + new String(buffer.array(), "UTF-8"));
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
	
	public void doDestory(){
		if(null != channel){
			try {
				channel.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		AIOClient client = new AIOClient("localhost", 9999);
		try{
			System.out.print("enter message send to server > ");
			Scanner s = new Scanner(System.in);
			String line = s.nextLine();
			client.write(line);
			client.read();
		}finally{
			client.doDestory();
		}
	}

}

 

package com.ty.nio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
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.Scanner;

public class NIOServer implements Runnable {

	// 多路复用器, 选择器。 用于注册通道的。
	private Selector selector;
	// 定义了两个缓存。分别用于读和写。 初始化空间大小单位为字节。
	private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
	private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
	
	public static void main(String[] args) {
		new Thread(new NIOServer(9999)).start();
	}
	
	public NIOServer(int port) {
		init(port);
	}
	
	private void init(int port){
		try {
			System.out.println("server starting at port " + port + " ...");
			// 开启多路复用器
			this.selector = Selector.open();
			// 开启服务通道
			ServerSocketChannel serverChannel = ServerSocketChannel.open();
			// 非阻塞, 如果传递参数true,为阻塞模式。
			serverChannel.configureBlocking(false);
			// 绑定端口
			serverChannel.bind(new InetSocketAddress(port));
			// 注册,并标记当前服务通道状态
			/*
			 * register(Selector, int)
			 * int - 状态编码
			 *  OP_ACCEPT : 连接成功的标记位。
			 *  OP_READ : 可以读取数据的标记
			 *  OP_WRITE : 可以写入数据的标记
			 *  OP_CONNECT : 连接建立后的标记
			 */
			serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
			System.out.println("server started.");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void run(){
		while(true){
			try {
				// 阻塞方法,当至少一个通道被选中,此方法返回。
				// 通道是否选择,由注册到多路复用器中的通道标记决定。
				this.selector.select();
				// 返回以选中的通道标记集合, 集合中保存的是通道的标记。相当于是通道的ID。
				Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
				while(keys.hasNext()){
					SelectionKey key = keys.next();
					// 将本次要处理的通道从集合中删除,下次循环根据新的通道列表再次执行必要的业务逻辑
					keys.remove();
					// 通道是否有效
					if(key.isValid()){
						// 阻塞状态
						try{
							if(key.isAcceptable()){
								accept(key);
							}
						}catch(CancelledKeyException cke){
							// 断开连接。 出现异常。
							key.cancel();
						}
						// 可读状态
						try{
							if(key.isReadable()){
								read(key);
							}
						}catch(CancelledKeyException cke){
							key.cancel();
						}
						// 可写状态
						try{
							if(key.isWritable()){
								write(key);
							}
						}catch(CancelledKeyException cke){
							key.cancel();
						}
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
	}
	
	private void write(SelectionKey key){
		this.writeBuffer.clear();
		SocketChannel channel = (SocketChannel)key.channel();
		Scanner reader = new Scanner(System.in);
		try {
			System.out.print("put message for send to client > ");
			String line = reader.nextLine();
			// 将控制台输入的字符串写入Buffer中。 写入的数据是一个字节数组。
			writeBuffer.put(line.getBytes("UTF-8"));
			writeBuffer.flip();
			channel.write(writeBuffer);
			
			channel.register(this.selector, SelectionKey.OP_READ);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void read(SelectionKey key){
		try {
			// 清空读缓存。
			this.readBuffer.clear();
			// 获取通道
			SocketChannel channel = (SocketChannel)key.channel();
			// 将通道中的数据读取到缓存中。通道中的数据,就是客户端发送给服务器的数据。
			int readLength = channel.read(readBuffer);
			// 检查客户端是否写入数据。
			if(readLength == -1){
				// 关闭通道
				key.channel().close();
				// 关闭连接
				key.cancel();
				return;
			}
			/*
			 * flip, NIO中最复杂的操作就是Buffer的控制。
			 * Buffer中有一个游标。游标信息在操作后不会归零,如果直接访问Buffer的话,数据有不一致的可能。
			 * flip是重置游标的方法。NIO编程中,flip方法是常用方法。
			 */
			this.readBuffer.flip();
			// 字节数组,保存具体数据的。 Buffer.remaining() -> 是获取Buffer中有效数据长度的方法。
			byte[] datas = new byte[readBuffer.remaining()];
			// 是将Buffer中的有效数据保存到字节数组中。
			readBuffer.get(datas);
			System.out.println("from " + channel.getRemoteAddress() + " client : " + new String(datas, "UTF-8"));
			
			// 注册通道, 标记为写操作。
			channel.register(this.selector, SelectionKey.OP_WRITE);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {
				key.channel().close();
				key.cancel();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}
	
	private void accept(SelectionKey key){
		try {
			// 此通道为init方法中注册到Selector上的ServerSocketChannel
			ServerSocketChannel serverChannel = (ServerSocketChannel)key.channel();
			// 阻塞方法,当客户端发起请求后返回。 此通道和客户端一一对应。
			SocketChannel channel = serverChannel.accept();
			channel.configureBlocking(false);
			// 设置对应客户端的通道标记状态,此通道为读取数据使用的。
			channel.register(this.selector, SelectionKey.OP_READ);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
}
package com.ty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {

	public static void main(String[] args) {
		// 远程地址创建
		InetSocketAddress remote = new InetSocketAddress("localhost", 9999);
		SocketChannel channel = null;
		
		// 定义缓存。
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		
		try {
			// 开启通道
			channel = SocketChannel.open();
			// 连接远程服务器。
			channel.connect(remote);
			Scanner reader = new Scanner(System.in);
			while(true){
				System.out.print("put message for send to server > ");
				String line = reader.nextLine();
				if(line.equals("exit")){
					break;
				}
				// 将控制台输入的数据写入到缓存。
				buffer.put(line.getBytes("UTF-8"));
				// 重置缓存游标
				buffer.flip();
				System.out.println("写数据到服务器");
				// 将数据发送给服务器
				channel.write(buffer);
				// 清空缓存数据。
				buffer.clear();

				// 读取服务器返回的数据
				int readLength = channel.read(buffer);
				System.out.println("从服务器读取数据");
				if(readLength == -1){
					break;
				}
				// 重置缓存游标
				buffer.flip();
				byte[] datas = new byte[buffer.remaining()];
				// 读取数据到字节数组。
				buffer.get(datas);
				System.out.println("from server : " + new String(datas, "UTF-8"));
				// 清空缓存。
				buffer.clear();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			if(null != channel){
				try {
					channel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值