Java Netty个人对个人私聊demo

news2024/12/24 21:30:30

一、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)

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

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

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

相关文章

【新手上路】C#联合Halcon第一个demo搭建

前言 学习Halcon目的是能够利用C#封装成一个视觉的上位机应用配合机器人或者过程控制来提高生产的效率&#xff0c;尤其是在检测外观和定位方面的应用。现在我们就来搭建第一个demo。让他们能够跑起来&#xff01; Halcon方面 打开Halcon软件&#xff0c;然后先随便写一个代…

ADW310 导轨式单相无线计量仪表-安科瑞黄安南

ADW310 无线计量仪表主要用于计量低压网络的有功电能&#xff0c;具有体积小、精度高、功能丰富等优点&#xff0c;并且可 选通讯方式多&#xff0c;可支持 RS485 通讯和 Lora、4G 等无线通讯方式&#xff0c;增加了外置互感器的电流采样模式&#xff0c;从而方便 用户在不同场…

uniapp使用npm命令引入font-awesome图标库最新版本

uniapp使用npm命令引入font-awesome图标库最新版本 图标库网址&#xff1a;https://fontawesome.com/search?qtools&or 命令行&#xff1a; 引入 npm i fortawesome/fontawesome-free 查看版本 npm list fortawesome在main.js文件中&#xff1a; import fortawesome/fo…

想在小红书写出数据分析类的爆文?带你分析爆文的写作思路

一、小红书运营分析背景介绍 在如今社交媒体的浪潮中&#xff0c;小红书、抖音、知乎等平台的流量如同滚滚长江&#xff0c;吸引了无数公司和品牌前来淘金。对于想要推广公司的产品和主营业务而言&#xff0c;如何在这些平台上脱颖而出&#xff0c;成为了一大难题。 数据分析&a…

Java文件流操作

一、文件创建和删除 public static void main(String[] args) throws IOException {File file new File("..\\hello-world.txt");//..表示在上机目录下创建hello-world.txtSystem.out.println(file.getPath());//返回当前相对路径System.out.println(file.getCanoni…

小型案例(acl,nat,dns,dhcp,静态路由)

实验目录&#xff1a;内网互通&#xff0c;pc不可以访问外网&#xff0c;server2可以通过外网访问&#xff08;nat技术&#xff09;&#xff0c;pc2&#xff0c;和pc3可以访问外网 拓扑图如下 配置信息如图&#xff0c;pc1~3 和server2 对应vlan分别是10&#xff0c;20&#…

Matlab 修改图例顺序

对于使用 .m 文件绘制的图片&#xff0c;可以修改程序中图例的顺序来改变图片的图例。如果图片所对应的 .fig 文件已经存在&#xff0c;而且不便修改源程序&#xff0c;则可以通过如下方式来修改图例&#xff1a; step 1: 打开fig文件&#xff0c;然后点击绘图浏览器 step 2&…

Qt实现无边框圆角窗口

我们在使用QDialog的时候许多场景下都不需要默认的标题栏&#xff0c;这时候我们需要设置他的标志位。 this->setWindowFlags(Qt::FramelessWindowHint);由于现代的窗口风格&#xff0c;我们一般会设置窗口为圆角边框的样式&#xff0c;我们可以使用qss的方式来进行设置。 …

WebAPI(一)之DOM操作元素属性和定时器

webAPI之DOM操作元素属性和定时器 介绍概念DOM 树DOM 节点document 获取DOM对象操作元素内容操作元素属性常用属性修改控制样式属性操作表单元素属性自定义属性 间歇函数今日单词 了解 DOM 的结构并掌握其基本的操作&#xff0c;体验 DOM 的在开发中的作用 知道 ECMAScript 与 …

鱼骨图功能实现

dom: <div class="module-content"><div class="title"><span>[</span><p>鱼骨图</p><span>]</span></div><div class="line-mian"></div><div :ref="module + i&q…

Francek Chen 的128天创作纪念日

目录 Francek Chen 的128天创作纪念日机缘收获日常成就憧憬 Francek Chen 的128天创作纪念日 Francek Chen 的个人主页 机缘 不知不觉的加入CSDN已有两年时间了&#xff0c;最初我第一次接触CSDN技术社区是在2022年4月的时候&#xff0c;通过学长给我们推荐了几个IT社区平台&a…

Redis数据库——主从、哨兵、群集

目录 前言 一、主从复制 1.基本原理 2.作用 3.流程 4.搭建主动复制 4.1环境准备 4.2修改主服务器配置 4.3从服务器配置&#xff08;Slave1和Slave2&#xff09; 4.4查看主从复制信息 4.5验证主从复制 二、哨兵模式——Sentinel 1.定义 2.原理 3.作用 4.组成 5.…

数字逻辑分析仪初体验

为啥会用到这玩意儿&#xff0c;要从一个荒诞的需求开始。想在市面上找一款特别低空飞行的监控&#xff0c;而且不想它一直开着监控&#xff0c;最好是我在外面远程指挥它起飞&#xff0c;飞去厨房&#xff0c;飞去洗手间&#xff0c;甚至飞去阳台&#xff0c;查看水龙头情况啊…

【力扣白嫖日记】1435.制作会话柱状图

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1435.制作会话柱状图 表&#xff1a;Sessions 列名类型session_idintdurationint session_id 是该表主键,d…

技术驱动下的同城O2O发展:跑腿配送APP开发教学

在同城生活服务领域&#xff0c;跑腿配送APP的出现与发展&#xff0c;为人们的日常生活提供了极大的便利。今天&#xff0c;小编将着重为大家讲解技术驱动下的同城O2O发展&#xff0c;并从跑腿配送APP的开发角度进行教学和解析。 一、同城O2O发展概述 在同城O2O模式中&#x…

摆动序列(力扣376)

文章目录 题目前知题解一、思路二、解题方法三、Code 总结 题目 Problem: 376. 摆动序列 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素或者含两个不等元…

《web应用技术》第二次课后练习

练习目的&#xff1a; 1、form表单值的获取 2、mysql数据库及表的建立&#xff08;参见视频&#xff09; 3、maven项目的建立&#xff08;参见视频&#xff09; 4、使用jdbc进行数据库的增删改查操作。&#xff08;参见源代码&#xff09; 具体如下&#xff1a; 1、继续理…

mysql dublewrite 双写缓存机制

mysql dublewrite 双写缓存机制&#xff0c;像不像主板双bois系统&#xff0c; 在MySQL的InnoDB存储引擎中&#xff0c;当进行数据写操作时&#xff0c;会先将数据写入到内存中的缓冲池&#xff08;Buffer Pool&#xff09;&#xff0c;然后异步刷新到磁盘上的数据文件。为了提…

怎么在UE过场动画中加入振动效果

我们已经学会了怎么在游戏中加入振动效果&#xff0c;比较典型的交互场景如&#xff1a;在开枪时让手柄同步振动&#xff0c;实现起来真的很简单&#xff0c;就是定义场景和事件&#xff0c;然后在游戏事件发生时播放特定的振动资源文件&#xff0c;跟播放音效是极其相似的&…

如何把学浪app的视频保存本地

如何把学浪app里面的视频保存到本地&#xff0c;其实很简单&#xff0c;只需要用到一个工具&#xff0c;那就是小浪助手.exe 这里我已经把小浪助手.exe打包好了&#xff0c;有需要得话自己下载 链接&#xff1a;https://pan.baidu.com/s/1y7vcqILToULrYApxfEzj_Q?pwdkqvj 提…