前面我们一起学习了NIO的基础知识,下面将用我们学习到的知识实现一个简单的群聊系统。
需求:
- 编写一个 NIO 群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞)
- 实现多人群聊
- 服务器端:可以监测用户上线,离线,并实现消息转发功能
- 客户端:通过 channel 可以无阻塞发送消息给其它所有用户,同时可以接受其它用户发送的消息(有服务器转发
得到)
示意图分析:
详细设计如下:
- 先编写服务器端
1.1. 服务器启动并监听 6667
1.2 服务器接收客户端信息,并实现转发 [处理上线和离线] - 编写客户端
2.1 连接服务器
2.2 发送消息
2.3 接收服务器消息
代码实现如下:
GroupChatServer:
package com.mylove.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class GroupChatServer {
//定义属性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
public GroupChatServer() {
try {
//得到选择器
selector = Selector.open();
listenChannel = ServerSocketChannel.open();
//绑定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞模式
listenChannel.configureBlocking(false);
//将该listenChannel注册到selector上,关注accept事件
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e){
e.printStackTrace();
}
}
public void listen(){
try{
//循环处理
while (true){
int count = selector.select();
if(count > 0 ){
//遍历得到selectionKey集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
//监听到accept
if(selectionKey.isAcceptable()){
SocketChannel socketChannel = listenChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
System.out.println(socketChannel.getRemoteAddress() + " 上线 ");
}
//通道READ事件
if(selectionKey.isReadable()){
//处理消息
readData(selectionKey);
}
iterator.remove();
}
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
{
}
}
}
/**
* 读取消息
* @param key
*/
private void readData(SelectionKey key){
SocketChannel channel = null;
try{
//得到Channel
channel = (SocketChannel) key.channel();
//创建Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
//根据count的值做处理
if(count > 0 ){
String msg = new String(buffer.array());
System.out.println("from 客户端:" + msg);
//向其他客户端转发消息
sendInfoToOtherClient(msg,channel);
}
}catch (IOException e){
try {
System.out.println(channel.getRemoteAddress() + " 离线了。。。");
//取消注册
key.cancel();
//关闭通道
channel.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
/**
* 转发消息给其他其他客户(通道)
* @param msg
* @param self
*/
private void sendInfoToOtherClient(String msg , SocketChannel self) throws IOException {
System.out.println("服务器转发消息中。。。");
for(SelectionKey key : selector.keys()){
Channel targetChannel = key.channel();
if(targetChannel instanceof SocketChannel && targetChannel != self){
SocketChannel dest = (SocketChannel) targetChannel;
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
dest.write(byteBuffer);
}
}
}
public static void main(String[] args) {
GroupChatServer chatServer = new GroupChatServer();
chatServer.listen();
}
}
GroupChatClient:
package com.mylove.nio.groupchat;
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.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
public class GroupChatClient {
//定义相关属性
private final String HOST = "127.0.0.1";
private final int PORT = 6667;
private Selector selector;
private SocketChannel socketChannel;
private String username;
//构造器完成初始化工作
public GroupChatClient() throws IOException {
selector = Selector.open();
//连接服务器
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",PORT));
//设置非阻塞
socketChannel.configureBlocking(false);
//将channel注册到selector上
socketChannel.register(selector, SelectionKey.OP_READ);
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ok....");
}
/**
* 向服务器发送消息
* @param info
*/
public void sendInfo(String info){
info = username + "说:" + info;
try{
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 从服务器读取回复的消息
*/
public void readInfo(){
try{
int readChannels = selector.select();
if(readChannels > 0 ){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
//得到相关通道
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取
socketChannel.read(buffer);
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
iterator.remove();
}
}else{
// System.out.println("没有可用的通道");
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
GroupChatClient chatClient = new GroupChatClient();
//启动一个线程每隔3秒从服务器端读取发送的数据
new Thread(){
public void run(){
while(true){
chatClient.readInfo();
try{
Thread.currentThread().sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}.start();
// 发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}
多次启动client,并发送消息效果如下: