背景介绍:
传统模式下的IO主要有两个缺点:
1、一个Socket连接,需要一个单独的线程来实现,而线程的增多会加剧服务器的开销(我们都知道一个线程大约占4MB左右的内存)。就算使用线程池也无法高效率解决,比如线程池的队列是有限制的。
2、而传统的网络IO(IO分三种:网路IO、磁盘IO、文件IO)是阻塞的,比如accept()、listen()、read、write()方法都是阻塞的。知道有连接接入、直到有可供读取的数据…
NIO的核心是基于Reactor模式,IO调用不会被阻塞,而是注册感兴趣的IO事件。如可读数据到达,新的套接字连接等等。注册的新的事件发生时,系统再进行通知等等。其核心就是selector。
通过一个selector可实时监听多个通道,当通道中有我们注册的事情发生时再进行执行。非阻塞IO指的是IO事件本身是不阻塞的,但是获取IO的方法select()是阻塞的。NIO是面向缓冲区的。
本篇简单介绍Socket通道的几个API及使用。
1 ServerSocketChannel 通道的使用
1.1 模拟socket服务端,管理多个socket请求
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ServerSocketChannelDemo {
public static void main(String[] args) throws IOException, InterruptedException {
int port = 7777;
ByteBuffer buffer = ByteBuffer.wrap("hello every one!".getBytes());
ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定
ssc.socket().bind(new InetSocketAddress(port));
// 设定非阻塞模式
ssc.configureBlocking(false);
// while循环模拟一直监听7777端口
while (true){
System.out.println("Waiting for connections!");
// ServerSocketChannel的accept方法会返回SocketChannel对象,这个对象可以在非阻塞模式下运行
// 如果ServerSocketChannel在非阻塞模式下运行,当没有连接接入的时候,accept方法会返回一个空对象
SocketChannel accept = ssc.accept(); // 如果是阻塞模式,accept方法会一直阻塞
if (accept==null){
System.out.println("没有连接接入");
Thread.sleep(2000);
}else {
System.out.println("有连接接入,地址:"+ (accept.getRemoteAddress()));
// 往buffer中读数据
buffer.rewind(); // 指针归0
accept.write(buffer);
accept.close();
}
}
}
}
1.2 运行main方法,启动服务端
1.3 通过网页发起客户端请求
1.4 再次查看控制台,请求已到达
2、DatagramChannel 通道的使用
DatagramChannel:数据包。
1、SocketChannel 模拟连接导向的流协议,如TCP/IP。
2、DatagramChannel模拟包导向的无连接协议,如UDP/IP。
DatagramChannel是无连接的,每一个数据包(datagram)都是一个自包含的实体,
拥有它自己的数据地址,不依赖于其他数据包的数据负载。
与面向流的socket不同,DatagramChannel可以发送不同的数据包给不同的目的地址。
同样也可以接收来自任意地址的数据包,每个到达的数据包都包含关于它来自何处的地址。
UDP不存在真正意义上的连接,这里的连接指的是向特定的服务地址 用read或write 接收发送数据包。
2.1 模拟基于DatagramChannel 发送和接收数据。
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.StandardCharsets;
public class DatagramChannelDemo {
/**
* 发送端
*
* @throws IOException
* @throws InterruptedException
*/
@Test
public void testSendDatagram() throws IOException, InterruptedException {
DatagramChannel datagramChannel = DatagramChannel.open();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 7777);
// 模拟循环发送
while (true) {
ByteBuffer buffer = ByteBuffer.wrap("hello girl!".getBytes(StandardCharsets.UTF_8));
datagramChannel.send(buffer, address);
System.out.println("发包完毕");
Thread.sleep(2000);
}
}
/**
* 接收端
*
* @throws IOException
*/
@Test
public void testReceiveDatagram() throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
InetSocketAddress address = new InetSocketAddress(7777);
// 地址绑定,监听发送端端口
datagramChannel.bind(address);
// 接收
ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
while (true) {
receiveBuffer.clear();
// receive方法将内容接收到缓冲区
SocketAddress socketAddress = datagramChannel.receive(receiveBuffer);
receiveBuffer.flip();
System.out.println(socketAddress.toString());
System.out.println(StandardCharsets.UTF_8.decode(receiveBuffer));
}
}
}
2.2 依次启动发送端、监听端代码,运行结果如下: