前面 我们介绍了 FileChannel,本篇我们来一起学习 SocketChannel, ServerSocketChannel 与 DatagramChannel。

ServerSocketChannel

Java NIO ServerSocketChannel 是用于监听 TCP 端口连接的通道,与 Java 标准 IO 中的 ServerSocket 一样。ServerSocketChannel 位于 java.nio.channels 包下。

例如:

1
2
3
4
5
6
7
8
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){
SocketChannel socketChannel = serverSocketChannel.accept();

//do something with socketChannel...
}

打开 ServerSocketChannel

通过调用 ServerSocketChannel.open() 方法,即可打卡一个 ServerSocketChannel:

1
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

关闭 ServerSocketChannel

调用 serverSocketChannel 中的 close() 方法,即可关闭:

1
serverSocket.close();

监听传入连接

通过调用 ServerSocketChannel.accept() 方法,我们可以来监听 TCP 端口上的连接。当 accept () 返回时,它将返回带有传入连接的 SocketChannel。因此,accept () 方法会一直阻塞到有传入连接到达位置。

由于您通常不想只听一个连接,所以在 while 循环中调用 accept ()。如下:

1
2
3
4
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
//do something with socketChannel...
}

Non-blocking 模式

ServerSocketChannel 可以设置为 non-blocking 模式,即使没有传入连接达到,accept () 方法也会立即返回。因此,你需要检查返回的 SocketChannel 是否为 null。例如:

1
2
3
4
5
6
7
8
9
10
11
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
SocketChannel socketChannel = serverSocketChannel.accept();

if(socketChannel != null){
//do something with socketChannel...
}
}

SocketChannel

Java NIO SocketChannel 是用于连接 TCP 网络 Socket 的通道。有两种方式可以来创建 SocketChannel:

  1. 打开一个 SocketChannel,并连接到网络上的某个服务的 TCP 端口上。
  2. 在 ServerSocketChannel 中提到过,当有传入连接进来是,ServerSocketChannel 的 accpet () 方法可以获得 SocketChannel.

打开 SocketChannel

1
2
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 80));

关闭 SocketChannel

1
socketChannel.close();    

读取数据

从 socketchannel 中读取数据到 byteBuffer 中。

1
2
3
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

写入数据

1
2
3
4
5
6
7
8
9
10
11
String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
channel.write(buf);
}

Non-blocking 模式

我们可以设置 SocketChannel 为 非阻塞模式,这样的话, 你就可以用异步模式来调用 connect(), read()write() 方法。

connect()

如果 SocketChannel 处于 非阻塞模式,并且调用了 connect () 方法,这个方法会在连接建立之前返回。我们可以通过调用 finishConnect () 来判断连接是否建立。像这样:

1
2
3
4
5
6
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 80));

while(! socketChannel.finishConnect() ){
//wait, or do something else...
}

write()

在非阻塞模式下,write () 方法可能在没有写入任何内容的情况下就返回了。 因此,您需要在循环中调用 write () 方法。

read()

在非阻塞模式下,read() 方法可能在没有读取任何数据的情况下就返回了。因此,您需要注意返回的 int,它会告诉读取了多少字节。

示例

前面 文章中的示例代码。

DatagramChannel

DatagramChannle 是一个可以用于发送和接收 UDP 数据包的通道。由于 UDP 是一种无连接的网络协议,因此我们不能像在其他通道中那样默认读取和写入 DatagramChannel,而是发送和接收数据包。

打开 DatagramChannel

在 UDP 端口 9999 上接收数据包:

1
2
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

接收数据

1
2
3
4
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();

channel.receive(buf);

receive() 方法将会把收到的数据包中的内容复制到 Buffer 中。如果接收到的数据比 Buffer 的容量还大,多余的数据会默认丢弃掉。

发送数据

1
2
3
4
5
6
7
8
String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();

int bytesSent = channel.send(buf, new InetSocketAddress("127.0.0.1", 80));

此示例将字符串发送到 UDP 端口 80 上的 “127.0.0.1” 服务器。但是没有任何内容正在侦听该端口,因此不会发生任何事情。 由于 UDP 不对数据传送做出任何保证,因此不会通知你是否收到了发送数据包。

示例

我们用 DatagramChannel 来实现一下 EchoServer。

DatagramSocketEchoServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package one.wangwei.java.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramSocketEchoServer {

public static void main(String[] args) {
try {
DatagramChannel server = DatagramChannel.open();
InetSocketAddress sAddr = new InetSocketAddress("127.0.0.1", 8989);
server.bind(sAddr);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

while (true) {
System.out.println("Waiting for a message from a remote host at " + sAddr);
SocketAddress remoteAddr = server.receive(byteBuffer);
byteBuffer.flip();
int limits = byteBuffer.limit();
byte[] bytess = new byte[limits];
byteBuffer.get(bytess, 0, limits);
String msg = new String(bytess);

System.out.println("Client at " + remoteAddr + " says: " + msg);
byteBuffer.rewind();
server.send(byteBuffer, remoteAddr);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

DatagramSocketEchoClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package one.wangwei.java.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramSocketEchoClient {

public static void main(String[] args) {
try {
DatagramChannel client = DatagramChannel.open();
client.bind(null);

String msg = "hello";
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 8989);

client.send(buffer, serverAddress);
buffer.clear();
client.receive(buffer);
buffer.flip();

int limits = buffer.limit();
byte[] bytes = new byte[limits];
buffer.get(bytes, 0, limits);
String response = new String(bytes);
System.out.println("Server response: " + response);
client.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

参考资料