java NIO的初步实现&碰到的一些问题

看了挺多关于NIO的东西,网上复制的代码也跑了几个,

但是多多少少都存在各种问题。

例子先写出来,具体的细节还有待分析

下面直接贴我改过的代码,源码网址找不到了。

服务端:

package com.nio.service;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
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.Set;

public class Server {
	
	private Selector selector;
	private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化
	private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化

	/**
	 * 启动服务
	 * 
	 * @throws IOException
	 */
	public void start() throws IOException {
		// 打开服务器套接字通道 
		ServerSocketChannel ssc = ServerSocketChannel.open(); 
		// 服务器配置为非阻塞 
		ssc.configureBlocking(false); 
		// 进行服务的绑定 
		ssc.bind(new InetSocketAddress("localhost", 8000)); 

		// 通过open()方法找到Selector
		selector = Selector.open(); 
		// 注册到selector,等待连接
		ssc.register(selector, SelectionKey.OP_ACCEPT);

		while (!Thread.currentThread().isInterrupted()) { 
			selector.select(); 
			Set<SelectionKey> keys = selector.selectedKeys(); 
			Iterator<SelectionKey> keyIterator = keys.iterator(); 
			while (keyIterator.hasNext()) { 
				SelectionKey key = keyIterator.next(); 
				if (!key.isValid()) { 
					continue; 
				} 
				if (key.isAcceptable()) { 
					accept(key); 
				} else if (key.isReadable()) { 
					read(key); 
				} else if (key.isWritable()) {
					write(key);
				}
				keyIterator.remove(); //该事件已经处理,可以丢弃
			} 
		}
	}

	/**
	 * 返回数据到客户端
	 * 
	 * @param key
	 * @throws IOException
	 * @throws ClosedChannelException
	 */
	private void write(SelectionKey key) throws IOException, ClosedChannelException {
		SocketChannel channel = (SocketChannel) key.channel();
		// 清除发送缓存
		sendBuffer.clear();
		sendBuffer.put("服务端已成功获取信息".getBytes());
		sendBuffer.flip();
		channel.write(sendBuffer); // 从缓存中读取数据,并发送消息到客户端
		channel.register(selector, SelectionKey.OP_READ);
	} 

	/**
	 * 读取客户端发送过来的数据
	 * 
	 * @param key
	 * @throws IOException
	 */
	private void read(SelectionKey key) throws IOException { 
		SocketChannel socketChannel = (SocketChannel) key.channel();
		// 清除缓冲区内容
		this.readBuffer.clear();
//		this.readBuffer.flip();
		// 将客户端的内容读取到缓存区中,并记录缓存区的长度
		int numRead = socketChannel.read(this.readBuffer);
		// 将缓存转成String
		String str = new String(readBuffer.array(), 0, numRead);

		System.out.println("客户端请求的数据:" + str);
		socketChannel.register(selector, SelectionKey.OP_WRITE);
	}

	/**
	 * 接受链接
	 * 
	 * @param key
	 * @throws IOException
	 */
	private void accept(SelectionKey key) throws IOException { 
		ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 
		SocketChannel clientChannel = ssc.accept(); 
		clientChannel.configureBlocking(false); 
		clientChannel.register(selector, SelectionKey.OP_READ); 
		System.out.println("a new client connected "+clientChannel.getRemoteAddress()); 
	}

	public static void main(String[] args) throws IOException {
		System.out.println("服务开启..."); 
		new Server().start(); 
	}
}

客户端:

package com.nio.client;

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

public class Client {
	ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
	ByteBuffer readBuffer = ByteBuffer.allocate(1024);

	@SuppressWarnings("resource")
	public void start() throws IOException { 
		// 打开socket通道  
		SocketChannel sc = SocketChannel.open(); 
		//设置为非阻塞
		sc.configureBlocking(false); 
		//连接服务器地址和端口
		sc.connect(new InetSocketAddress("localhost", 8000)); 
		//打开选择器
		Selector selector = Selector.open(); 
		//注册连接服务器socket的动作
		sc.register(selector, SelectionKey.OP_CONNECT); 
		
		Scanner scanner = new Scanner(System.in); 
		while (true) { 
			//选择一组键,其相应的通道已为 I/O 操作准备就绪。  
			//此方法执行处于阻塞模式的选择操作。
			selector.select();
			//返回此选择器的已选择键集。
			Set<SelectionKey> keys = selector.selectedKeys(); 
			System.out.println("keys=" + keys.size()); 
			Iterator<SelectionKey> keyIterator = keys.iterator(); 
			while (keyIterator.hasNext()) { 
				SelectionKey key = keyIterator.next(); 
				keyIterator.remove(); 
				// 判断此通道上是否正在进行连接操作。 
				if (key.isConnectable()) { 
					sc.finishConnect(); 
					sc.register(selector, SelectionKey.OP_WRITE); 
					System.out.println("server connected..."); 
					break; 
				} else if (key.isWritable()) { //写数据 
					System.out.print("请输入发送到服务端的数据:"); 
					String message = scanner.nextLine();
					// 清除缓存数据
					writeBuffer.clear();
					writeBuffer.put(message.getBytes());
					writeBuffer.flip();
					sc.write(writeBuffer); 
					
					sc.register(selector, SelectionKey.OP_READ);
					
				} else if (key.isReadable()){//读取数据
					System.out.print("服务端返回的数据:");
					SocketChannel client = (SocketChannel) key.channel();
					//将缓冲区清空以备下次读取 
					readBuffer.clear();
					int num = client.read(readBuffer);
					System.out.println(new String(readBuffer.array(),0, num));
					//注册读操作,下一次读取
					sc.register(selector, SelectionKey.OP_WRITE);
				}
			} 
		} 
	} 

	public static void main(String[] args) throws IOException { 
		new Client().start(); 
	}
}

-------------------------------------------------------------------------------------------------------------------

之前拷贝过来的时候代码存在以下问题:

1,服务端的read(SelectionKey key)方法原先是这样的:

/**
 * 读取客户端发送过来的数据
 * 
 * @param key
 * @throws IOException
 */
private void read(SelectionKey key) throws IOException { 
	SocketChannel socketChannel = (SocketChannel) key.channel();
	this.readBuffer.clear();
	this.readBuffer.flip();
	// 将客户端的内容读取到缓存区中,并记录缓存区的长度
	int numRead = socketChannel.read(this.readBuffer);
	// 将缓存转成String
	String str = new String(readBuffer.array(), 0, numRead);
	System.out.println("客户端请求的数据:" + str);
	socketChannel.register(selector, SelectionKey.OP_WRITE);
}
区别是我把下面这行注释了
this.readBuffer.flip();

flip()方法说明https://www.cnblogs.com/woshijpf/articles/3723364.html

buffer中的flip方法涉及到bufer中的Capacity,Position和Limit三个概念。
其中Capacity在读写模式下都是固定的,就是我们分配的缓冲大小,Position类似于读写指针,
表示当前读(写)到什么位置,Limit在写模式下表示最多能写入多少数据,
此时和Capacity相同,在读模式下表示最多能读多少数据,此时和缓存中的实际数据大小相同。
在写模式下调用flip方法,那么limit就设置为了position当前的值(即当前写了多少数据),postion会被置为0,以表示读操作从缓存的头开始读。

也就是说调用flip之后,读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)。

flip源码:

public final Buffer flip() {
	limit = position;
	position = 0;
	mark = -1;
	return this;
}
所以,当执行flip()后,readBuffer的limit重置成0导致无法写入数据(position初始值为0)。


猜你喜欢

转载自blog.csdn.net/h996666/article/details/80499071