NIO
由于Java原生的socket只支持阻塞方式处理IO
所以Java后来推出了新版IO 也叫New IO = NIO
NIO提出了socketChannel,serversocketchannel,bytebuffer,selector和selectedkey等概念。
1 socketchannel其实就是socket的替代品,他的好处是多个socket可以复用同一个bytebuffer,因为socket是从channel里打开的,所以多个socket都可以访问channel绑定着的buffer。
2 serversocketchannel顾名思义,是用在服务端的channel。
3 bytebuffer以前对用户是透明的,用户直接操作io流即可,所以之前的socket io操作都是阻塞的,引入bytebuffer以后,用户可以更灵活地进行io操作。
buffer可以分为不同数据类型的buffer,但是常用的还是bytebuffer。写入数据时按顺序写入,写入完使用flip方法反转缓冲区,让接收端反向读取。这个操作比较麻烦,后来的netty对缓冲区进行了重新封装,封装了这个经常容易出错的方法。
4 selector其实就是对io多路复用器的封装,一般基于linux的epoll来实现。
socket把感兴趣的事件和描述符注册到selector上,然后通过遍历selectedKey来获取感兴趣的请求,进行IO操作。
selectedkey应该就是epoll中就绪链表的实现了。
5 所以一般的流程是:
新建一个serversocket,启动一个线程进行while循环,当有请求接入时,使用accept方法阻塞获取socket,然后将socket和感兴趣的事件注册到selector上。再开启一个线程轮询selectoredKey,当请求就绪时开启一个线程去处理即可。
package 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.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class NioServer {
private volatile static LinkedList<String> message_list = new LinkedList<String>();//发送消息队列
private volatile static List<SocketChannel> socketChannels_list=new ArrayList<SocketChannel>();
public static void main(String[] args) throws IOException {
int port = 8001;
Selector selector = null;
ServerSocketChannel servChannel = null;
try {
selector = Selector.open();
servChannel = ServerSocketChannel.open();
servChannel.configureBlocking(false);
servChannel.socket().bind(new InetSocketAddress(port), 1024);
servChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器在8001端口守候");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
try
{
Thread.sleep(3000);
}
catch(Exception ex)
{
ex.printStackTrace();
}
while(true)
{
try
{
Thread.sleep(5100);
}
catch(Exception ex)
{
ex.printStackTrace();
}
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(selector,key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
for(String string : message_list) {
for(SocketChannel socketChannel : socketChannels_list) {
doWrite(socketChannel, string);
}
}
message_list.clear();
socketChannels_list.clear();
}
}
public static void handleInput(Selector selector, SelectionKey key) throws IOException {
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) {
// Accept the new connection
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// Add the new connection to the selector
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);//这里就是NIO费阻塞的关键点,read并不会一直等待而是返会一个int值;
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String request = new String(bytes, "UTF-8"); //接收到的输入
System.out.println(request);
String response = request;
socketChannels_list.add(sc);
message_list.add(response);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
}
public static void doWrite(SocketChannel channel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
package 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.Iterator;
import java.util.Set;
import java.util.UUID;
public class NioClient {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8001;
Selector selector = null;
SocketChannel socketChannel = null;
try
{
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // 非阻塞
// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress(host, port)))
{
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
}
else
{
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (true)
{
try
{
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext())
{
key = it.next();
it.remove();
try
{
//处理每一个channel
handleInput(selector, key);
}
catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
// 多路复用器关闭后,所有注册在上面的Channel资源都会被自动去注册并关闭
// if (selector != null)
// try {
// selector.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//
// }
}
public static void doWrite(SocketChannel sc) throws IOException {
byte[] str = ("Client:"+sc.getLocalAddress()+" said "+UUID.randomUUID().toString()+"\r\n").getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);
writeBuffer.put(str);
writeBuffer.flip();
sc.write(writeBuffer);
}
public static void handleInput(Selector selector, SelectionKey key) throws Exception {
if (key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
}
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.print(body);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
Thread.sleep(5000);
doWrite(sc);
}
}
}