Java Netty个人对个人私聊demo

一、demo要求

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

2)实现单人对单人聊

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

4)客户端:通过channel可以无阻塞发送消息给对应用户(有服务器转发得到)

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

二、服务器代码

package com.tfq.netty.netty.personalchat;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @author: fqtang
 * @date: 2024/04/03/13:39
 * @description: 描述
 */
public class PersonChatServer {
	//监听端口
	private int port;

	public PersonChatServer(int port) {
		this.port = port;
	}

	/**
	 * 处理客户端的请求
	 */
	public void run() throws InterruptedException {

		//创建两个线程组
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
		EventLoopGroup workerGroup = new NioEventLoopGroup(8);
		try {

			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(bossGroup, workerGroup)
				.channel(NioServerSocketChannel.class)
				.option(ChannelOption.SO_BACKLOG, 128)
				.childOption(ChannelOption.SO_KEEPALIVE, true)
				.childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) throws Exception {
						//获取到pipeline
						ChannelPipeline pipeline = ch.pipeline();
						//向pipeline加入一个解码器
						pipeline.addLast("decoder", new StringDecoder());
						//向pipeline加入一个编码器
						pipeline.addLast("encoder", new StringEncoder());
						//加入自己的业务处理handler
						pipeline.addLast(new PToPChatServerHandler());
					}
				});

			System.out.println("netty 服务器启动");
			ChannelFuture channelFuture = serverBootstrap.bind(port)
				.sync();
			channelFuture.channel()
				.closeFuture()
				.sync();
		}finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		try {
			new PersonChatServer(7888).run();
		} catch(InterruptedException e) {
			throw new RuntimeException(e);
		}
	}

}

服务器端的Handler

package com.tfq.netty.netty.personalchat;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * @author: fqtang
 * @date: 2024/04/03/13:53
 * @description: 个人对个人聊天
 */
public class PToPChatServerHandler extends SimpleChannelInboundHandler<String> {

	//使用一个hashmap管理账户
	private static HashMap<User, Channel> userChannels = new HashMap<>();

	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	/**
	 * 表示连接建立,一旦连接,第一个被执行
	 * 将当前channel加入到 channelGroup
	 *
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
		Channel channel = ctx.channel();
		//将该客户加入聊天的信息推送给其他在线的客户端
		userChannels.put(new User(channel.hashCode() + "", "client" + new Random().nextInt(100)), channel);

		userChannels.forEach((user, uChannel) -> {
			channel.writeAndFlush(sdf.format(new Date()) + ",现在有的[客户]:" + uChannel.remoteAddress() + "加入聊天.请选择一个客户Id进行私聊......."+uChannel.hashCode()+"\n");
		});
	}

	/**
	 * 表示channel 处于活动上线,提示 xx上线
	 *
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println(ctx.channel()
			.remoteAddress() + " 在[ " + sdf.format(new Date()) + " ] 上线了~");
	}

	/**
	 * 表示channel 处于离线,提示 xx离线
	 *
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println(ctx.channel()
			.remoteAddress() + "在 " + sdf.format(new Date()) + " 离线了~");
	}

	/**
	 * 断开连接,将XX客户离开信息推送给当前在线的客户
	 *
	 * @param ctx
	 * @throws Exception
	 */
	@Override
	public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
		Channel channel = ctx.channel();
		userChannels.keySet().removeIf(key ->key.getId().equals(String.valueOf(channel.hashCode())));
		System.out.println("移除通道,当前账户总数量:" + userChannels.size());
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
		//获取当前通道channel
		Channel channel = ctx.channel();

		userChannels.forEach((user, uChannel) -> {
			channel.writeAndFlush(sdf.format(new Date()) + ",现在有的[客户]:" + uChannel.remoteAddress() + ".请选择一个客户Id进行私聊....." + user.getId() + "..\n");
		});
		String[] start = msg.split("id_");
		String[] end = start[1].split(":");
		String targetId = end[0];
		//私对私发信息聊天
		userChannels.forEach((user, uChannel) -> {
			if(targetId.equals(user.getId()) && uChannel != channel) {
				//把当前通道的消息转发给选定通道的客户
				uChannel.writeAndFlush("[客户]" + channel.remoteAddress() + "在 【" + sdf.format(new Date()) + "】 发送了消息:" + end[1] + " \n");
			}
		});

	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		//关闭通道
		ctx.close();
		System.out.println("在 【" + sdf.format(new Date()) + "】 关闭通道,客户总数:" + userChannels.size());
	}
}

三、客户端代码

package com.tfq.netty.netty.personalchat;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.Scanner;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @author: fqtang
 * @date: 2024/04/04/7:54
 * @description: 描述
 */
public class PersonChatClient {

	private final String host;
	private final int port;

