【箴3:3】不可使慈爱、诚实离开你,要系在你颈项上,刻在你心版上。
-
应用
在 nio 中数据基本都是通过 Channel 操作 Buffer 实现的,不会直接将数据写入到 Channel。
Channel ---read---> Buffer 从通道读到缓冲区
Channel <--write--- Buffer 向通道写入数据
-
Channel 接口
package java.nio.channels;
import java.io.IOException;
import java.io.Closeable;
public interface Channel extends Closeable {
/**
* 判断通道是否打开
*/
public boolean isOpen();
/**
* 关闭通道
*/
public void close() throws IOException;
}
-
Channel 实现
- ServerSocketChannel
服务端用于监听新进来的TCP连接的通道,每一个监听到的链接都会以 SocketChannel 对象返回
- SocketChannel
客户端发起TCP连接的网络套接字通道
- DatagramChannel
可用于UDP 连接发送数据包的通道
- FileChannel
进行文件数据处理的通道
-
ServerSocketChannel
/**
* 用于面向流的侦听套接字的可选通道。
* 可供多个并发线程安全使用。
*/
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel{
protected ServerSocketChannel(SelectorProvider provider) {
super(provider);
}
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
/**
* 返回标识此通道支持的操作的操作集
*/
public final int validOps() {
return SelectionKey.OP_ACCEPT;
}
// -- ServerSocket-specific operations --
/**
* 将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
*/
public final ServerSocketChannel bind(SocketAddress local) throws IOException{
return bind(local, 0);
}
/**
* 将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
* 用于在套接字和本地地址建立链接。一旦建立了关联,那么套接字将保持绑定状态,直到通道关闭。
* @param backlog 套接字上挂起连接的最大数目
*/
public abstract ServerSocketChannel bind(SocketAddress local, int backlog)
throws IOException;
/**
* 设置操作项
*/
public abstract <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
throws IOException;
/**
* 检索与此通道关联的服务器套接字。
*/
public abstract ServerSocket socket();
/**
* 接受与此频道的套接字建立的连接。
* [1]如果此通道处于非阻塞模式,则如果没有挂起的连接,则此方法将立即返回<tt>null<tt>
* [2]否则,它将无限期阻塞,直到新连接可用或发生I/O错误。
* 无论此通道的阻塞模式如何,此方法返回的 SocketChannel(如果有)都将处于阻塞模式。
*/
public abstract SocketChannel accept() throws IOException;
@Override
public abstract SocketAddress getLocalAddress() throws IOException;
}
- 阻塞模式 demo
public class ServerSocketChannelDemo {
public static void main(String[] args) throws Exception{
// 打开(创建)ServerSocketChannel
ServerSocketChannel server = ServerSocketChannel.open();
// 绑定监听地址:ip 默认是本机
server.bind(new InetSocketAddress(8080));
while (true) {
// 开始监听,默认是阻塞模式
SocketChannel accept = server.accept();
// do something .......
}
}
}
- 非阻塞模式 demo
public class ServerSocketChannelDemo {
public static void main(String[] args) throws Exception{
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
// 设置为非阻塞模式
server.configureBlocking(false);
while (true) {
// 开始监听,非阻塞模式下会不管存不存在 socket,都是直接返回,可能会为 null
SocketChannel accept = server.accept();
// 所以在这里要做非空判断,才继续业务逻辑
if (null != accept) {
// do something .......
}
}
}
}
-
SocketChannel
/**
* 用于面向流的连接套接字的可选通道。
*【1】可通过 #open 方法创建。新创建的套接字通道已打开,但尚未连接。
* 尝试对未连接的通道调用I/O操作将导致抛出@link notyetconnectedException。
* 通过调用 #connect 方法可以连接套接字通道;一旦连接,套接字通道将保持连接状态,直到关闭为止。
* 可以通过调用其 #isconnected方法来确定套接字通道是否已连接。
*
* 【2】套接字通道支持非阻塞连接:
* 可以创建套接字通道,并通过 #connect 启动建立到远程套接字的链接的过程,以便稍后通过#finishConnect 完成。
* 连接操作是否正在进行可以通过调用 #isconnectionPending 来确定。
*
* 【3】套接字通道支持异步关闭,这类似于@link channel类中指定的异步关闭操作。
* 如果一个线程关闭了一个套接字的输入端,而另一个线程在套接字通道上的读取操作中被阻塞,则被阻塞线程中的读取操作将在不读取任何字节的情况下完成,并将返回 -1
* 如果套接字的输出端被一个线程关闭,而另一个线程在套接字的写入操作中被阻塞,被阻塞的线程将收到一个@link asynchronicuscloseexception。
*
*
*
* 套接字通道可供多个并发线程安全使用。
* 它们支持并发读写,尽管在任何给定的时间内,至多有一个线程在读,至多有一个线程在写。
* #connect 和 #finishConnect 相互同步,当调用其中一个方法时,试图启动读或写操作将被阻止,直到调用完成。
*
*/
public abstract class SocketChannel extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel {
protected SocketChannel(SelectorProvider provider) {
super(provider);
}
/**
* Opens a socket channel.
*/
public static SocketChannel open() throws IOException {
return SelectorProvider.provider().openSocketChannel();
}
/**
* Opens a socket channel and connects it to a remote address.
* @param remote
* The remote address to which the new channel is to be connected
*/
public static SocketChannel open(SocketAddress remote) throws IOException {
SocketChannel sc = open();
try {
sc.connect(remote);
} catch (Throwable x) {
try {
sc.close();
} catch (Throwable suppressed) {
x.addSuppressed(suppressed);
}
throw x;
}
assert sc.isConnected();
return sc;
}
/**
* Returns an operation set identifying this channel's supported operations.
* <p> Socket channels support connecting, reading, and writing
*/
public final int validOps() {
return (SelectionKey.OP_READ
| SelectionKey.OP_WRITE
| SelectionKey.OP_CONNECT);
}
// -- Socket-specific operations --
@Override
public abstract SocketChannel bind(SocketAddress local)
throws IOException;
@Override
public abstract <T> SocketChannel setOption(SocketOption<T> name, T value)
throws IOException;
/**
* 关闭读取连接,而不关闭通道。一旦关闭读取,通道上的进一步读取将返回@code-1,即流结束指示。
*/
public abstract SocketChannel shutdownInput() throws IOException;
/**
* 关闭写入连接而不关闭通道。
*/
public abstract SocketChannel shutdownOutput() throws IOException;
/**
* 检索与此通道关联的套接字。
*/
public abstract Socket socket();
/**
* 判断此通道的网络套接字是否已连接。
*/
public abstract boolean isConnected();
/**
* 判断此通道上是否正在进行连接操作。
*/
public abstract boolean isConnectionPending();
/**
* 连接此通道的套接字
* 如果此通道处于非阻塞模式,则此方法的调用将启动非阻塞连接操作。如果立即建立连接(本地连接可能会发生这种情况),则此方法返回 true。否则,此方法返回 false,稍后必须通过调用 #finishConnect完成连接操作。
* 如果此通道处于阻塞模式,则此方法的调用将阻塞,直到建立连接或发生I/O错误。
*/
public abstract boolean connect(SocketAddress remote) throws IOException;
public abstract boolean finishConnect() throws IOException;
/**
* Returns the remote address to which this channel's socket is connected.
*/
public abstract SocketAddress getRemoteAddress() throws IOException;
// -- ByteChannel operations --
public abstract long read(ByteBuffer[] dsts, int offset, int length)
throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
public abstract int write(ByteBuffer src) throws IOException;
public abstract long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
@Override
public abstract SocketAddress getLocalAddress() throws IOException;
}
- SocketChannel 读写
打开 -> 连接到服务器
// 打开 SocketChannel socketChannel = SocketChannel.open(); // 连接到服务器 socketChannel.connect(new InetSocketAddress(“192.168.3.172”,8080));
#read -> 从该通道读取数据
// socketChannel 只能通过 Buffer 操作数据 ByteBuffer buffer = ByteBuffer.allocate(1024); // 把数据从 socketChannel 读到 buffer,返回值是读取的字节数 int read = socketChannel.read(buffer);
#write -> 向该通道写入数据
// 准备缓冲区数据 ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.clear(); buffer.put("hello nio socket channel".getBytes()); buffer.flip();//切换模式 // 写入数据到 socketChannel while (buffer.hasRemaining()) {// 放在 while 里面,知道把bufffer 中的数据写完 socketChannel.write(buffer); }
- 非阻塞 SocketChannel 读写
#connect
非阻塞模式下的 connect 方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。
// 打开 SocketChannel socketChannel = SocketChannel.open(); // 设置非阻塞模式 socketChannel.configureBlocking(false); // 连接到服务器 socketChannel.connect(new InetSocketAddress(8080)); // 要判断是否完成连接 if (socketChannel.finishConnect()) { }
#read 和 #write
同样的非阻塞模式的下的 #read 和 #write 都有可能在数据尚未处理完成时就返回了。所以:
#read 读出数据时可判断返回值是否为 -1 ,确定数据流是否已经读完。
#write 同上放在 while() 里面处理。
SocketChannel & Selector
非阻塞模式通常配合 Selector 使用,将多个 SocketChannel 注册到 Selector,然后Selector 轮询处理准备好数据的通道。
-
DatagramChannel
一个用于接收发送 UDP 数据包的通道。UDP是无连接状态的协议,所以只能以数据包的形式用于数据的网络传输。
#bind
// 打开 DatagramChannel channel = DatagramChannel.open(); // 接口UDP下8080端口的数据包 channel.socket().bind(new InetSocketAddress(8080));
#receive
通过此通道接收数据报。
如果此通道是阻塞模式,那么数据报只有在可用时,才会复制到给定的字节缓冲区,并返回其源地址。
如果此通道处于非阻塞模式,而数据报不是立即可用,然后此方法立即返回空。ByteBuffer buf = ByteBuffer.allocate(1024); buf.clear(); channel.receive(buf);
#send
通过此通道发送数据包。
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(1024); buf.clear(); buf.put(“hello nio DatagramSocketChannel”.getBytes()); buf.flip(); // 向指定服务器指定的UDP端口,发送数据包 int bytesSent = channel.send(buf, new InetSocketAddress("192.168.7.176", 8080));
-
FileChannel
操作文件的读写通道。
RandomAccessFile file = new RandomAccessFile("/Users/***/test.txt","rw"); // 获取文件通道 FileChannel channel = file.getChannel();
#read
ByteBuffer buffer = ByteBuffer.allocate(10);
// 从文件通道读取数据到缓冲区
channel.read(buffer);
#write
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.clear();
buffer.put("hello file channel".getBytes());
// buffer切换模式
buffer.flip();
// 向 fileChannel 写数据
while (buffer.hasRemaining()) {
channel.write(buffer);
}
左手圣经,右手Java
灵魂指导思想,思想更新技术
一个对圣经感兴趣的Java菜鸟 ? -> 留言指导哦?亲