Channel
IO操作纽带,一个Channel代表与实体开放开放连接。在网络IO中一般就是和Buffer读/写数据用。
JAVA NIO中的一些主要Channel的实现
- FileChannel:从文件中读写数据。
- DatagramChannel:能通过UDP读写网络中的数据。
- SocketChannel:能通过TCP读写网络中的数据。
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
常用方法
int read(ByteBuffer var1)从Buffer中读取字节int write(ByteBuffer src)向Buffer中写数据boolean connect(SocketAddress remote)客户端连接某个地址SocketChannel accept()接受连接并创建SocketChannel
Buffer
Buffer本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
Java NIO里关键的Buffer实现
这些实现基本上覆盖了所有IO传送的基本数据类型
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
常用方法
- ByteBuffer allocate(int capacity):创建一个指定大小的buffer
- ByteBuffer put(byte b):将指定字节写入buffer ,channel的read是将channel的数据写入到buffer
- byte get():从buffer中读取数据,channel的write是将buffer中的数据写入到channel中
- Buffer flip() 读写模式切换
Selector
Selector (多路复用器)。它是 Java NIO 核心组件中的一个,同时对多个Channel监听。当某个channel发生特定事件的时候,这些通道就会变成可用状态。
常用方法
- public static Selector open(): 创建selector
- final SelectionKey register(Selector sel, int ops) 这个方法是channel的,selector作为参数传入,第二个参数在,为事件。当channel触发一个事件的时候,就变为就绪状态。这四个事件分别是
- SelectionKey.OP_READ::读事件,适用于两端
- SelectionKey.OP_WRITE::写时间,适用于两端
- SelectionKey.OP_CONNECT::连接完成事件( TCP 连接 ),仅适用于客户端
- SelectionKey.OP_ACCEPT::接受新连接事件,仅适用于服务端
- Set selectedKeys():返回对应的SelectionKeys
- int select():阻塞到一个channel就绪了,返回多少channel就绪了
- int select(long timeout):增加超时机制
- public abstract int selectNow():立即返回数量,而不阻塞。
SelectionKey
SelectionKey表示一个channel向一个selector的注册的令牌
常用方法
- SelectionKey interestOps():返回感兴趣的事件集合
- Object attach(Object ob):往SelectionKey中添加附件
- Object attachment():在SelectionKey中取出附件
SocketChannel
SocketChannel是连接到TCP网络套接字的通道。
创建方式为SocketChannel.open或者ServerSocketChannel.accept()上面有介绍,重复介绍下
ServerSocketChannel
ServerSocketChannel 是监听新进来的TCP连接的通道
传统的IO编程【简化版本】
上面这些基础概念知道后,下面就开始网络编程部分了,先来个简单的例子。
服务端
public class NIOServer {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel,处理接入连接
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//设置是否为非阻塞
serverSocketChannel.configureBlocking(false);
//绑定端口号
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
while (true){
if(serverSocketChannel.isOpen()){
SocketChannel socketChannel=serverSocketChannel.accept();
if(socketChannel!=null){
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = socketChannel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
System.arraycopy(buffer.array(), buffer.position(), bytes, 0, buffer.remaining());
System.out.println(new String(bytes, "UTF-8"));
//通道关闭
socketChannel.close();
}
}
}
}
}
客户端
public static void main(String[] args) throws IOException, InterruptedException {
for (int i = 0; i < 30; i++) {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(8080));
if(socketChannel.isConnected()){
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put("hello world".getBytes());
System.out.println("hello world");
buffer.flip();
while(buffer.hasRemaining()) {
socketChannel.write(buffer);
}
socketChannel.close();
}
}
}
就是简单的发送数据
那么问题也来了,这是个简单的网络编程,这个还是有很多问题的,例如,服务端是只能处理新连接,并处理第一次接受到的信息,对于后续发送的数据是无法处理的。问题还是很多的。下面我门使用Selector,对这个例子优化下
传统的IO编程
服务端
public class NIOServer2 {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel,处理接入连接
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//创建Selector
Selector selector=Selector.open();
//设置是否为非阻塞
serverSocketChannel.configureBlocking(false);
//创建注册channel进selector的创立连接时间
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//绑定端口号
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
while (true){
if(serverSocketChannel.isOpen()){
// 通过 Selector 选择 Channel
int selectNums = selector.select(1000L);
if (selectNums == 0) {
continue;
}
// 遍历可选择的 Channel 的 SelectionKey 集合
for (SelectionKey selectKey:selector.selectedKeys()) {
// 忽略无效的 SelectionKey
if (!selectKey.isValid()) {
continue;
}
//新建立的连接
if(selectKey.isAcceptable()){
//获取新连接创建的channel
SocketChannel socketChannel= ((ServerSocketChannel) selectKey.channel()).accept();
if(socketChannel!=null){
//设置为非阻塞
socketChannel.configureBlocking(false);
//注册进selector
socketChannel.register(selector,SelectionKey.OP_READ);
}
}
//处理读时间
if(selectKey.isReadable()){
SocketChannel socketChannel= (SocketChannel) selectKey.channel();
if(socketChannel!=null){
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = socketChannel.read(buffer);
if(bytesRead==-1){
socketChannel.register(selector,0);
socketChannel.close();
}else{
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
System.arraycopy(buffer.array(), buffer.position(), bytes, 0, buffer.remaining());
System.out.println(new String(bytes, "UTF-8"));
}
}
}
}
}
}
}
}
客户端
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(8080));
for (int i = 0; i < 30; i++) {
if(socketChannel.isConnected()){
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put("hello world".getBytes());
System.out.println("hello world");
buffer.flip();
while(buffer.hasRemaining()) {
socketChannel.write(buffer);
}
}
}
socketChannel.close();
}
现在可以在针对不同的事件进行不同的处理