网络通信就是一个基本的通道连接,在NIO中提供两个新的通道类:ServerSocketChannel SocketChannel 。为了方便地进行所有通道的管理,NIO提供一个Selector 通道管理类,这样所有的通道都可以直接向Selector进行注册,并采用统一的模式进行读/写操作,这样的设计被称为Reactor模式 。
在进行非阻塞网络开发的时候需要使用SelectableChannel类型向Select类注册,SelectableChannel 提供了注册Selector的方法和阻塞模式。ServerSocketChannel描述了服务器通道。
在使用register()方法的时候需要指定一个选择器(Selector对象)以及Select域,Selector对象可以通过Selector中的open()方法取得,而Selector域则在SelectionKey类中定义。
1-1、实现服务器端程序:
package com.mydemo;
import java.io.IOException;
import java.net.InetSocketAddress;
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.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NIOServer {
// 绑定端口
public static final int PORT = 9999;
public static void main(String[] args) throws IOException {
// 考虑到性能的优化,所以最多只允许5个用户进行访问
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 打开一个服务器端的Socket的连接通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置非阻塞模式
serverSocketChannel.configureBlocking(false);
// 服务绑定端口
serverSocketChannel.bind(new InetSocketAddress(PORT));
// 打开一个选择器,随后所有的Channel都要注册到此选择器中
Selector selector = Selector.open();
// 将当前的ServerSocketChannel统一注册到Selector中,接受统一的管理
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器端启动程序,该程序在" + PORT + "端口上监听,等待客户端连接......");
// 接受连接状态
int keySelect = 0;
// 持续等待连接
while((keySelect = selector.select()) > 0){
// 获取全部连接通道
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeySet.iterator();
while(iterator.hasNext()){
// 获取每一个通道
SelectionKey selectionKey = iterator.next();
// 模式为接收连接模式
if(selectionKey.isAcceptable()){
// 等待接收
SocketChannel socketChannel_Client = serverSocketChannel.accept();
// 已经有了连接
if(socketChannel_Client != null){
executorService.submit(new SocketClientChannelThread(socketChannel_Client));
}
}
// 移除此通道
iterator.remove();
}
}
// 关闭线程池
executorService.shutdown();
// 关闭服务器端通道
serverSocketChannel.close();
}
}
// 客户端处理线程
class SocketClientChannelThread implements Runnable{
private SocketChannel socketChannel_Client; // 客户端通道
private boolean flag = true; // 循环标记
public SocketClientChannelThread(SocketChannel socketChannel_Client) throws IOException {
// 保存客户端通道
this.socketChannel_Client = socketChannel_Client;
System.out.println("【客户端连接成功】, 该客户端的地址为: " + socketChannel_Client.getRemoteAddress());
}
/**
* 线程任务
*/
@Override
public void run() {
// 创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(50);
try {
// 不断与客户端交互
while (this.flag){
// 由于可能重复使用一个Buffer,所以使用前需要将其做出清空处理
byteBuffer.clear();
// 接收客户端发送数据
int readCount = this.socketChannel_Client.read(byteBuffer);
// 数据变为字符串
String readMessage = new String(byteBuffer.array(), 0, readCount).trim();
// 提示信息
System.out.println("【服务器接收消息】" + readMessage);
// 回应信息
String writeMessage = "【ECHO】" + readMessage + "\n";
// 结束指令
if("exit".equalsIgnoreCase(readMessage)){
// 结束标记
writeMessage = "【EXIT】再见~~~~~~";
// 修改标记
this.flag = false;
}
// 清空缓冲区
byteBuffer.clear();
// 缓冲区保存数据
byteBuffer.put(writeMessage.getBytes());
// 重置缓冲区
byteBuffer.flip();
// 回应消息
this.socketChannel_Client.write(byteBuffer);
}
// 关闭通道
this.socketChannel_Client.close();
}catch (Exception e){}
}
}
1-2_1、定义一个输入工具类,实现键盘数据接收:
package com.mydemo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputUtil {
// 键盘缓冲输入流
private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(
new InputStreamReader(System.in)
);
// 键盘接收数据
public InputUtil() {
}
// 键盘接收数据
public static String getString(String prompt) throws IOException {
// 输入标记
boolean flag = true;
// 接收输入字符串
String str = null;
while (flag){
// 提示信息
System.out.println(prompt);
// 读取数据
str = KEYBOARD_INPUT.readLine();
// 保证不为null
if(str == null || "".equals(str)){
System.out.println("数据输入有误,请重新输入~~~");
}else{
flag = false;
}
}
return str;
}
}
1-2_2、实现客户端Socket程序:
package com.mydemo;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
// 连接主机
public static final String HOST = "localhost";
// 绑定端口
public static final int PORT = 9999;
public static void main(String[] args) throws Exception {
// 获取客户端通道
SocketChannel socketChannel_Client = SocketChannel.open();
// 连接服务器端
socketChannel_Client.connect(new InetSocketAddress(HOST, PORT));
// 开辟缓存
ByteBuffer byteBuffer = ByteBuffer.allocate(500);
boolean flag = true;
// 持续性输入信息
while(flag){
// 清空缓冲区
byteBuffer.clear();
// 提示信息
String msg = InputUtil.getString("请输入要发送的信息:");
// 数据保存在缓冲区
byteBuffer.put(msg.getBytes());
// 重设缓冲区
byteBuffer.flip();
// 发送消息
socketChannel_Client.write(byteBuffer);
// 清空缓冲区
byteBuffer.clear();
// 读取服务器端回应
int readCount = socketChannel_Client.read(byteBuffer);
// 重置缓冲区
byteBuffer.flip();
// 输出信息
System.out.println(new String(byteBuffer.array(), 0, readCount));
// 结束指令
if("exit".equals(msg)){
// 结束循环
flag = false;
}
}
// 关闭通道
socketChannel_Client.close();
}
}