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));
}
}
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();
}
}
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();
}
}
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 map = Charset.availableCharsets();
Set> set = map.entrySet();
for (Entry entry : set) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
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();
}
}
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 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();
}
}
}
}
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();
}
}
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 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();
}
}
}
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 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 geym_time_stat = new HashMap(10240);
/**
*
* @author 梦境迷离
* @description 一个实例代表一个客户端
* @time 2018年3月28日
*/
class EchoClient {
private LinkedList outq;
EchoClient() {
outq = new LinkedList();
}
public LinkedList 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 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 readyKeys = selector.selectedKeys();
Iterator 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 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();
}
}