【Netty】NIO 网络编程 聊天室案例
一、 NIO 聊天室需求
1 . NIO 聊天室需求 :
① 服务器 客户端 通信 : 服务器 与 客户端 实现 双向通信 ; 服务器可以写出数据到客户端 , 也能读取客户端的数据 ; 客户端可以写出数据到服务器端 , 也可以读取服务器端的数据 ;
② 多人聊天 : 一个服务器 与 多个客户端 进行数据交互 , 同时还要实现将某一个客户端的数据转发给其它客户端 ;
③ 用户状态监测 : 服务器可以检测用户的 上线 , 离线 状态 ;
2 . 数据传输细节 :
① 上线监听 : 当有客户端连接时 , 服务器检测到用户上线 , 服务器将该用户上线状态通知给其它客户端 ;
② 下线监听 : 如果有客户端离线 , 服务器检测到连接断开 , 服务器将该用户离线的状态通知给聊天室的其它客户端 ;
③ 聊天信息转发 : 客户端发送消息时 , 服务器端接收到该数据 , 并转发给聊天室的其它用户客户端 ;
二、 NIO 聊天室 服务器端 代码分析
服务器端的连接管理流程 : 创建 服务器套接字通道 ( ServerSocketChannel ) , 将该通道注册给 选择器 ( Selector ) , 选择器开启监听 , 监听到客户端连接 , 就创建一个 套接字通道 ( SocketChannel ) , 注册给选择器 ;
服务器端的消息转发流程 : 服务器端收到客户端发送的消息 , 将该消息转发给除该客户端外的其它客户端 , 从选择器中可以获取到所有的 通道 , 注意 屏蔽 服务器套接字通道 和 发送本消息的客户端对应的通道 ;
服务器连接监听 : 当客户端与服务器连接成功 , 即触发注册给 选择器 ( Selector ) 的 服务器套接字通道 ( ServerSocketChannel ) 的 SelectionKey.OP_ACCEPT 事件 , 表示有客户端连接服务器成功 , 用户上线 ;
服务器断开连接监听 : 当服务器端与客户端读写数据出现异常时 , 说明该客户端离线 , 在异常处理代码中可以判定某个客户端离线 ;
1 . 服务器套接字通道 : 调用 open 静态方法创建服务器套接字通道 , 并绑定 8888 端口 , 设置非阻塞网络通信模式 ;
// 创建并配置 服务器套接字通道 ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
- 1
- 2
- 3
- 4
2 . 服务器端选择器 : 调用 open 静态方法获取 选择器 , 注册之前创建的 服务器套接字通道 ;
// 获取选择器, 并注册 服务器套接字通道 ServerSocketChannel
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
- 1
- 2
- 3
3 . 监听事件 : 阻塞监听, 如果有事件触发, 返回触发的事件个数 ; 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中 ;
// 阻塞监听, 如果有事件触发, 返回触发的事件个数
// 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中
// 下面开始遍历上述 selectedKeys 集合
try {
int eventTriggerCount = selector.select();
} catch (IOException e) {
e.printStackTrace();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4 . 处理客户端连接事件 : 接受客户端连接 , 获取 网络套接字通道 ( SocketChannel ) , 并注册给 选择器 ( Selector ) , 监听 SelectionKey.OP_READ 数据读取事件 ;
// 客户端连接服务器, 服务器端需要执行 accept 操作
if (key.isAcceptable()) {
//创建通道 : 为该客户端创建一个对应的 SocketChannel 通道
//不等待 : 当前已经知道有客户端连接服务器, 因此不需要阻塞等待
//非阻塞方法 : ServerSocketChannel 的 accept() 是非阻塞的方法
SocketChannel socketChannel = null;
try {
socketChannel = serverSocketChannel.accept();
//如果 ServerSocketChannel 是非阻塞的, 这里的 SocketChannel 也要设置成非阻塞的
//否则会报 java.nio.channels.IllegalBlockingModeException 异常
socketChannel.configureBlocking(false);
//注册通道 : 将 SocketChannel 通道注册给 选择器 ( Selector )
//关注事件 : 关注事件时读取事件, 服务器端从该通道读取数据
//关联缓冲区 :
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println(String.format("用户 %s 进入聊天室", socketChannel.getRemoteAddress()));
} catch (IOException e) {
e.printStackTrace();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
5 . 处理客户端消息转发事件 :
① 读取客户端上传的数据 : 通过 SelectionKey 获取 通道 和 缓冲区 , 使用 套接字通道 ( SocketChannel ) 读取 缓冲区 ( ByteBuffer ) 中的数据 , 然后记录显示该数据 ;
// 获取 通道 ( Channel ) : 通过 SelectionKey 获取
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取 缓冲区 ( Buffer ) : 获取到 通道 ( Channel ) 关联的 缓冲区 ( Buffer )
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
String remoteAddress = null;
String message = null;
try {
// 读取客户端传输的数据
int readCount = socketChannel.read(byteBuffer);
byte[] messageBytes = new byte[readCount];
byteBuffer.flip();
byteBuffer.get(messageBytes);
// 处理读取的消息
message = new String(messageBytes);
//重置以便下次使用
byteBuffer.flip();
remoteAddress = socketChannel.getRemoteAddress().toString();
System.out.println(String.format("%s : %s", remoteAddress, message));
} catch (IOException e) {
//e.printStackTrace();
// 如果此处出现异常, 说明该客户端离线了, 服务器提示, 取消选择器上的注册信息, 关闭通道
try {
System.out.println( String.format("%s 用户离线 !", socketChannel.getRemoteAddress()) );
key.cancel();
socketChannel.close();
//继续下一次循环
continue;
} catch (IOException ex) {
ex.printStackTrace();
}
}
- 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
② 转发给其它客户端 : 从 选择器 ( Selector ) 的 keys 集合 中获取所有注册的通道 , 然后除 ServerSocketChannel 和 发送本信息的 客户端对应的 SocketChannel 通道 之外 , 其它所有的通道都转发一份聊天信息 ;
// 向其它客户端转发消息, 发送消息的客户端自己就不用再发送该消息了
// 遍历所有注册到 选择器 Selector 的 SocketChannel
Set<SelectionKey> selectionKeys = selector.keys();
for (SelectionKey selectionKey : selectionKeys) {
// 获取客户端对应的 套接字通道
// 这里不能强转成 SocketChannel, 因为这里可能存在 ServerSocketChannel
Channel channel = selectionKey.channel();
// 将自己排除在外, 注意这里是地址对比, 就是这两个类不能是同一个地址的类
// 这个类的类型必须是 SocketChannel, 排除之前注册的 ServerSocketChannel 干扰
if (socketChannel != channel && channel instanceof SocketChannel) {
// 将通道转为 SocketChannel, 之后将字符串发送到客户端
SocketChannel clientSocketChannel = (SocketChannel) channel;
// 写出字符串到其它客户端
try {
clientSocketChannel.write(ByteBuffer.wrap( ( remoteAddress + " : " + message ).getBytes()));
} catch (IOException e) {
//e.printStackTrace();
// 如果此处出现异常, 说明该客户端离线了, 服务器提示, 取消选择器上的注册信息, 关闭通道
try {
System.out.println( String.format("%s 用户离线 !", clientSocketChannel.getRemoteAddress()) );
selectionKey.cancel();
clientSocketChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
- 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
三、 NIO 聊天室 客户端 代码分析
客户端的连接与数据接收 : 客户端的工作是连接服务器 , 得到与服务器通信的 套接字通道 ( SocketChannel ) , 注册该通道到 选择器 ( Selector ) , 监听 SelectionKey.OP_READ 读取数据事件 , 接收到数据后显示即可 ;
1 . 连接服务器 : 连接服务器 , 并设置网络通信非阻塞模式 ;
// 创建并配置 服务器套接字通道 ServerSocketChannel
socketChannel = SocketChannel.open(new InetSocketAddress(SERVER_ADDRESS, PORT));
socketChannel.configureBlocking(false);
- 1
- 2
- 3
2 . 获取选择器并注册通道 : 获取 选择器 ( Selector ) , 并将 套接字通道 ( SocketChannel ) 注册给该选择器 ;
// 获取选择器, 并注册 服务器套接字通道 ServerSocketChannel
selector = Selector.open();
//注册通道 : 将 SocketChannel 通道注册给 选择器 ( Selector )
//关注事件 : 关注事件时读取事件, 服务器端从该通道读取数据
//关联缓冲区 :
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
- 1
- 2
- 3
- 4
- 5
- 6
3 . 监听服务器端下发的消息 : 阻塞监听, 如果有事件触发, 返回触发的事件个数 ; 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中 ;
// 阻塞监听, 如果有事件触发, 返回触发的事件个数
// 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中
// 下面开始遍历上述 selectedKeys 集合
try {
int eventTriggerCount = selector.select();
} catch (IOException e) {
e.printStackTrace();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4 . 处理服务器端发送的数据 : 如果监听到服务器下发数据 , 开始遍历当前触发事件的通道 , 调用该通道读取数据到缓冲区 , 之后显示该数据 ;
// 处理事件集合 :
// 获取当前发生的事件的 SelectionKey 集合, 通过 SelectionKey 可以获取对应的 通道
Set<SelectionKey> keys = selector.selectedKeys();
// 使用迭代器迭代, 涉及到删除操作
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 根据 SelectionKey 的事件类型, 处理对应通道的业务逻辑
// 客户端写出数据到服务器端, 服务器端需要读取数据
if (key.isReadable()) {
// 获取 通道 ( Channel ) : 通过 SelectionKey 获取
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取 缓冲区 ( Buffer ) : 获取到 通道 ( Channel ) 关联的 缓冲区 ( Buffer )
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
String message = null;
try {
// 读取客户端传输的数据
int readCount = socketChannel.read(byteBuffer);
byte[] messageBytes = new byte[readCount];
byteBuffer.flip();
byteBuffer.get(messageBytes);
// 处理读取的消息
message = new String(messageBytes);
byteBuffer.flip();
System.out.println(String.format(message));
} catch (IOException e) {
//e.printStackTrace();
// 客户端连接断开
key.cancel();
try {
socketChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}// try
}// if (key.isReadable())
- 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
- 37
- 38
四、 NIO 聊天室 服务器端 完整代码
package kim.hsl.nio.chat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* 聊天室服务器端
*
* @author hsl
* @date 2020-05-29 17:24
*/
public class Server {
/**
* 服务器监听的端口号
*/
public static final int PORT = 8888;
/**
* 监听 ServerSocketChannel 通道和各个客户端对应的 SocketChannel 通道
*/
private Selector selector;
/**
* 服务器端的套接字通道, 相当于 BIO 中的 ServerSocket
*/
private ServerSocketChannel serverSocketChannel;
/**
* 初始化服务器相关操作
*/
public Server() {
initServerSocketChannelAndSelector();
}
/**
* 初始化 服务器套接字通道 和
*/
private void initServerSocketChannelAndSelector() {
try {
// 创建并配置 服务器套接字通道 ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
// 获取选择器, 并注册 服务器套接字通道 ServerSocketChannel
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Selector 开始执行 监听工作
*/
private void selectorStartSelectOperation() {
System.out.println("服务器端启动监听 :");
while (true) {
// 阻塞监听, 如果有事件触发, 返回触发的事件个数
// 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中
// 下面开始遍历上述 selectedKeys 集合
try {
int eventTriggerCount = selector.select();
} catch (IOException e) {
e.printStackTrace();
}
// 当前状态说明 :
// 如果能执行到该位置, 说明 selector.select() 方法返回值大于 0
// 当前有 1 个或多个事件触发, 下面就是处理事件的逻辑
// 处理事件集合 :
// 获取当前发生的事件的 SelectionKey 集合, 通过 SelectionKey 可以获取对应的 通道
Set<SelectionKey> keys = selector.selectedKeys();
// 使用迭代器迭代, 涉及到删除操作
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 根据 SelectionKey 的事件类型, 处理对应通道的业务逻辑
// 客户端连接服务器, 服务器端需要执行 accept 操作
if (key.isAcceptable()) {
//创建通道 : 为该客户端创建一个对应的 SocketChannel 通道
//不等待 : 当前已经知道有客户端连接服务器, 因此不需要阻塞等待
//非阻塞方法 : ServerSocketChannel 的 accept() 是非阻塞的方法
SocketChannel socketChannel = null;
try {
socketChannel = serverSocketChannel.accept();
//如果 ServerSocketChannel 是非阻塞的, 这里的 SocketChannel 也要设置成非阻塞的
//否则会报 java.nio.channels.IllegalBlockingModeException 异常
socketChannel.configureBlocking(false);
//注册通道 : 将 SocketChannel 通道注册给 选择器 ( Selector )
//关注事件 : 关注事件时读取事件, 服务器端从该通道读取数据
//关联缓冲区 :
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println(String.format("用户 %s 进入聊天室", socketChannel.getRemoteAddress()));
} catch (IOException e) {
e.printStackTrace();
}
}
// 客户端写出数据到服务器端, 服务器端需要读取数据
if (key.isReadable()) {
// 获取 通道 ( Channel ) : 通过 SelectionKey 获取
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取 缓冲区 ( Buffer ) : 获取到 通道 ( Channel ) 关联的 缓冲区 ( Buffer )
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
String remoteAddress = null;
String message = null;
try {
// 读取客户端传输的数据
int readCount = socketChannel.read(byteBuffer);
byte[] messageBytes = new byte[readCount];
byteBuffer.flip();
byteBuffer.get(messageBytes);
// 处理读取的消息
message = new String(messageBytes);
//重置以便下次使用
byteBuffer.flip();
remoteAddress = socketChannel.getRemoteAddress().toString();
System.out.println(String.format("%s : %s", remoteAddress, message));
} catch (IOException e) {
//e.printStackTrace();
// 如果此处出现异常, 说明该客户端离线了, 服务器提示, 取消选择器上的注册信息, 关闭通道
try {
System.out.println( String.format("%s 用户离线 !", socketChannel.getRemoteAddress()) );
key.cancel();
socketChannel.close();
//继续下一次循环
continue;
} catch (IOException ex) {
ex.printStackTrace();
}
}
// 向其它客户端转发消息, 发送消息的客户端自己就不用再发送该消息了
// 遍历所有注册到 选择器 Selector 的 SocketChannel
Set<SelectionKey> selectionKeys = selector.keys();
for (SelectionKey selectionKey : selectionKeys) {
// 获取客户端对应的 套接字通道
// 这里不能强转成 SocketChannel, 因为这里可能存在 ServerSocketChannel
Channel channel = selectionKey.channel();
// 将自己排除在外, 注意这里是地址对比, 就是这两个类不能是同一个地址的类
// 这个类的类型必须是 SocketChannel, 排除之前注册的 ServerSocketChannel 干扰
if (socketChannel != channel && channel instanceof SocketChannel) {
// 将通道转为 SocketChannel, 之后将字符串发送到客户端
SocketChannel clientSocketChannel = (SocketChannel) channel;
// 写出字符串到其它客户端
try {
clientSocketChannel.write(ByteBuffer.wrap( ( remoteAddress + " : " + message ).getBytes()));
} catch (IOException e) {
//e.printStackTrace();
// 如果此处出现异常, 说明该客户端离线了, 服务器提示, 取消选择器上的注册信息, 关闭通道
try {
System.out.println( String.format("%s 用户离线 !", clientSocketChannel.getRemoteAddress()) );
selectionKey.cancel();
clientSocketChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
// 处理完毕后, 当前的 SelectionKey 已经处理完毕
// 从 Set 集合中移除该 SelectionKey
// 防止重复处理
keyIterator.remove();
}
}
}
public static void main(String[] args) {
Server server = new Server();
server.selectorStartSelectOperation();
}
}
- 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
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
五、 NIO 聊天室 客户端 完整代码
package kim.hsl.nio.chat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class Client {
/**
* 服务器地址
*/
public final static String SERVER_ADDRESS = "127.0.0.1";
/**
* 服务器监听的端口号
*/
public static final int PORT = 8888;
/**
* 监听 SocketChannel 通道的 选择器
*/
private Selector selector;
/**
* 服务器端的套接字通道, 相当于 BIO 中的 ServerSocket
*/
private SocketChannel socketChannel;
public Client() {
initClientSocketChannelAndSelector();
}
/**
* 初始化 服务器套接字通道 和
*/
private void initClientSocketChannelAndSelector() {
try {
// 创建并配置 服务器套接字通道 ServerSocketChannel
socketChannel = SocketChannel.open(new InetSocketAddress(SERVER_ADDRESS, PORT));
socketChannel.configureBlocking(false);
// 获取选择器, 并注册 服务器套接字通道 ServerSocketChannel
selector = Selector.open();
//注册通道 : 将 SocketChannel 通道注册给 选择器 ( Selector )
//关注事件 : 关注事件时读取事件, 服务器端从该通道读取数据
//关联缓冲区 :
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 向服务器端发送消息
* @param message
*/
public void sendMessageToServer(String message){
try {
socketChannel.write(ByteBuffer.wrap(message.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void readMessageFromServer(){
// 阻塞监听, 如果有事件触发, 返回触发的事件个数
// 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中
// 下面开始遍历上述 selectedKeys 集合
try {
int eventTriggerCount = selector.select();
} catch (IOException e) {
e.printStackTrace();
}
// 当前状态说明 :
// 如果能执行到该位置, 说明 selector.select() 方法返回值大于 0
// 当前有 1 个或多个事件触发, 下面就是处理事件的逻辑
// 处理事件集合 :
// 获取当前发生的事件的 SelectionKey 集合, 通过 SelectionKey 可以获取对应的 通道
Set<SelectionKey> keys = selector.selectedKeys();
// 使用迭代器迭代, 涉及到删除操作
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 根据 SelectionKey 的事件类型, 处理对应通道的业务逻辑
// 客户端写出数据到服务器端, 服务器端需要读取数据
if (key.isReadable()) {
// 获取 通道 ( Channel ) : 通过 SelectionKey 获取
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取 缓冲区 ( Buffer ) : 获取到 通道 ( Channel ) 关联的 缓冲区 ( Buffer )
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
String message = null;
try {
// 读取客户端传输的数据
int readCount = socketChannel.read(byteBuffer);
byte[] messageBytes = new byte[readCount];
byteBuffer.flip();
byteBuffer.get(messageBytes);
// 处理读取的消息
message = new String(messageBytes);
byteBuffer.flip();
System.out.println(String.format(message));
} catch (IOException e) {
//e.printStackTrace();
// 客户端连接断开
key.cancel();
try {
socketChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}// try
}// if (key.isReadable())
// 处理完毕后, 当前的 SelectionKey 已经处理完毕
// 从 Set 集合中移除该 SelectionKey
// 防止重复处理
keyIterator.remove();
}
}
public static void main(String[] args) {
Client client = new Client();
// 接收服务器端数据线程
new Thread(new Runnable() {
@Override
public void run() {
while (true){
//不停地从服务器端读取数据
client.readMessageFromServer();
}
}
}).start();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String message = scanner.nextLine();
client.sendMessageToServer(message);
}
}
}
- 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
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
六、 NIO 聊天室 运行
按照如下步骤演示聊天室功能 ( 一定要按照顺序执行 ) ;
1 . 服务器启动 : 首先
2 . 设置客户端多个 : 点击下图绿框中的下拉菜单 , 选择 Edit Configuration , 弹出如下对话框 , 配置 Client 应用 , 勾选 Allow parallel run 选项 , 之后 Client 程序就可以运行多个 , 否则只能运行一个 ;
3 . 客户端 1 1 1 连接 : 运行客户端程序即可 ;
4 . 客户端 2 2 2 连接 : 运行客户端程序即可 ;
5 . 客户端 3 3 3 连接 : 运行客户端程序即可 ;
6 . 客户端 2 2 2 发送消息 :
服务器显示 : 客户端 2 2 2 将消息发送给服务器 , 服务器显示该消息 ;
客户端 1 1 1 显示 : 这是由服务器转发的客户端 2 2 2 发送的消息 , 客户端 3 3 3 也收到该消息 ;
7 . 客户端 2 2 2 退出 : 终止客户端 2 2 2 程序 , 服务器端检测到异常 , 就可以将客户端 2 2 2 退出 ;
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/106422974
- 点赞
- 收藏
- 关注作者
评论(0)