java NIO群聊系统

news2024/11/15 21:43:13

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.coloradmin.cn/o/1534727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用autodl服务器跑模型

1. 租用服务器 本地改模型 服务器 将改进好的、数据集处理好的模型压缩为zip文件上传到阿里云盘打开服务器AUTODL服务器&#xff0c;在主页中选择容器实例 在此位置进行开关机操作&#xff0c;若停止服务器&#xff0c;必须关机&#xff0c;不然会一直扣钱 2. 运行模型 选择…

【Unity】捕捉PC桌面的插件

【背景】 之前介绍了如何用一款名为uWindowCapture的Unity免费插件在Unity的Canvas上展示PC桌面。经过一段时间的使用,本篇继续分享此插件的一些功能和限制。 在此感谢作者Hecomi。 【特征和限制】 一般局域网络环境只能最多达到15帧的帧率,所以别幻想用来窜流游戏或者看电…

【个人记录】CentOS7安装MySQL 5.7和libmysqlclient.so.20

记录 之前使用MariaDB 发现使用的libmysqlclient.so是18版本的&#xff0c;一些程序需要20版本的库&#xff0c;查了一下需要安装5.7以上版本的才有libmysqlclient.so.20&#xff0c;这里简单记录一下怎么安装。 安装MySQL 5.7 Yum源 yum install -y https://repo.mysql.com…

【C++】狗屁不通文章生成器2.0

【C】狗屁不通文章生成器2.0 1 前言2 改进2.1 字词的前后关系2.2 文章生成系统 3 实现(部分)3.1 class wordpair3.1.1 转化为 json3.1.2 添加后缀词3.1.3 选择后缀词 3.2 class createArticle3.2.1文本分割3.2.2生成文章 4演示4.1 wordpair(3x2), 启动词(春天)4.2 wordpair(2x1…

电脑维修的相关资料,有需要的自取

电脑维修的相关资料&#xff0c;有需要的自取。 链接&#xff1a;https://pan.baidu.com/s/1X81sBNAOmomFvug6mK56Bw 提取码&#xff1a;52pj 爆笑幽默段子&#xff1a;电脑出故障了&#xff0c;准备拿去修&#xff0c;结果被女朋 友拦住了。女朋友&#xff1a;“你们男人一定…

ginblog博客系统/golang+vue

ginblog博客系统 前台&#xff1a; 后台&#xff1a; Gitee的项目地址&#xff0c;点击进入下载 注意&#xff1a; 数据库文件导入在model里面&#xff0c;直接导入即可。 admin和front前后台系统记住修改https里的地址为自己的IP地址&#xff1a; front同上。

Doris实战——工商信息查询平台的湖仓一体建设

目录 前言 一、架构1.0&#xff1a;传统Lambda架构 二、OLAP引擎调研 三、架构2.0&#xff1a;数据服务层All in Apache Doris 四、架构 3.0&#xff1a;基于Doris Multi-Catalog的湖仓一体架构 五、实践经验 5.1 引入Merge-on-Write&#xff0c;百亿级单表查询提速近三…

学习vue3第九节(新加指令 v-pre/v-once/v-memo/v-cloak )

1、v-pre 作用&#xff1a;防止编译器解析某个特定的元素及其内容&#xff0c;即v-pre 会跳过当前元素以及其子元素的vue语法解析&#xff0c;并将其保持原样输出&#xff1b; 用于&#xff1a;vue 中一些没有指令和插值表达式的节点的元素&#xff0c;使用 v-pre 可以提高 Vu…

【项目实践】VS配置Qt

文章目录 前言版本使用具体步骤1&#xff09;安装Qt或者添加删除组件2&#xff09;VS安装Qt Visual Studio Tools 如何使用遇到的问题双击ui文件编辑报错 前言 最近因为一个项目&#xff0c;需要使用Qt&#xff0c;本来想使用Python的&#xff0c;但是由于另外一个第三方的库是…

反诈提醒:谨防私人财务、跑分类项目

文章目录 引言I 私人财务的特征II “跑分”的本质III 妥善做好个人账户管理IV 处理非柜面交易限制V 个人账户收款监管规则VI 警惕“手机口”诈骗VII 反诈提醒引言 一切需要你的账户入资和出资的,进行资金中转的都是洗钱。 发现自己身边有人涉嫌买卖个人信息、手机卡、银行卡…

Java设计模式之单例模式(多种实现方式)

虽然写了很多年代码&#xff0c;但是说真的对设计模式不是很熟练&#xff0c;虽然平时也会用到一些&#xff0c;但是都没有深入研究过&#xff0c;所以趁现在有空练下手 这章主要讲单例模式&#xff0c;也是最简单的一种模式&#xff0c;但是因为spring中bean的广泛应用&#…

JVM垃圾回收之内存分配,死亡对象判断方法

Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆。 堆划分为新生代 老生代 永久代。 下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代&#xff0c;中间一层属于老年代&#xff0c;最下面一层属于永久代。 内存分配原则 对象优先在Eden区域分…

算法打卡day11

今日任务&#xff1a; 1&#xff09;239. 滑动窗口最大值 2&#xff09;347.前 K 个高频元素 239. 滑动窗口最大值 题目链接&#xff1a;239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移…

解锁人工智能新境界:大模型工程与架构的深度探索

在当今世界&#xff0c;人工智能(AI)无处不在&#xff0c;它已经悄然改变了我们的生活方式。从自动驾驶汽车到智能音箱&#xff0c;从智能医疗系统到虚拟助手&#xff0c;AI的影子无处不在。而在这一切背后&#xff0c;是一个个巨大的数学模型在默默运转。这些模型就像是我们人…

关于微信/支付宝支付

文章目录 前言一、多个支付怎么统一&#xff1f;二、支付调试支付宝支付注意 微信支付 总结 前言 最近搞了下微信支付&#xff0c;支付宝支付&#xff1b; 一、多个支付怎么统一&#xff1f; 这个很明显&#xff0c;通常直接用设计模式之-- 策略模式&#xff0c;对外公共提出…

把txt、pdf等文件转为一行一行的doccano数据集输入格式

文章目录 doccano 数据集导入简介代码实现代码运行结果代码公开 doccano 数据集导入 在Doccano 导入数据集时&#xff0c;使用TextLine的文件格式&#xff0c;导入的文件需要为一行一行文本的数据格式&#xff0c;每一行文本在导入Doccano后就是一条数据。 简介 主要工作说明…

Android Launcher开发注意事项

在开发Android Launcher时&#xff0c;需要关注性能、用户体验、权限管理、兼容性等方面&#xff0c;同时遵循相关的开发者政策和最佳实践。有几个重要的注意事项&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎…

iStoreOS使用体验

iStoreOS是OpenWRT改版而来的易用的软路由系统 我们知道OpenWRT还是有一定的上手难度的&#xff0c;对于小白要玩好openwrt就需要学习openwrt的扩容 和一些插件的安装&#xff0c;问题的拍错&#xff0c;需要一定的linux系统基础 而iStoreOS这个系统对于小白非常的优化 首先他…

【随笔】Git -- 解决提交时本地与目标分支不一致导致提交失败(三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

教学管理与教育研究知网教育类G4期刊投稿发表简介

《教学管理与教育研究》杂志是由国家新闻出版总署批准&#xff0c;教育部主管&#xff0c;北京师范大学主办&#xff0c;北京师范大学出版社&#xff08;集团&#xff09;有限公司出版的教育类G4期刊。期刊面向全国学校教育的发展管理、教学研究、科研创新和师资培养等领域&…