在我的专栏Java NIO中,已经简单的完成了对Java NIO的基本学习,本篇将完成一个Server - Client的完整示例,演示一下学习成果,对Java NIO在网络编程中的应用做一个总结
服务端:
package com.leolee.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* @ClassName NIOServerTest
* @Description: 客户端服务端完整测试——模拟简单群聊,服务端单端口监听
* @Author LeoLee
* @Date 2020/9/23
* @Version V1.0
**/
public class NIOServerTest {
//该map维护通道的唯一标识和通道对象本身
private Map<String, SocketChannel> clientChannelMap = new HashMap<>();
public void buildServer () throws IOException {
//服务端初始化
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);//非阻塞模式(异步模式)
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress("127.0.0.1", 8899));
//构建selector
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//开始阻塞代码,进行请求监听
while (true) {
try {
//开始监听selector中“感兴趣事件”的通道
int number = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
//对应客户端的Channel,服务端通过此channel和客户端联系
final SocketChannel clientSocketChannel;
//开始判断SelectionKey的事件类型
if (selectionKey.isAcceptable()) {//客户端请求服务端,建立连接请求
//获取当前事件发生的通道,ServerSocketChannel的作用就是帮助服务端和客户端建立连接
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
try {
//建立服务端和客户端连接的操作
clientSocketChannel = serverSocketChannel.accept();
clientSocketChannel.configureBlocking(false);//非阻塞模式(异步模式)
//将与客户端建立好连接的channel注册到selector,并对“读”操作感兴趣
clientSocketChannel.register(selector, SelectionKey.OP_READ);
//连接建立后,给每一个对应客户端的channel分配唯一标识
clientChannelMap.put(UUID.randomUUID().toString(), clientSocketChannel);
} catch (IOException e) {
e.printStackTrace();
}
} else if (selectionKey.isReadable()) {//判断客户端是否发送消息给服务端(通道是否有数据可读)
clientSocketChannel = (SocketChannel) selectionKey.channel();
//开始读数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
int i = clientSocketChannel.read(byteBuffer);
if (i > 0) {//开始回写数据到客户端
byteBuffer.flip();
//字节转字符
Charset charset = Charset.forName("UTF-8");//设置字符集
String receiveMessage = String.valueOf(charset.decode(byteBuffer).array());
System.out.println("接收到客户端[" + clientSocketChannel + "]的消息:" + receiveMessage);
//开始向发送者之外的客户端推送消息
//获取发送客户端的唯一标识
String sendKey = null;
for (Map.Entry<String, SocketChannel> entry : clientChannelMap.entrySet()) {
if (entry.getValue().equals(clientSocketChannel)) {
sendKey = entry.getKey();
break;
}
}
//推送消息
for (Map.Entry<String, SocketChannel> entry : clientChannelMap.entrySet()) {
SocketChannel socketChannel = entry.getValue();
//为每一个客户端写入数据
ByteBuffer writeByteBuffer = ByteBuffer.allocate(1024);
writeByteBuffer.put((sendKey + ":" + receiveMessage).getBytes());//将发送的数据写入buffer
writeByteBuffer.flip();
socketChannel.write(writeByteBuffer);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
//重点重点重点重点重点重点重点重点重点重点重点
//处理完每一个selectionKey,就从selectionKeys集合将它清理掉,不然会报空指针异常
selectionKeys.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 功能描述: <br> 运行该方法,即可使用[nc localhost 8899]命令,开启多个客户端进行消息发送测试
* 〈〉
* @Param: [args]
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/24 0:13
*/
public static void main(String[] args) throws IOException {
NIOServerTest scTest = new NIOServerTest();
scTest.buildServer();
}
}
客户端:
package com.leolee.nio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName NIOClientTest
* @Description: 客户端服务端完整测试——模拟简单群聊,本类为客户端,通过Scanner作为数据输入
* @Author LeoLee
* @Date 2020/9/24
* @Version V1.0
**/
public class NIOClientTest {
public void buildClient () {
try {
//发起对服务端连接的建立
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);//非阻塞模式,异步模式
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);//注意:客户端这里注册的是 OP_CONNECT
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8899));
while (true) {//同样是阻塞代码,始终接收服务端的消息数据
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
if (selectionKey.isConnectable()) {//判断是否是已经连接状态,是否已经和服务端建立好了连接
SocketChannel client = (SocketChannel) selectionKey.channel();
if (client.isConnectionPending()) {//判断连接是否在这个通道中“进行”
try {
client.finishConnect();//需要手动的去完成连接的建立[Finishes the process of connecting a socket channel.]
//发送消息通知服务端:连接已建立
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.put((LocalDateTime.now() + ":连接成功").getBytes());
writeBuffer.flip();
client.write(writeBuffer);
//使用异步来接收键盘的标准输入流
ExecutorService executorService = Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory());
executorService.submit(() -> {
while (true) {
writeBuffer.clear();
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(input);
String sendMessage = bufferedReader.readLine();
writeBuffer.put(sendMessage.getBytes());
writeBuffer.flip();
client.write(writeBuffer);
}
});
} catch (IOException e) {
e.printStackTrace();
}
//重点重点重点重点重点重点重点重点重点重点
try {
//在建立好连接之后,注册通道为:对读操作“感兴趣”,即通道关注于读操作,以便之后接收服务端的消息
client.register(selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
} else if (selectionKey.isReadable()) {//判断通道是否可读
SocketChannel client = (SocketChannel) selectionKey.channel();
//开始读取
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
try {
int read = client.read(readBuffer);
if (read > 0) {
String receiveMessage = new String(readBuffer.array(), 0 , read);
System.out.println(receiveMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
//重点重点重点重点重点重点重点重点重点重点
//处理完每一个selectionKey,就从selectionKeys集合将它清理掉,不然会报空指针异常
selectionKeys.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
NIOClientTest test = new NIOClientTest();
test.buildClient();
}
}
客户端同样可以使用 nc 或者 telnet 命令来代替
需要代码的来这里拿嗷:demo项目地址