	public PersionChatClient(String host, int port) {
		this.host = host;
		this.port = port;
	}

	public void run() {
		EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(eventLoopGroup)
				.channel(NioSocketChannel.class)
				.handler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) throws Exception {
						//得到pipeline
						ChannelPipeline pipeline = ch.pipeline();
						//加入相关handler的解码器
						pipeline.addLast("decoder", new StringDecoder());
						//加入相关handler的编码器
						pipeline.addLast("encoder", new StringEncoder());
						//加入自定义的handler
						pipeline.addLast(new ChatClientHandler());
					}
				});
			//连接服务器返回通道
			ChannelFuture channelFuture = bootstrap.connect(host, port)
				.sync();
			Channel channel = channelFuture.channel();

			if(channelFuture.isSuccess()) {
				System.out.println("本地ip:"+channel.localAddress()+",连接服务器ip: "+channel.remoteAddress() + " 成功");
			}
			Scanner scanner = new Scanner(System.in);
			while(scanner.hasNextLine()) {
				channel.writeAndFlush(scanner.nextLine());
			}

			//给关闭监听进行通道
			channel.closeFuture()
				.sync();

		} catch(InterruptedException e) {
			throw new RuntimeException(e);
		} finally {
			eventLoopGroup.shutdownGracefully();
		}

	}


	public static void main(String[] args) {
		new PersonChatClient("127.0.0.1", 7888).run();
	}
}

客户端handler

package com.tfq.netty.netty.personalchat;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @author: fqtang
 * @date: 2024/04/04/8:16
 * @description: 描述
 */
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
		System.out.println("收到服务器端转发的消息:"+msg.trim());
	}
}

四、运行服务端程序(PersonChatServer.java)和客户端程序(PersonChatClient)

若大家运行有问题请留言。

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

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

相关文章

【游戏逆向】游戏全屏捡物的实现

0x0前言&#xff1a; 在角色对战类中&#xff0c;拾取怪物掉落的装备是一项必备的工作&#xff0c;由于装备位置掉落的不确定性&#xff0c;玩家想要拾取离角色距离较远的装备需要一定的时间&#xff0c;这一段时间往往会影响游戏的评分或是玩家的心态&#xff0c;基于此&…

【经典算法】LeetCode25:K 个一组翻转链表(Java/C/Python3,Hard)

#算法 目录 题目描述思路及实现方式一&#xff1a;递归思路代码实现Java 版本C 语言版本Python3 版本 复杂度分析 方式二&#xff1a;迭代和原地反转思路代码实现Java 版本C 语言版本Python3 版本 复杂度分析 总结相似题目 标签&#xff1a;链表、递归 题目描述 给你链表的头…

接口日志配置表

表&#xff1a;ZTALL_LOGCFG ZE_ENABLED XFIELD ZE_LOGGING XFIELD ZE_NOTRANS XFIELD ZE_IFURL TEXT255 MANDT MANDT CLNT 3 0 0 客户端 SYSID SYST_SYSID CHAR 8 0 0 ABAP 系统字段&#xff1a;SAP 系统的名称 IFSNR ZE_IFSNR CHAR 30 0 0 接口编号(系统ID流水号…

线程安全--深入探究线程等待机制和死锁问题

꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转…

9亿用户、估值300亿美元,「暗黑版微信」决定上市

自 2017 年以来&#xff0c;Telegram 的创始人帕维尔•杜罗夫&#xff08;Pavel Durov&#xff09;就从没有接受过任何公共采访。直到不久前&#xff0c;这位神秘的亿万富翁接受了英国《金融时报》的采访&#xff0c;他向外界释放出了一个信号——Telegram 正在谋求 IPO。 根据…

Springboot相关知识-图片描述(学习笔记)

学习java过程中的一些笔记&#xff0c;觉得比较重要就顺手记录下来了~ 目录 一、前后端请求1.前后端交互2.简单传参3.数组集合传参4.日期参数5.Json参数6.路径参数7.响应数据8.解析xml文件9.统一返回类10.三层架构11.分层解耦12.Bean的声明13.组件扫描14.自动注入 一、前后端请…

《Java面试自救指南》(专题三)数据库

文章目录 一条sql语句的查询流程有哪些数据库存储引擎&#xff0c;各自的区别数据库的三大范式事务的四大特性&#xff08;含隔离级别&#xff09;MySQL四种隔离机制的底层实现&#xff08;如何解决幻读 &#xff09;MySQL有哪几种锁&#xff0c;分别怎么实现数据库中有哪些索引…

Leetcode-Hot 100题目分类

哈希 &#xff08;以空间换时间&#xff09; 1 两数之和 原始的暴力破解的方法&#xff1a; class Solution {public int[] twoSum(int[] nums, int target) {/** 暴力破解的方法 */int[] result new int[2];int length nums.length;for(int i 0;i<length;i){for(int j…

