java NIO群聊系统

demo要求:

1)编写一个NIO群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞)

2)实现多人群聊

3)服务器端:可以监测用户上线,离线,并实现消息转发功能。

4)客户端:通过channel可以无阻塞发送消息给其他所有用户(客户端),同时可以接受其他用户发送的消息(由服务器转发得到)

5)目的:进一步理解NIO非阻塞网络编程机制。

从Netty的Reactor模式看demo是单Reactor单线程模式。其实Netty是在NIO1.0的基础上封装了复杂的调用操作,解决了JDK1.6的NIO臭名昭著的Epoll Bug,方便程序员进行网络编程,提升效率。

以下代码:

服务器端实现的代码:

java 复制代码
package com.tfq.netty.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @author: fqtang
 * @date: 2024/03/19/11:22
 * @description: 服务器端
 */
public class GroupChatServer {

	//定义属性
	private ServerSocketChannel listenChannel;
	private Selector selector;
	private static final int PORT = 6667;

	//构造器
	public GroupChatServer() {
		try {
			//得到选择器
			this.selector = Selector.open();
			//获取监听通道
			this.listenChannel = ServerSocketChannel.open();
			//绑定端口
			this.listenChannel.socket()
				.bind(new InetSocketAddress(PORT));
			//设置通道为非阻塞
			this.listenChannel.configureBlocking(false);
			//将该listenChannel注册到selector
			this.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 key = iterator.next();
						//监听到accept
						if(key.isAcceptable()) {
							SocketChannel sc = listenChannel.accept();
							sc.configureBlocking(false);
							//将sc注册到Selector
							sc.register(selector, SelectionKey.OP_READ);
							System.out.println(sc.getRemoteAddress() + " 上线");
						}

						if(key.isReadable()) {//通道发送read事件,即通道是可读的状态
							//处理读
							readDate(key);
						}
						//当前的Key删除,防止重复处理
						iterator.remove();
					}
				}
			}
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			try {
				listenChannel.close();
			} catch(IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 读取客户端的消息
	 */
	private void readDate(SelectionKey key) {
		//定义一个SocketChannel
		SocketChannel channel = null;
		try {
			//得到channel
			channel = (SocketChannel) key.channel();
			//创建buffer
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			int count = channel.read(buffer);
			if(count > 0) {
				//把缓冲区的数据转成字符串
				String msg = new String(buffer.array());
				System.out.println("from 客户端发送的消息:" + msg);

				//向其他的客户端转发消息
				sendMsgToOtherClients(channel, msg);
			}
		} catch(Exception e) {
			try {
				System.out.println(channel.getRemoteAddress() + "已离线了");
				//取消注册
				key.cancel();
				//关闭通道
				channel.close();
			} catch(IOException ex) {
				throw new RuntimeException(ex);
			}
		}

	}

	/**
	 * 转发消息给其他通道,排除自己
	 * @param selfChannel
	 * @param msg
	 */
	private void sendMsgToOtherClients(SocketChannel selfChannel,String msg) throws IOException {
		System.out.println("服务器转发消息中.....");
		//遍历 所有注册到selector 上的SocketChannel,并排除seflChannel
		for(SelectionKey key: selector.keys()){
			//通过key 取出对应的SocketChannel
			Channel targetChannel = key.channel();
			//排除自己
			if(targetChannel instanceof  SocketChannel && targetChannel != selfChannel ){
				//转型
				SocketChannel dest = (SocketChannel) targetChannel;
				//将数据存储到buffer。写入buffer
				ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
				//将buffer 的数据写入通道
				dest.write(byteBuffer);
			}
		}
	}

	public static void main(String[] args) {
		GroupChatServer groupChatServer = new GroupChatServer();
		groupChatServer.listen();
	}

}

客户端代码如下:

java 复制代码
package com.tfq.netty.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;

/**
 * @author: fqtang
 * @date: 2024/03/19/14:14
 * @description: 描述
 */
public class GroupChatClient {

	//定义属性
	private final String HOST = "127.0.0.1";
	private final int PORT = 6667;
	private SocketChannel sc;
	private Selector selector;
	private String userName;

	public GroupChatClient() throws IOException {
		selector = Selector.open();
		//连接服务器
		sc = sc.open(new InetSocketAddress(HOST, PORT));
		//设置非阻塞
		sc.configureBlocking(false);
		//将channel注册到selector
		sc.register(selector, SelectionKey.OP_READ);
		//得到username
		userName = sc.getLocalAddress()
			.toString()
			.substring(1);
		System.out.println(userName + " is ok.....");
	}

	/**
	 * 向服务器发送消息
	 *
	 * @param info
	 */
	public void sendMsg(String info) {
		info = userName + " 说: " + info;
		try {
			sc.write(ByteBuffer.wrap(info.getBytes()));
		} catch(IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 读取服务器端的数据
	 */
	public void readMsg() {
		try {
			int readChannel = selector.select();
			if(readChannel > 0) {//有可用通道
				Iterator<SelectionKey> iterator = selector.selectedKeys()
					.iterator();
				while(iterator.hasNext()) {
					SelectionKey key = iterator.next();
					if(key.isReadable()) {
						//得到相关通道
						SocketChannel socketChannel = (SocketChannel) key.channel();
						//得到一个Buffer
						ByteBuffer buffer = ByteBuffer.allocate(1024);
						//读取
						socketChannel.read(buffer);

						System.out.println("读取数据:" + new String(buffer.array()));
					}
				}
				//删除当前的selectionKey,防止重复操作,若不清空,其他客户端收到不到最新消息数据
				iterator.remove();
			}
		} catch(IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException {
		GroupChatClient groupChatClient = new GroupChatClient();

		//启动一个线程,每隔3秒读取发送的数据
		new Thread() {
			public void run() {
				while(true){
					groupChatClient.readMsg();
					try {
						Thread.sleep(3000);
					} catch(InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();

		//发送数据给服务器端
		Scanner scanner =  new Scanner(System.in);
		while(scanner.hasNextLine()){
			String s = scanner.nextLine();
			groupChatClient.sendMsg(s);
		}
	}

}

通过idea运行GroupChatClient.java,多开几个客户端实现。实现如下截图:

**总结:**服务器端用一个线程通过多路复用器搞定所有的IO操作(包括连接、读、写等),编码简单,清晰明了,但是如果客户端连接数量较多,将无法支撑。要使用单Reactor多线程解决。

若有问题请留言。

相关推荐
我喜欢山,也喜欢海30 分钟前
Java和go在并发上的表现为什么不一样
java·python·golang
Wenzar_1 小时前
**零信任架构下的微服务权限控制:用Go实现基于JWT的动态访问策略**在现代云原生环境中,
java·python·微服务·云原生·架构
zhangfeng11331 小时前
多台服务器同时训练llamfactory 大语言模型 国家超算中心 Slurm 是目前全球最主流的开源、高性能计算(HPC)集群资源管理与作业调度系统
服务器·语言模型·开源
不会写DN2 小时前
其实跨域问题是后端来解决的? CORS
服务器·网络·面试·go
爱学习的小囧8 小时前
ESXi 8.0 原生支持 NVMe 固态硬盘吗?VMD 配置详解教程
linux·运维·服务器·esxi·esxi8.0
lUie INGA8 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
坚持就完事了8 小时前
Linux中的变量
linux·运维·服务器
geBR OTTE8 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端
Porunarufu9 小时前
博客系统UI自动化测试报告
java
Cat_Rocky9 小时前
利用Packet Tracer网络实验
linux·运维·服务器