基于NIO的多客户端群聊
分析需求
服务端
- 监听客户端状态
- 保存客户端聊天记录
- 将客户端的信息分发给其他客户端 群聊
客户端
- 连接服务端
- 接受服务端分发的消息
- 发出消息
代码编写
代码里有详细的注释,这里我们主要是看一下编写步骤
服务端
代码语言:javascript复制---------------------初始化------------------------------ 1.开启serversocket通道 2.开启选择器 3.设置非阻塞,注册任务 --------------------监听客户端------------------- 1.判断是否有连接 2.有链接打印用户上线日志 ---------------读取客户端发送到信息--------------------- 1.打开对应的通道 2.打印消息日志 3.分发消息给其他客户端
//服务端通道
private ServerSocketChannel channel;
// 多路复用选择器
private Selector selector;
public chatServer() {
try {
channel = ServerSocketChannel.open();
selector = Selector.open();
SocketAddress address = new InetSocketAddress(6666);
channel.socket().bind(address);
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}
// 监听客户端变化
public void listenClient() throws Exception {
System.out.println("服务端开始监听客户端变化");
while (true) {
//获取需要处理的事件
int num = selector.select();
if (num == 0) {
continue;
}
Set<SelectionKey> set = selector.selectedKeys();
Iterator<SelectionKey> iterator = set.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
//获取连接
SocketChannel Clientchannel = channel.accept();
//设置非阻塞
Clientchannel.configureBlocking(false);
//注册任务
Clientchannel.register(selector, SelectionKey.OP_READ);
//打印用户上线日志
System.out.println("用户:" Clientchannel.socket().getRemoteSocketAddress() "上线了");
continue;
} else if (key.isReadable()) {
readData(key);
}
}
}
}
//读取信息
public void readData(SelectionKey key) {
SocketChannel channel = null;
try {
//获取当前的信道
channel = (SocketChannel) key.channel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
//>0代表有可读取的内容
if (read > 0) {
String msg = "用户" channel.socket().getRemoteSocketAddress() ":" new String(buffer.array());
//打印信息
System.out.println(msg);
sendToOther(msg, channel);
}
} catch (Exception e) {
System.out.println("用户:" channel.socket().getRemoteSocketAddress() "下线了");
key.cancel();
try {
channel.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
//信息分发
public void sendToOther(String msg, SocketChannel selfSocketChannel) throws Exception {
//获得所有的信道,可以理解为获取所有的用户
Set<SelectionKey> set = selector.keys();
for (SelectionKey key : set) {
Channel channel = key.channel();
//判断这是一个用户,并且不是发信息的那个人
//这个是一个 socketchannel 并且不等价与我们发信息的 selfsocketchannel
if (channel instanceof SocketChannel && channel != selfSocketChannel) {
SocketChannel socketChannel = (SocketChannel) channel;
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
socketChannel.write(buffer);
}
}
}
//启动客户端,等待连接
public static void main(String[] args) throws Exception {
chatServer chatServer = new chatServer();
chatServer.listenClient();
}
客户端
代码语言:javascript复制---------------------初始化------------------------------ 1.连接服务端通道 2.开启选择器 3.注册任务,打印上线日志 --------------------发送信息------------------- 1.向服务端发送信息 --------------------读取信息--------------------- 1.接收服务端分发的消息(自己除外)
//服务端通道
private SocketChannel channel;
// 多路复用选择器
private Selector selector;
public chatClient() throws Exception {
channel = SocketChannel.open();
selector = Selector.open();
SocketAddress address = new InetSocketAddress("127.0.0.1", 6666);
channel.connect(address);
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
System.out.println("用户:" channel.getLocalAddress() "上线了");
}
public void sendData(String msg) {
try {
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
public void readData() throws Exception {
SocketChannel channel = null;
while (true) {
int num = selector.select();
if (num == 0) {
continue;
}
Set<SelectionKey> set = selector.selectedKeys();
Iterator<SelectionKey> iterator = set.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isReadable()) {
try {
//获取当前的信道
channel = (SocketChannel) key.channel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
//>0代表有可读取的内容
if (read > 0) {
String msg = new String(buffer.array());
System.out.println(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws Exception {
final chatClient client = new chatClient();
new Thread() {
@Override
public void run() {
while (true) {
try {
//一直阅读是否有信息
client.readData();
//每一秒阅读一次
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String str = scanner.nextLine();
client.sendData(str);
}
}
案例测试
只需要创建两个类,将客户端和服务端的代码放入IDE就可以启动代码了,小冷保证开箱即用哦~ 客户端想要多开的话,打开这个选项就可以开很多个客户端程序了
效果图
服务端日志
客户端看到的信息