Java NIO ServerSocketChannel、SocketChannel、DatagramChannel

前面 我们介绍了 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
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();
}
}
}

参考资料

请我喝杯咖啡吧~