案例代码:https://gitee.com/flymini/codes01/tree/master/example_/com-learn-nio01
****
下面将实现多个 Client 与一个 Server 通信,Client 向 Server 发送数据,Server接收数据。
![](https://img.kancloud.cn/aa/9c/aa9cd7d9dbe649f5e319f13744beb963_979x308.jpg)
<br/>
步骤如下:
**1. 编写服务端程序**
```java
public class NioServer {
// 选择器
private Selector selector;
// 连接通道
private ServerSocketChannel listenChannel;
public NioServer(int port) {
try {
selector = Selector.open();
listenChannel = ServerSocketChannel.open();
// 绑定端口
listenChannel.socket().bind(new InetSocketAddress(port));
// 设置非阻塞模式
listenChannel.configureBlocking(false);
// 通道注册到selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}
public void listen() {
System.out.println("服务端正在监听.........");
try {
while (true) {
// 获取事件
if (selector.select() == 0) {
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 监听到不同的事件进行不同的处理
listenHandler(key);
// 删除当前key,防止重复处理
iterator.remove();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 针对监听到的不同事件做不同的处理
*/
public void listenHandler(SelectionKey key) {
SocketChannel sc = null;
try {
// 连接事件处理
if (key.isAcceptable()) {
sc = listenChannel.accept();
// 设置非阻塞模式
sc.configureBlocking(false);
// 通道注册到selector
sc.register(selector, SelectionKey.OP_READ);
}
// 读取事件处理
if (key.isReadable()) {
// 拿到socketChannel
sc = (SocketChannel) key.channel();
// 设置非阻塞模式
sc.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取消息数据
if (sc.read(buffer) > 0) {
String msg = new String(buffer.array());
System.out.println("转发消息: " + msg);
// 将消息转发
sendMsgToClient(msg);
}
}
} catch (IOException e) {
try {
// 取消注册
key.cancel();
// 关闭通道
sc.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
/**
* 发送消息到客户端
*/
public void sendMsgToClient(String msg) throws IOException {
for (SelectionKey key : selector.keys()) {
Channel channel = key.channel();
if (channel instanceof SocketChannel) {
SocketChannel targetChannel = (SocketChannel) channel;
// 将msg写到buffer中
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
// 将buffer数据写入通道
targetChannel.write(buffer);
}
}
}
public static void main(String[] args) {
NioServer server = new NioServer(8888);
server.listen();
}
}
```
**2. 编写客户端程序**
客户端程序都是一样的,将下面的代码复制多份,文件名不同即可,就可以构建多个Client了。
```java
public class NioClientE {
// 选择器
private Selector selector;
// 连接通道
private SocketChannel socketChannel;
// 用户名
private String username;
// 启动标志位
private boolean flag;
public NioClientE(String ip, int port, String username) {
try {
this.username = username;
flag = true;
selector = Selector.open();
// 连接服务器
socketChannel = SocketChannel.open(new InetSocketAddress(ip, port));
// 设置非阻塞
socketChannel.configureBlocking(false);
// 连接通道注册到selector
socketChannel.register(selector, SelectionKey.OP_READ);
sendMsgToServer("上线了");
// 监听线程
ExecutorService executor = Executors.newSingleThreadExecutor();
// 循环读取服务端的消息
executor.execute(() -> {
while (flag) {
acceptMsgFromServer();
}
});
executor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 向服务端发送消息
*/
public void sendMsgToServer(String msg) {
msg = username + "," + msg;
try {
// 发送消息
socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 接收服务端发来的消息
*/
public void acceptMsgFromServer() {
try {
if (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext() && flag) {
SelectionKey key = iterator.next();
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
String msg = new String(buffer.array());
System.out.println(msg.trim());
// 移除当前的key,防止重复操作
iterator.remove();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.print("请输入用户名: ");
Scanner scanner = new Scanner(System.in);
String username = scanner.nextLine();
NioClientE client = new NioClientE("127.0.0.1", 8888, username);
while (true) {
// 发送数据到服务端
String msg = scanner.nextLine();
if (msg.equals("exit")) {
try {
client.flag = false;
client.sendMsgToServer("下线了");
client.socketChannel.finishConnect();
client.selector.close();
break;
} catch (IOException e) {
e.printStackTrace();
}
} else {
client.sendMsgToServer(msg);
}
}
}
}
```
**3. 测试**
先启动服务端,然后分别启动客户端,我这里共有2个客户端程序。
![](https://img.kancloud.cn/13/18/13181ab27134edf6714569ede46f8bcd_1397x221.gif)
- 网络通信
- 网络协议
- 端口和套接字
- TCP网络程序
- UDP网络程序
- 多线程聊天室
- 多线程
- 线程相关概念
- 线程实现方式
- 中断线程
- 线程生命周期
- 线程优先级
- 优先级规则
- 案例演示
- 线程同步机制
- 线程同步机制
- synchronized关键字
- ReentrantLock类
- Condition类
- 监视器概念
- volatile关键字
- final变量
- 死锁
- 线程局部变量
- 读/写锁
- 原子类
- 阻塞队列
- 工作规则
- 案例演示
- 常用阻塞队列
- 线程安全集合
- 高效的映射/集/队列
- 并发集视图
- 写数组的拷贝
- Arrays类的并行数组算法
- 同步包装器
- Callable与Future
- 执行器
- 线程池
- 预定执行
- 控制任务组
- Fork-Join框架
- 同步器
- 同步器
- 信号量
- CountDownLatch类
- CyclicBarrier类
- Exchanger类
- SynchronousQueue类
- 线程与Swing
- Swing与线程问题
- 两个原则
- Swing工作线程
- 单一线程规则
- 文件IO
- File类
- 文件输入输出
- ZIP压缩文件
- 集合
- 集合框架
- 集合接口
- 集合实现类
- 线程安全集合
- 集合算法
- 迭代器
- 集合排序
- JDBC
- JDBC是什么
- JDBC-ODBC桥
- JDBC驱动程序类型
- JDBC常用类与接口
- 数据库操作
- 连接数据库
- 增/删/改/查/预处理
- 事务
- 批处理
- commons-dbutils工具
- 安全问题
- Jedis
- 使用Jedis操作Redis数据库
- JSON转换
- 使用连接池
- 案例
- 单例破坏
- 单例定义
- 单例实现方式
- 懒汉式实现单例
- 饿汉式实现单例
- 单例破坏
- 类的单例破坏
- 枚举的单例破坏
- 克隆
- 克隆是什么
- 浅克隆
- 深克隆
- 注解
- 注解是什么
- 三大注解
- 内置注解
- 元注解
- 自定义注解
- NIO
- 相关概念
- BIO/NIO/AIO
- 多线程编程
- 线程同步
- 线程通信
- NIO
- NIO三大核心组件
- NIO网络编程
- NIO文件读写
- AIO
- Java8新特性
- Lambda表达式
- 方法引用
- 函数式接口
- 默认方法
- 什么是默认方法
- 默认方法语法格式
- 多个同名的默认方法问题
- 静态默认方法
- 默认方法实例
- Stream
- Stream是什么
- Stream示例
- Optional容器
- 新的日期时间API
- Base64
- SPI
- SPI是什么
- SPI与API的区别
- 常见场景
- 使用SPI需遵循的约定
- SPI使用步骤