企业计算机服务器中了locked勒索病毒怎么办,locked勒索病毒解密流程步骤

网络技术的不断发展为企业的生产运营提供了极大便利&#xff0c;也让企业的生产效率大大提高&#xff0c;但网络是一把双刃剑&#xff0c;给给企业的数据安全问题带来严重威胁。近期&#xff0c;云天数据恢复中心接到浙江某商贸公司的求助&#xff0c;企业计算机服务器遭到了lo…

使用 Azure OpenAI、知识图 FalkorDB 和 LlamaIndex 构建医疗保健聊天机器人

原文地址&#xff1a;building-a-mental-health-qa-chatbot-using-falkordb-knowledge-graph-and-llamaindex 介绍 知识图谱是一种将数据转换为机器可以理解的知识的模型&#xff0c;它比传统数据管理系统更能够捕获数据的语义性质和含义。知识图谱通过结构化地表示实体、属性…

docker部署小霸王游戏

下载镜像 docker pull registry.cn-beijing.aliyuncs.com/wuxingge123/jsnes:1.0.0docker-compose部署 vim docker-compose.yml version: 3 services:jsnes:container_name: jsnesimage: registry.cn-beijing.aliyuncs.com/wuxingge123/jsnes:1.0.0ports:- 8082:80restart: …

ArduPilot无人船(车)故障保护设置

故障保护 无人船&#xff08;车&#xff09;支持三种故障保护机制&#xff0c;如下所述。 一、遥控器故障保护&#xff08;也称油门故障保护&#xff09; 如果用户的遥控器和无人船&#xff08;车&#xff09;上的接收机之间的连接丢失至少FS_TIMEOUT秒&#xff0c;则会触发此…

07 | Swoole 源码分析之 Channel 通道模块

原文首发链接&#xff1a;Swoole 源码分析之 Channel 通道模块 大家好&#xff0c;我是码农先森。 引言 通道&#xff0c;用于协程间通讯&#xff0c;支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。 通道与 PHP 的 Array 类似&#xff0c;仅占用内存&am…

中科大发布Agent-FLAN,微调提升Agent能力

随着大语言模型&#xff08;LLMs&#xff09;在各种自然语言处理任务中取得巨大成功&#xff0c;将这些模型作为智能代理&#xff08;agents&#xff09;使用时&#xff0c;它们与基于API的模型相比仍有不小的差距。如何将代理能力有效地整合到通用的LLMs中&#xff0c;成为了一…

单片机为什么还在用C语言编程?

单片机产品的成本是非常敏感的。因此对于单片机开发来说&#xff0c;最重要的是在极其有限的ROM和RAM中实现最多产品的功能。或者反过来说&#xff0c;实现相同的产品功能&#xff0c;所需要的ROM和RAM越小越好&#xff0c;在开始前我有一些资料&#xff0c;是我根据网友给的问…

C++ 【原型模式】

简单介绍 原型模式是一种创建型设计模式 | 它使你能够复制已有对象&#xff0c;客户端不需要知道要复制的对象是哪个类的实例&#xff0c;只需通过原型工厂获取该对象的副本。 以后需要更改具体的类或添加新的原型类&#xff0c;客户端代码无需改变&#xff0c;只需修改原型工…

Linux(CentOS7)部署 y-api 接口管理平台

目录 前言 前置环境 mongodb node 安装 y-api 部署页面 启动 y-api 基本使用教程 前言 前后端分离时代&#xff0c;前后端通过接口文档来协作开发项目。一般开发过程中&#xff0c;由后端先编写接口文档&#xff0c;然后交付给前端&#xff0c;这时候前后端都根据这个…

C# 委托的基础应用

一、Action 和 Func 的使用。 二、自定义委托&#xff1a; 完整的使用代码示例&#xff1a; 三、委托的一般使用 模板方法&#xff1a; 回调方法&#xff0c;在模板方法的基础上进行添加。

刷题之Leetcode209题(超级详细)

209.长度最小的子数组 力扣题目链接(opens new window)https://leetcode.cn/problems/minimum-size-subarray-sum/ 给定一个含有 n 个正整数的数组和一个正整数 s &#xff0c;找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组&#xff0c;并返回其长度。如果不存在符合条…

EPSON高精度导航陀螺仪XV7001BB

随着道路交通的不断发展&#xff0c;以及城市道路的不断更新&#xff0c;以前走过的路早已物是人非&#xff0c;越来越多的驾驶者不得不借助导航系统才能到达目的地&#xff0c;导航成为了出行必不可少的功能。目前的导航都是基于GPS信号定位&#xff0c;再结合导航内部的地图软…