Java NIO Demo全

1、文件NIO

package cn.edu.jxnu.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import org.junit.Test;

/**
 * @ClassName: NIODemo1.java
 * @author Mr.Li
 * @Description: 利用通道完成文件的复制(非直接缓冲区)
 * @Date 2018-1-28 下午7:09:28
 * @version V1.0
 */
public class NIODemo1 {
	/**
	 * @description 通道(Channel):用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。
	 * Channel 本身不存储数据,因此需要配合缓冲区进行传输。
	 */
	@Test
	public void test1() {
		
		long start = System.currentTimeMillis();

		FileInputStream fis = null;
		FileOutputStream fos = null;
		
		// ①获取通道
		FileChannel inChannel = null;
		FileChannel outChannel = null;
		try {
			//1G+的exe文件
			fis = new FileInputStream("g:/myeclipse2013.exe");
			fos = new FileOutputStream("g:/JAVA_PROJECT/myeclipse2013.exe");

			inChannel = fis.getChannel();
			outChannel = fos.getChannel();

			// ②分配指定大小的缓冲区
			ByteBuffer buf = ByteBuffer.allocate(1024);

			// ③将通道中的数据存入缓冲区中
			while (inChannel.read(buf) != -1) {
				
				// 切换读取数据的模式
				buf.flip(); 
				
				// ④将缓冲区中的数据写入通道中
				outChannel.write(buf);
				// 清空缓冲区
				buf.clear(); 
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (outChannel != null) {
				try {
					outChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (inChannel != null) {
				try {
					inChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		long end = System.currentTimeMillis();
		System.out.println("耗费时间为:" + (end - start));

	}
}
package cn.edu.jxnu.nio;

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

/**
 * @ClassName: NIODemo2.java
 * @author Mr.Li
 * @Description: 使用直接缓冲区完成文件的复制(内存映射文件) JDK1.7 NIO.2版
 */
public class NIODemo2 {

	// 只支持Byte 不能由程序来控制结束的时间 由os决定
	@Test
	public void test2() throws IOException {
		long start = System.currentTimeMillis();
		// myeclipse2013.exe 1G -> out of memory 因为是直接使用内存 我的机器是4G可用不足
		// 使用小的图片测试
		FileChannel inChannel = FileChannel.open(Paths.get("g:/复杂度.png"),
				StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(
				Paths.get("g:/JAVA_PROJECT/复杂度.png"), StandardOpenOption.WRITE,
				StandardOpenOption.READ, StandardOpenOption.CREATE);

		// 内存映射文件
		MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0,
				inChannel.size());
		MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0,
				inChannel.size());

		// 直接对缓冲区进行数据的读写操作
		byte[] dst = new byte[inMappedBuf.limit()];
		inMappedBuf.get(dst);
		outMappedBuf.put(dst);

		inChannel.close();
		outChannel.close();

		long end = System.currentTimeMillis();
		System.out.println("耗费时间为:" + (end - start));
	}
}

2、通道数据传输

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

/**
 * @ClassName: NIODemo3.java
 * @author Mr.Li
 * @Description: 通道之间的数据传输(直接缓冲区)
 */
public class NIODemo3 {

	@Test
	public void test3() throws IOException {
		FileChannel inChannel = FileChannel.open(Paths.get("g:/复杂度.png"),
				StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(
				Paths.get("g:/JAVA_PROJECT/复杂度.png"), StandardOpenOption.WRITE,
				StandardOpenOption.READ, StandardOpenOption.CREATE);

		// inChannel.transferTo(0, inChannel.size(), outChannel);
		outChannel.transferFrom(inChannel, 0, inChannel.size());

		inChannel.close();
		outChannel.close();
	}
}

3、分散于聚集

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import org.junit.Test;

/**
 * @ClassName: NIODemo4.java
 * @author Mr.Li
 * @Description: 分散和聚集
 */
public class NIODemo4 {
	/**
	 * 分散(Scatter)与聚集(Gather) 
	 * 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
	 * 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
	 * 
	 * @throws IOException
	 */
	@Test
	public void test4() throws IOException {
		RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");

		// 1. 获取通道
		FileChannel channel1 = raf1.getChannel();

		// 2. 分配指定大小的缓冲区
		ByteBuffer buf1 = ByteBuffer.allocate(100);
		ByteBuffer buf2 = ByteBuffer.allocate(1024);

		// 3. 分散读取
		ByteBuffer[] bufs = { buf1, buf2 };
		channel1.read(bufs);

		for (ByteBuffer byteBuffer : bufs) {
			byteBuffer.flip();
		}

		System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
		System.out.println("-----------------");
		System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
		raf1.close();

		// 4. 聚集写入
		RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
		FileChannel channel2 = raf2.getChannel();

		channel2.write(bufs);
		raf2.close();
	}
}

4、解码于编码

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.junit.Test;

/**
 * @ClassName: NIODemo5.java
 * @author Mr.Li
 * @Description: 解码与编码
 */
public class NIODemo5 {
	// 字符集
	@Test
	public void test6() throws IOException {
		Charset cs1 = Charset.forName("UTF-8");

		// 获取编码器
		CharsetEncoder ce = cs1.newEncoder();

		// 获取解码器
		CharsetDecoder cd = cs1.newDecoder();

		CharBuffer cBuf = CharBuffer.allocate(1024);
		cBuf.put("哈哈哈哈!");
		cBuf.flip();

		// 编码
		ByteBuffer bBuf = ce.encode(cBuf);

		for (int i = 0; i < 12; i++) {
			System.out.println(bBuf.get());
		}

		// 解码
		bBuf.flip();
		CharBuffer cBuf2 = cd.decode(bBuf);
		System.out.println(cBuf2.toString());

		System.out.println("------------------------------------------------------");
		// 乱码
		Charset cs2 = Charset.forName("GBK");
		bBuf.flip();
		CharBuffer cBuf3 = cs2.decode(bBuf);
		System.out.println(cBuf3.toString());
	}

	@Test
	public void test5() {
		Map<String, Charset> map = Charset.availableCharsets();

		Set<Entry<String, Charset>> set = map.entrySet();

		for (Entry<String, Charset> entry : set) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
	}
}

5、网络IO

阻塞

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

/*
 * 一、使用 NIO 完成网络通信的三个核心:
 * 
 * 1. 通道(Channel):负责连接
 * 		
 * 	   java.nio.channels.Channel 接口:
 * 			|--SelectableChannel
 * 				|--SocketChannel
 * 				|--ServerSocketChannel
 * 				|--DatagramChannel
 * 
 * 				|--Pipe.SinkChannel
 * 				|--Pipe.SourceChannel
 * 
 * 2. 缓冲区(Buffer):负责数据的存取
 * 
 * 3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
 * 
 */
public class TestBlockingNIO {

	//客户端
	@Test
	public void client() throws IOException{
		//1. 获取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
		
		FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		
		//2. 分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//3. 读取本地文件,并发送到服务端
		while(inChannel.read(buf) != -1){
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		
		//4. 关闭通道
		inChannel.close();
		sChannel.close();
	}
	
	//服务端
	@Test
	public void server() throws IOException{
		//1. 获取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
		
		//2. 绑定连接
		ssChannel.bind(new InetSocketAddress(9898));
		
		//3. 获取客户端连接的通道
		SocketChannel sChannel = ssChannel.accept();
		
		//4. 分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//5. 接收客户端的数据,并保存到本地
		while(sChannel.read(buf) != -1){
			buf.flip();
			outChannel.write(buf);
			buf.clear();
		}
		
		//6. 关闭通道
		sChannel.close();
		outChannel.close();
		ssChannel.close();
		
	}
	
}

带反馈

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class TestBlockingNIO2 {
	
	//客户端
	@Test
	public void client() throws IOException{
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
		
		FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		while(inChannel.read(buf) != -1){
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		
		sChannel.shutdownOutput();
		
		//接收服务端的反馈
		int len = 0;
		while((len = sChannel.read(buf)) != -1){
			buf.flip();
			System.out.println(new String(buf.array(), 0, len));
			buf.clear();
		}
		
		inChannel.close();
		sChannel.close();
	}
	
	//服务端
	@Test
	public void server() throws IOException{
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
		
		ssChannel.bind(new InetSocketAddress(9898));
		
		SocketChannel sChannel = ssChannel.accept();
		
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		while(sChannel.read(buf) != -1){
			buf.flip();
			outChannel.write(buf);
			buf.clear();
		}
		
		//发送反馈给客户端
		buf.put("服务端接收数据成功".getBytes());
		buf.flip();
		sChannel.write(buf);
		
		sChannel.close();
		outChannel.close();
		ssChannel.close();
	}

}

非阻塞NIO

package cn.edu.jxnu.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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

/*
 * 一、使用 NIO 完成网络通信的三个核心:
 * 
 * 1. 通道(Channel):负责连接
 * 		
 * 	   java.nio.channels.Channel 接口:
 * 			|--SelectableChannel
 * 				|--SocketChannel
 * 				|--ServerSocketChannel
 * 				|--DatagramChannel
 * 
 * 				|--Pipe.SinkChannel
 * 				|--Pipe.SourceChannel
 * 
 * 2. 缓冲区(Buffer):负责数据的存取
 * 
 * 3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
 * 
 */
public class TestNonBlockingNIO {
	
	//客户端
	@Test
	public void client() throws IOException{
		//1. 获取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
		
		//2. 切换非阻塞模式
		sChannel.configureBlocking(false);
		
		//3. 分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//4. 发送数据给服务端
		Scanner scan = new Scanner(System.in);
		
		while(scan.hasNext()){
			String str = scan.next();
			buf.put((new Date().toString() + "\n" + str).getBytes());
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		
		//5. 关闭通道
		sChannel.close();
		scan.close();
	}

	//服务端
	@Test
	public void server() throws IOException{
		//1. 获取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		//2. 切换非阻塞模式
		ssChannel.configureBlocking(false);
		
		//3. 绑定连接
		ssChannel.bind(new InetSocketAddress(9898));
		
		//4. 获取选择器
		Selector selector = Selector.open();
		
		//5. 将通道注册到选择器上, 并且指定“监听接收事件”
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		//6. 轮询式的获取选择器上已经“准备就绪”的事件
		while(selector.select() > 0){
			
			//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			
			while(it.hasNext()){
				//8. 获取准备“就绪”的是事件
				SelectionKey sk = it.next();
				
				//9. 判断具体是什么事件准备就绪
				if(sk.isAcceptable()){
					//10. 若“接收就绪”,获取客户端连接
					SocketChannel sChannel = ssChannel.accept();
					
					//11. 切换非阻塞模式
					sChannel.configureBlocking(false);
					
					//12. 将该通道注册到选择器上
					sChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()){
					//13. 获取当前选择器上“读就绪”状态的通道
					SocketChannel sChannel = (SocketChannel) sk.channel();
					
					//14. 读取数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					
					int len = 0;
					while((len = sChannel.read(buf)) > 0 ){
						buf.flip();
						System.out.println(new String(buf.array(), 0, len));
						buf.clear();
					}
				}
				
				//15. 取消选择键 SelectionKey
				it.remove();
			}
		}
	}
}

6、管道

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

import org.junit.Test;

public class TestPipe {

	@Test
	public void test1() throws IOException{
		//1. 获取管道
		Pipe pipe = Pipe.open();
		
		//2. 将缓冲区中的数据写入管道
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		Pipe.SinkChannel sinkChannel = pipe.sink();
		buf.put("通过单向管道发送数据".getBytes());
		buf.flip();
		sinkChannel.write(buf);
		
		//3. 读取缓冲区中的数据
		Pipe.SourceChannel sourceChannel = pipe.source();
		buf.flip();
		int len = sourceChannel.read(buf);
		System.out.println(new String(buf.array(), 0, len));
		
		sourceChannel.close();
		sinkChannel.close();
	}
	
}

7、udp数据报

package cn.edu.jxnu.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

public class TestNonBlockingNIO2 {
	
	@Test
	public void send() throws IOException{
		DatagramChannel dc = DatagramChannel.open();
		
		dc.configureBlocking(false);
		
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		Scanner scan = new Scanner(System.in);
		
		while(scan.hasNext()){
			String str = scan.next();
			buf.put((new Date().toString() + ":\n" + str).getBytes());
			buf.flip();
			dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
			buf.clear();
		}
		
		dc.close();
		scan.close();
	}
	
	@Test
	public void receive() throws IOException{
		DatagramChannel dc = DatagramChannel.open();
		
		dc.configureBlocking(false);
		
		dc.bind(new InetSocketAddress(9898));
		
		Selector selector = Selector.open();
		
		dc.register(selector, SelectionKey.OP_READ);
		
		while(selector.select() > 0){
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			
			while(it.hasNext()){
				SelectionKey sk = it.next();
				
				if(sk.isReadable()){
					ByteBuffer buf = ByteBuffer.allocate(1024);
					
					dc.receive(buf);
					buf.flip();
					System.out.println(new String(buf.array(), 0, buf.limit()));
					buf.clear();
				}
			}
			
			it.remove();
		}
	}

}

8、NIO多线程实例

package cn.edu.jxnu.nio.thread;

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

/**
 * @ClassName: ClientThreead.java
 * @author Mr.Li
 * @Description: 客户端线程
 * @Date 2018-1-28 下午9:34:15
 * @version V1.0
 */
public class ClientThreead implements Runnable {

	@Override
	public void run() {
		System.out.println("已经启动Client...");
		/* 1. 获取通道 */
		SocketChannel sChannel = null;
		try {
			sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		/* 2. 切换非阻塞模式 */
		try {
			sChannel.configureBlocking(false);
		} catch (IOException e) {
			e.printStackTrace();
		}
		/* 3. 分配指定大小的缓冲区 */
		ByteBuffer buf = ByteBuffer.allocate(1024);
		/* 4. 发送数据给服务端 */
		Scanner scan = new Scanner(System.in);
		while (scan.hasNext()) {
			String str = scan.next();
			buf.put((new Date().toString() + "----->" + str).getBytes());
			buf.flip();
			try {
				sChannel.write(buf);
			} catch (IOException e) {
				e.printStackTrace();
			}
			buf.clear();
		}
		/* 5. 关闭通道 */
		try {
			sChannel.close();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			scan.close();
		}
	}

}

服务端


package cn.edu.jxnu.nio.thread;

import java.net.InetSocketAddress;
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.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName: ServerThreead.java
 * @author Mr.Li
 * @Description: 服务器线程
 * @Date 2018-1-28 下午9:35:10
 * @version V1.0
 */
public class ServerThreead implements Runnable {
	int taskSize = 5;
	// 创建一个线程池
	ExecutorService pool = Executors.newFixedThreadPool(taskSize);

	@Override
	public void run() {
		System.out.println("已经启动Server...");
		try {
			/*
			 * 1. 获取通道
			 */
			ServerSocketChannel ssChannel = ServerSocketChannel.open();
			/*
			 * 2. 切换非阻塞模式
			 */
			ssChannel.configureBlocking(false);
			/*3. 绑定连接*/
			ssChannel.bind(new InetSocketAddress(9898));
			/*
			 * 4. 获取选择器
			 */
			Selector selector = Selector.open();
			/*
			 * 5. 将通道注册到选择器上, 并且指定“监听接收事件”
			 */
			 ssChannel.register(selector, SelectionKey.OP_ACCEPT);
			/*
			 * 6. 轮询式的获取选择器上已经“准备就绪”的事件
			 */
			 while (selector.select() > 0) {
				/*
				 * 7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
				 */
				 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					/*
					 * 8. 获取准备“就绪”的是事件
					 */
					SelectionKey sk = it.next();
					/*
					 * 9. 判断具体是什么事件准备就绪
					 */
					 if (sk.isAcceptable()) {
						/*
						 * 10. 若“接收就绪”,获取客户端连接
						 */
						 SocketChannel sChannel = ssChannel.accept();
						/*
						 * 11. 切换非阻塞模式
						 *
						 */
						 sChannel.configureBlocking(false);
						/*
						 * 12. 将该通道注册到选择器上
						 */
						 sChannel.register(selector, SelectionKey.OP_READ);
					} else if (sk.isReadable()) {
						/*
						 * 13. 获取当前选择器上“读就绪”状态的通道
						 */
						SocketChannel sChannel = (SocketChannel) sk.channel();
						pool.execute(new WriteThread(sChannel));
					}

					/*
					 * 15. 取消选择键 SelectionKey
					 */
					 it.remove();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

服务器派发任务的线程

package cn.edu.jxnu.nio.thread;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @ClassName: Write.java
 * @author Mr.Li
 * @Description: 这是一个专门负责写数据的线程
 * @Date 2018-1-28 下午9:54:13
 * @version V1.0
 */
public class WriteThread implements Runnable {
	/*
	 * 接受通道
	 */
	private volatile SocketChannel socketChannel;

	public WriteThread(SocketChannel socketChannel) {
		this.socketChannel = socketChannel;
	}

	@Override
	public void run() {
		/* 创建缓冲,读取数据 */
		ByteBuffer buf = ByteBuffer.allocate(1024);
		int len = 0;
		synchronized (this) {
			try {
				while ((len = socketChannel.read(buf)) > 0) {
					buf.flip();
					System.out.println("服务器" + Thread.currentThread().getId()
							+ "做出反应:");
					System.out.println(new String(new String(buf.array(), 0,
							len).getBytes("GBK")));
					buf.clear();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
	}

}

主线程

package cn.edu.jxnu.nio.thread;

/**
 * @ClassName: MainThread.java
 * @author Mr.Li
 * @Description: 主线程:负责启动 因为双行注释在代码着色的时候会把代码也注释了所以不用。
 * @Date 2018-1-28 下午9:33:58
 * @version V1.0
 */
public class MainThread {
	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		Thread threadClient = new Thread(new ClientThreead());
		Thread threadServer = new Thread(new ServerThreead());
		// 启动服务器
		threadServer.start();
		//启动客户端
		threadClient.start();

	}

}

注:客户端只有一个线程,服务器线程池默认5个

以下来参考实战Java并发程序设计一书

服务器

package cn.edu.jxnu.nio;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
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.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 
 * @author 梦境迷离
 * @description 使用NIO实现的Echo服务器
 * @time 2018年3月28日
 */
public class MultiThreadNIOEchoServer {
	// 存放socket对应的启动时间,为了计算耗时
	public static Map<Socket, Long> geym_time_stat = new HashMap<Socket, Long>(10240);

	/**
	 * 
	 * @author 梦境迷离
	 * @description 一个实例代表一个客户端
	 * @time 2018年3月28日
	 */
	class EchoClient {
		private LinkedList<ByteBuffer> outq;

		EchoClient() {
			outq = new LinkedList<ByteBuffer>();
		}

		public LinkedList<ByteBuffer> getOutputQueue() {
			return outq;
		}

		public void enqueue(ByteBuffer bb) {
			// 左边入队
			outq.addFirst(bb);
		}
	}

	/**
	 * 
	 * @author 梦境迷离
	 * @description 单独是数据处理线程
	 * @time 2018年3月28日
	 */
	class HandleMsg implements Runnable {
		SelectionKey sk;
		ByteBuffer bb;

		public HandleMsg(SelectionKey sk, ByteBuffer bb) {
			this.sk = sk;
			this.bb = bb;
		}

		@Override
		public void run() {
			EchoClient echoClient = (EchoClient) sk.attachment();
			echoClient.enqueue(bb);
			// 需要重新注册感兴趣的消息事件,写将写事件作为感兴趣的,这样在通道准备好写入,就能通知线程
			sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
			// 强迫selector立即返回
			selector.wakeup();
		}
	}

	private Selector selector;
	private ExecutorService tp = Executors.newCachedThreadPool();

	/**
	 * 
	 * @author 梦境迷离
	 * @description 与客户端建立链接
	 * @time 2018年3月28日
	 */
	private void doAccept(SelectionKey sk) {
		ServerSocketChannel server = (ServerSocketChannel) sk.channel();
		SocketChannel clientChannel;
		try {
			clientChannel = server.accept();
			// 设置为非阻塞的
			clientChannel.configureBlocking(false);
			// 注册可读
			SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);
			// 将客户端实例作为附件,附加到表示这个连接 的SelectionKey上,这样整个连接处理都可以共享这个客户端
			EchoClient echoClient = new EchoClient();
			clientKey.attach(echoClient);
			// 获取客户端的地址
			InetAddress clientAddress = clientChannel.socket().getInetAddress();
			System.out.println("Accepted connection from " + clientAddress.getHostAddress() + ".");
		} catch (Exception e) {
			System.out.println("Failed to accept new client.");
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @author 梦境迷离
	 * @description 处理读取事件
	 * @time 2018年3月28日
	 */
	private void doRead(SelectionKey sk) {
		SocketChannel channel = (SocketChannel) sk.channel();
		// 分配缓冲区
		ByteBuffer bb = ByteBuffer.allocate(2048);
		int len;
		try {
			len = channel.read(bb);
			if (len < 0) {
				// 关闭连接
				disconnect(sk);
				return;
			}
		} catch (Exception e) {
			System.out.println("Failed to read from client.");
			e.printStackTrace();
			disconnect(sk);
			return;
		}

		// 切换读取数据的模式
		bb.flip();
		// 提交任务
		tp.execute(new HandleMsg(sk, bb));
	}

	/**
	 * 
	 * @author 梦境迷离
	 * @description 处理写事件
	 * @time 2018年3月28日
	 */
	private void doWrite(SelectionKey sk) {
		SocketChannel channel = (SocketChannel) sk.channel();
		// 获取附属品 - 客户端实例
		EchoClient echoClient = (EchoClient) sk.attachment();
		// 获取该客户端实例的缓冲队列【发送内容列表】
		LinkedList<ByteBuffer> outq = echoClient.getOutputQueue();
		// 头插,所以这里相当于FIFO
		ByteBuffer bb = outq.getLast();
		// 写入数据到客户端
		try {
			int len = channel.write(bb);
			if (len == -1) {
				disconnect(sk);
				return;
			}
			if (bb.remaining() == 0) {
				// 全部发生完成则移除这个缓存对象
				outq.removeLast();
			}
		} catch (Exception e) {
			System.out.println("Failed to write to client.");
			e.printStackTrace();
			disconnect(sk);
		}

		// 避免每次写的时候执行doWork方法而实际上又无数据可写
		if (outq.size() == 0) {
			sk.interestOps(SelectionKey.OP_READ);
		}
	}

	private void disconnect(SelectionKey sk) {
		SocketChannel channel = (SocketChannel) sk.channel();

		InetAddress clientAddress = channel.socket().getInetAddress();
		System.out.println(clientAddress.getHostAddress() + " disconnected.");

		try {
			channel.close();
		} catch (Exception e) {
			System.out.println("Failed to close client socket channel.");
			e.printStackTrace();
		}
	}

	private void startServer() throws Exception {
		selector = SelectorProvider.provider().openSelector();
		// 创建非阻塞的服务器Socket通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);

		// 绑定到指定的端口
		InetSocketAddress isa = new InetSocketAddress(8000);
		ssc.socket().bind(isa);
		// 注册到选择器,为监听,返回SelectionKey。无任何数据准备好则连接将阻塞
		ssc.register(selector, SelectionKey.OP_ACCEPT);

		// 轮询
		for (;;) {
			selector.select();
			Set<SelectionKey> readyKeys = selector.selectedKeys();
			Iterator<SelectionKey> i = readyKeys.iterator();
			long e = 0;
			while (i.hasNext()) {
				SelectionKey sk = (SelectionKey) i.next();
				// 连接操作处理
				if (sk.isAcceptable()) {
					doAccept(sk);
				}
				// 可读操作处理
				else if (sk.isValid() && sk.isReadable()) {
					if (!geym_time_stat.containsKey(((SocketChannel) sk.channel()).socket()))
						geym_time_stat.put(((SocketChannel) sk.channel()).socket(), System.currentTimeMillis());
					doRead(sk);
				}
				// 可写操作处理
				else if (sk.isValid() && sk.isWritable()) {
					doWrite(sk);
					e = System.currentTimeMillis();
					long b = geym_time_stat.remove(((SocketChannel) sk.channel()).socket());
					System.out.println("spend:" + (e - b) + "ms");
				}
				// 一定要取出这个SelectionKey,否则会重复处理相同的SelectionKey
				i.remove();
			}
		}
	}

	// 启动服务器线程
	public static void main(String[] args) {
		MultiThreadNIOEchoServer echoServer = new MultiThreadNIOEchoServer();
		try {
			echoServer.startServer();
		} catch (Exception e) {
			System.out.println("Exception caught, program exiting...");
			e.printStackTrace();
		}
	}
}

客户端

package cn.edu.jxnu.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.nio.channels.spi.SelectorProvider;
import java.util.Iterator;

/**
 * @author 梦境迷离
 * @description NIO客户端
 * @time 2018年3月28日
 */
public class MultiThreadNIOEchoClient {
	private Selector selector;

	/**
	 * 
	 * @author 梦境迷离
	 * @description 客户端初始化操作
	 * @time 2018年3月28日
	 */
	public void init(String ip, int port) throws IOException {
		SocketChannel channel = SocketChannel.open();
		channel.configureBlocking(false);// 设置非阻塞模式
		this.selector = SelectorProvider.provider().openSelector();// 取得选择器
		channel.connect(new InetSocketAddress(ip, port));// 绑定端口
		// 注册事件与通道到选择器中,选择器可以管理该通道,channel数据准备好了,选择器就好收到通知
		channel.register(selector, SelectionKey.OP_CONNECT);
	}

	/**
	 * 
	 * @author 梦境迷离
	 * @description 客户端业务操作
	 * @time 2018年3月28日
	 */
	public void work() throws IOException {
		while (true) {
			if (!selector.isOpen()) {
				break;
			}
			selector.select();// 一个准备好的数据都没有则将会阻塞
			Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
			while (it.hasNext()) {
				SelectionKey key = it.next();
				if (key.isValid() && key.isConnectable()) {
					// 连接
					connect(key);
				} else if (key.isValid() && key.isReadable()) {
					// 读取
					read(key);
				} // 用完必须去除
				it.remove();
			}

		}
	}

	/**
	 * 
	 * @author 梦境迷离
	 * @throws IOException
	 * @description 读取
	 * @time 2018年3月28日
	 */
	private void read(SelectionKey key) throws IOException {
		SocketChannel channel = (SocketChannel) key.channel();
		// 创建读取缓冲区
		ByteBuffer byteBuffer = ByteBuffer.allocate(100);
		channel.read(byteBuffer);
		byte[] data = byteBuffer.array();
		String msg = new String(data).trim();
		System.out.println("客户端收到消息:" + msg);
		channel.close();
		key.selector().close();
	}

	/**
	 * 
	 * @author 梦境迷离
	 * @description 连接
	 * @time 2018年3月28日
	 */
	private void connect(SelectionKey key) throws IOException {
		SocketChannel channel = (SocketChannel) key.channel();
		// 如果正在连接,则完成连接
		if (channel.isConnectionPending()) {
			channel.finishConnect();
		}
		channel.configureBlocking(false);
		channel.write(ByteBuffer.wrap("Hello Server !\r\n".getBytes()));
		// 注册读事件为感兴趣的事件
		channel.register(this.selector, SelectionKey.OP_READ);
	}

	public static void main(String[] args) throws IOException {
		MultiThreadNIOEchoClient cEchoClient = new MultiThreadNIOEchoClient();
		cEchoClient.init("127.0.0.1", 8000);
		cEchoClient.work();

	}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值