NIO核心三大组件Channel、Buffer和Selector,传送门:
Java NIO核心三大组件Channel、Buffer和Selector(一)
Java NIO核心三大组件Channel、Buffer和Selector(二)
NIO主要有:
- FileChannel 连接到文件的通道,它无法设置为非阻塞模式,它总是运行在阻塞模式下。
- ServerSocketChannel 监听新进来的TCP连接的通道,对应前面的服务器端ServerSocket
- SocketChannel 连接到TCP网络套接字的通道,对应前面的客户端Socket
- DatagramChannel 连接到UDP包的通道
FileChannel在NIO上面认识过了,这里主要用ServerSocketChannel 和SocketChannel 来进行网络编程。
1、ServerSocketChannel 对象
NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 类似于ServerSocket,它们的用法比较类似。
-
-
abstract SocketChannel
accept()
接受这个通道的套接字的连接。
ServerSocketChannel
bind(SocketAddress local)
结合通道的插座到本地地址和配置套接字监听连接。
abstract ServerSocketChannel
bind(SocketAddress local, int backlog)
结合通道的插座到本地地址和配置套接字监听连接。
abstract SocketAddress
getLocalAddress()
返回此通道的套接字绑定到的套接字地址。
static ServerSocketChannel
open()
打开服务器套接字通道。
abstract ServerSocketChannel
setOption(SocketOption name, T value)
设置套接字选项的值。
abstract ServerSocket
socket()
检索与此通道关联的服务器套接字。
-
accept() 方法监听新进来的连接,返回一个新进来的连接的 SocketChannel。
1. 阻塞模式
通常在while循环中调用 accept()方法,一直监听,因此, accept()方法会一直阻塞到有新连接到达。如:
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
//do something
}
2. 非阻塞模式
ServerSocketChannel可以设置成非阻塞模式。
在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,立即返回null。 因此,需要检查返回的SocketChannel是否是null。如:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 9999));
serverSocketChannel.configureBlocking(false);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
//do something
}
}
2、SocketChannel 对象
NIO中的SocketChannel是一个连接到TCP网络套接字的通道。类似于ServerSocket,它们的用法比较类似。
-
-
abstract SocketChannel
bind(SocketAddress local)
将信道的套接字绑定到本地地址。
abstract boolean
connect(SocketAddress remote)
连接这个通道的插座。
abstract boolean
finishConnect()
完成了一个套接字通道的连接的过程。
abstract SocketAddress
getLocalAddress()
返回此通道的套接字绑定到的套接字地址。
abstract SocketAddress
getRemoteAddress()
返回此通道的套接字连接的远程地址。
abstract boolean
isConnected()
告诉是否该通道的网络套接字连接。
abstract boolean
isConnectionPending()
告诉是否该通道的连接操作正在进行中。
static SocketChannel
open()
打开一个套接字通道。
static SocketChannel
open(SocketAddress remote)
打开一个套接字通道,并将其连接到远程地址。
-
connect() 连接服务器端。
1. 非阻塞模式
如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。
为了确定连接是否建立,可以调用finishConnect()的方法。如:
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
while (!socketChannel.finishConnect()) {
//wait 等待连接
}
3、非阻塞模式与选择器
非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道的兴趣事件来进行连接就绪,读取,写入等。
二、NIO同步网络编程
服务端:
public class SimpleServer {
public static void main(String[] args) {
try {
// 获取一个serverSocketChannel,并设置为非阻塞模式,绑定ip和port
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress("127.0.0.1", 9999));
// 获取Selector,注册,并将 ServerSocketChannel 的接收就绪事件通道注册到 Selector
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 缓冲区, 写点数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer writeBuffer = ByteBuffer.allocate(100);
writeBuffer.put("服务器端收到了".getBytes());
writeBuffer.flip();
// 使用循环不断地监听来自客户端的连接
while (true){
int count = selector.select(1000);
if (count == 0) {
continue;
}
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while(it.hasNext()){
SelectionKey key = it.next();
it.remove();// 手动移除
if(key.isAcceptable()){
// 接收就绪,创建新的连接,并且把连接注册到Selector上,并且指定感兴趣的事件是 Accept
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 这里我们指定 Server先读,Client先写
socketChannel.register(selector, SelectionKey.OP_READ);
}else if (key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
readBuffer.clear();
socketChannel.read(readBuffer);
readBuffer.flip();
System.out.println("服务器received:" + new String(readBuffer.array()));
key.interestOps(SelectionKey.OP_WRITE);
}else if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
writeBuffer.rewind();
socketChannel.write(writeBuffer);
key.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
客户端:
public class SimpleClient {
public static void main(String[] args) {
try {
// 创建一个SocketChannel,并设置为非阻塞模式,绑定ip和port
// 获取Selector,注册,并将 socketChannel 的连接事件通道注册到 Selector
// 并向服务器发出连接请求
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
// 缓冲区, 写点数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer writeBuffer = ByteBuffer.allocate(100);
// 使用一个循环来连接服务器
while (true) {
int count = selector.select(5000);
if (count == 0) {
continue;
}
Set selectionKeys = selector.selectedKeys();
Iterator it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey selectionKey = it.next();
it.remove();
if (selectionKey.isConnectable()) {
SocketChannel sc = (SocketChannel) selectionKey.channel();
if (socketChannel.isConnectionPending()) {
if (sc.finishConnect()) {
writeBuffer.put("客户端向你问好".getBytes());
writeBuffer.flip();
sc.write(writeBuffer);
// 改变选择器对通道的兴趣事件, 两种方式都可以,二选一
//socketChannel.register(selector, SelectionKey.OP_READ);
selectionKey.interestOps(SelectionKey.OP_READ);
} else {
System.exit(1);
}
}
} else if (selectionKey.isReadable()) {
int readBytes = socketChannel.read(readBuffer);
if (readBytes > 0) {
socketChannel.read(readBuffer);
readBuffer.flip();
System.out.println("客户端received:" + new String(readBuffer.array()));
}
}
}
}
} catch (
IOException ioException) {
ioException.printStackTrace();
}
}
}
这篇博主通过线程的实例代码不错,可以参考:IO模型之NIO代码及其实践详解
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。