java NIO群聊系统

demo要求:

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

2)实现多人群聊

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

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

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

以下代码:

服务器端实现的代码:

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();
	}

}

客户端代码如下:

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,多开几个客户端实现。实现如下截图:

若有问题请留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/473635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Open World Object Detection in the Era of Foundation Models

Open World Object Detection in the Era of Foundation Models 摘要介绍相关工作开放词汇物体检测开放世界目标检测类无关的目标检测3.真实世界目标检测基准3.1 数据集细节3.2 基准架构3.3 什么是一个未知对象4. 利用基准模型用于开放世界目标检测4.1 背景4.2 属性生成4.3 属性…

汽车价格的回归预测项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 问题描述 汽车价格预测是一个旨在预估二手车市场中汽车售价的问题。这个问题涉及到分析各种影响汽车价格的因素&#xff0c;如品牌、车龄、性能…

Git原理与使用(一)

目录 前言 版本控制器 Linux下的Git的安装 Git的基本操作 创建Git本地仓库 配置Git 工作区、暂存区、版本库 添加与提交 查看.git文件 前言 我们可能要写多个文档对一个产品进行描述&#xff0c;但是一般情况下我们可能要写多个文档&#xff0c;比如&#xff1a; 初…

图片编辑器中实现文件上传的三种方式和二进制流及文件头校验文件类型

背景 最近在 vue-design-editor 开源项目中实现 psd 等多种文件格式上传解析成模板过程中, 发现搞定设计文件上传没有使用 input 实现文件上传, 所以我研究了一下相关技术, 总结了以下三种文件上传方法 input 文件选择window.showOpenFilePicker 和 window.showDirectoryPicke…

Follow-Your-Click——点选图像任意区域对象使用短提示语即可生成视频

简介 “I2V”&#xff08;图像到视频生成&#xff09;旨在将静态图像转换为具有合理动作的动态视频剪辑&#xff0c;在电影制作、增强现实和自动广告等领域有广泛应用。然而&#xff0c;现有的I2V方法存在一些问题&#xff0c;例如缺乏对图像中需要移动的部分的精准控制&#…

RAFT: Adapting Language Model to Domain Specific RAG

预备知识 RAG介绍一文搞懂大模型RAG应用&#xff08;附实践案例&#xff09; - 知乎 (zhihu.com) RAG的核心理解为“检索生成” 检索&#xff1a;者主要是利用向量数据库的高效存储和检索能力&#xff0c;召回目标知识&#xff1b; 生成&#xff1a;利用大模型和Prompt工程…

Android Studio实现内容丰富的安卓校园公告助手

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 093校园助手 1.开发环境 android stuido3.6 jak1.8 eclipse mysql tomcat 2.功能介绍 具体往下看第三节&#xff0c;功能截图 安卓端&#xff1a; 1.注册登录 2.校园公告列表…

微信小程序订阅消息(一次性订阅消息)

1、准备工作 登录微信公众平台–>订阅消息–>在公共模板库中选中一个模版–>将模版id复制&#xff0c;前后端都需要。 点击详情–>查看详细内容模版 复制给后端 2、相关api的使用 前端使用&#xff1a;wx.requestSubscribeMessage wx.openSetting wx.getSetti…

[Qt学习笔记]QPushButton点击事件和长按事件使用功能

1、背景介绍 在使用QPushButton中&#xff0c;一般都在UI界面直接右键添加槽函数进入代码&#xff0c;很少去分析每个触发事件的功能&#xff0c;比如需要通过长按按钮来触发相应的操作&#xff0c;这里点击信号不可以达到预期的效果。 2、功能分析 首先分析QPushButton的点…

13014.Linux小知识点记录

文章目录 1 工具记录1.1 串口传输文件 1 工具记录 1.1 串口传输文件 打开SecureCRT的串口&#xff0c;执行rx 文件名指令从桌面将可执行文件&#xff0c;拖拽到串口终端即可

计算机三级——网络技术(综合题第二题)

路由器工作模式 用户模式 当通过Console或Telnet方式登录到路由器时&#xff0c;只要输入的密码正确&#xff0c;路由器就直接进入了用户模式。在该模式下&#xff0c;系统提示符为一个尖括号(>)。如果用户以前为路由器输入过名称&#xff0c;则该名称将会显示在尖指号的前…

opengl日记10-opengl使用多个纹理示例

文章目录 环境代码CMakeLists.txt文件内容不变。fragmentShaderSource.fsvertexShaderSource.vsmain.cpp 总结 环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.…

【Hadoop】Hadoop 编译源码

目录 为什么要源码编译Hadoop 编译源码1前期工作准备2jar 包安装2.1安装 Maven2.2安装 ant2.3安装 glibc-headers 和 g2.4安装 make 和 cmake2.5安装 protobuf2.6安装 openssl 库2.7安装 ncurses-devel 库 3编译源码3.1解压源码到 /opt/ 目录3.2 进入到 hadoop 源码主目录 /opt…

课时70:流程控制_for循环_嵌套循环

2.4.4 嵌套循环 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习。 基础知识 简介 这里的嵌套实践&#xff0c;与选择语句的嵌套实践基本一致&#xff0c;只不过组合的方式发生了一些变化。常见的组合样式如下&#xff1a;for嵌套for语句for …

【Android】【Bluetooth Stack】蓝牙电话本协议分析(超详细)

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【蓝牙协议栈】专栏会持续更新中.....敬请期待&#xff01; 目录 1. 协议简述 1.1 PBAP…

Qt笔记 事件处理_鼠标事件

什么是事件&#xff1f; 点击鼠标左键&#xff0c;双击鼠标左键&#xff0c;鼠标来回移动&#xff0c;按下键盘按钮&#xff0c;这些都是事件。 那么事件的响应机制是什么样的呢&#xff1f; 首先main函数中有一个QApplication&#xff0c;其作用是创建一个应用程序对象&…

11种创造型设计模式(下)

观察者模式 我们可以比喻观察者模式是一种类似广播的设计模式 介绍 观察者模式&#xff1a;对象之间多对一依赖的一种设计方案&#xff0c;被依赖的对象是Subject&#xff0c;依赖的对象是Observer&#xff0c;Subject通知Observer变化。 代码 说明&#xff1a; WeatherStat…

手撕算法-判断是不是二叉搜索树

题目描述 分析 二叉搜索树的特性就是中序遍历是递增序。既然是判断是否是二叉搜索树&#xff0c;那我们可以使用中序递归遍历。只要之前的节点是二叉树搜索树&#xff0c;那么如果当前的节点大于上一个节点值那么就可以向下判断。 如果有出现当前的节点小于上一个节点值&…

Host xxx1 has more disk space than database expected (xxx2 GB > xxx3 GB)

在nova-compute.log中有时会看到日志“Host xxx1 has more disk space than database expected (xxx2 GB &#xff1e; xxx3 GB)”类似日志。 查看下源码&#xff0c;如下&#xff1a; 分析&#xff1a; 定时任务更新主机资源到内存或者对象中&#xff0c;当执行检测的定时任务…

颠覆传统:Web3如何塑造未来的数字经济

引言 近年来&#xff0c;随着数字化时代的到来&#xff0c;互联网已经成为人们生活中不可或缺的一部分。然而&#xff0c;随着技术的不断发展和社会的不断变迁&#xff0c;传统的Web2模式逐渐显露出一些弊端&#xff0c;如数据垄断、隐私泄露等问题&#xff0c;这促使人们寻求…
最新文章