在之前的三篇文章中介绍了 NIO 的相关知识:

  • Java NIO 缓冲区
  • Java NIO 通道
  • Java NIO 选择器Selector

下面根据 NIO 的相关知识,编写一个 NIO 服务端与客户端通信的一个完整的比较简单的例子。

程序的功能是客户端向服务器端发送 request time 请求,服务器接收到请求并验证正确后向客户端返回服务器时间。

服务器:

public class TimeServer {
private static final String ORDER = "request time";
private static final int DEFAULT_PORT = 8080;
private static volatile boolean stop = false;
private static Selector selector;

public static void main(String[] args) throws IOException {
new TimeServer().run();
}

/**
* 启动服务器
* @throws IOException
*/
public void run() throws IOException {
run(DEFAULT_PORT);
}

private void run(int port) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(port));
System.out.println("服务器已开启!");
while (!stop) {
selector.select(1000);
Set<SelectionKey> keySet = selector.selectedKeys();
Iterator<SelectionKey> it = keySet.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
try {
response(key);
} catch (IOException e) {
key.cancel();
key.channel().close();
}
it.remove();
}
}
}

/**
* 接收客户端请求并返回相应内容
* @param key
* @throws IOException
*/
private void response(SelectionKey key) throws IOException {
if (key.isValid()) {
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer toRead = ByteBuffer.allocate(1024);
int number = sc.read(toRead);
if (number > 0) {
toRead.flip();
byte[] msg = new byte[toRead.remaining()];
toRead.get(msg);
String order = new String(msg, "UTF-8");
System.out.println("接收请求:" + order);
String response;
if (ORDER.equalsIgnoreCase(order)) {
response = LocalDateTime.now().withNano(0).toString();
System.out.println("成功发送:" + response);
} else {
response = "请求错误!";
}
ByteBuffer toWrite = ByteBuffer.allocate(response.length());
toWrite.put(response.getBytes());
toWrite.flip();
sc.write(toWrite);
} else if (number < 0) {
key.cancel();
sc.close();
}
}
}
}

/**
* 停止服务器
*/
private void stop() {
stop = true;
}
}

客户端:

public class TimeClient {
private static final int DEFAULT_PORT = 8080;
private static final String ORDER = "request time";
private SocketChannel sc;
private Selector selector;

public static void main(String[] args) throws IOException {
new TimeClient().run();
}

/**
* 运行客户端
* @throws IOException
*/
public void run() throws IOException {
sc = SocketChannel.open();
sc.configureBlocking(false);
selector = Selector.open();
connect(InetAddress.getLocalHost(), DEFAULT_PORT);
while (true) {
selector.select(1000);
Set<SelectionKey> keySet = selector.selectedKeys();
Iterator<SelectionKey> it = keySet.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
try {
request(key);
} catch (IOException e) {
key.cancel();
key.channel().close();
}
it.remove();
}
}
}

/**
* 与服务器连接
* @param address 服务器地址
* @param port 服务器端口
* @throws IOException
*/
public void connect(InetAddress address, int port) throws IOException {
System.out.println("连接服务器中......");
boolean connected = sc.connect(new InetSocketAddress(address, port));
if (connected) {
System.out.println("连接成功!");
ByteBuffer toWrite = ByteBuffer.allocate(ORDER.length());
toWrite.put(ORDER.getBytes());
toWrite.flip();
sc.write(toWrite);
System.out.println("发送命令:" + ORDER);
} else {
sc.register(selector, SelectionKey.OP_CONNECT);
}
}

/**
* 向服务器发送请求
* @param key
* @throws IOException
*/
public void request(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel channel = (SocketChannel) key.channel();
channel.configureBlocking(false);
if (key.isConnectable()) {
try {
if (channel.finishConnect()) {
System.out.println("连接成功!");
channel.register(selector, SelectionKey.OP_READ);
ByteBuffer toWrite = ByteBuffer.allocate(ORDER.getBytes().length);
toWrite.put(ORDER.getBytes());
toWrite.flip();
channel.write(toWrite);
System.out.println("发送请求:" + ORDER);
}
} catch (IOException e) {
System.out.println("连接失败!");
System.exit(1);
}
}
if (key.isReadable()) {
ByteBuffer toRead = ByteBuffer.allocate(1024);
int number = channel.read(toRead);
if (number > 0) {
toRead.flip();
byte[] msg = new byte[toRead.remaining()];
toRead.get(msg);
String response = new String(msg, "UTF-8");
System.out.println("返回时间:" + response);
} else if (number < 0) {
key.cancel();
channel.close();
}
}
}
}
}

运行结果:

  • 服务器
服务器已开启!
接收请求:request time
成功发送:2017-11-24T21:35
  • 客户端
连接服务器中......
连接成功!
发送请求:request time
返回时间:2017-11-24T21:35

参考链接:

  • Netty权威指南