Java网络编程(三)NIO|Netty实现多人聊天功能

news2025/1/21 5:57:43

NIO实现

服务端

package com.bierce.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
//服务器端
public class NIOChatServer {
public static void main(String[] args) {
	try {
		new NIOChatServer().startServer(); //启动服务器
	} catch (IOException e) {
		throw new RuntimeException(e);
	}
}
//服务端启动方法
public void startServer() throws IOException {
	//1. 创建Selector选择器
	Selector selector = Selector.open();
	//2.创建ServerSocketChannel通道
	ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
	//3.channel通道绑定监听端口,并设置为非阻塞模式
	serverSocketChannel.bind(new InetSocketAddress(8000));
	serverSocketChannel.configureBlocking(false);
	//4.channel注册到selector上,设置为就绪接收状态
	serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
	System.out.println("Server starts Successfully!");
	//5.自旋,实时监听客户端状态
	for (;;) {
		//获取所有就绪的Channel
		int readChannels = selector.select();
		if (readChannels == 0){ //没有接入的客户端
			continue;
		}
		Set<SelectionKey> selectionKeys = selector.selectedKeys();
		//遍历可用的Channel
		Iterator<SelectionKey> iterator = selectionKeys.iterator();
		while (iterator.hasNext()){
			SelectionKey selectionKey = iterator.next();
			//6.根据就绪状态,调用对应方法实现具体业务操作
			//6.1 accept状态
			if (selectionKey.isAcceptable()){
				acceptOperator(serverSocketChannel,selector);
			}
			//6.2 readable状态
			if (selectionKey.isReadable()){
				readOperator(selector,selectionKey);
			}
			iterator.remove(); //获取成功后没有必要保留需要移除
		}
	}
}
//处理可读状态的方法实现
private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException {
	//从SelectionKey获取到已经就绪的通道
	SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
	ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
	//自旋读取客户端信息
	int readLength = socketChannel.read(byteBuffer);
	String message = "";
	if (readLength > 0){
		byteBuffer.flip(); //切换读模式
		message += Charset.forName("UTF-8").decode(byteBuffer);
	}
	//注册channel到selector,监听可读状态
	socketChannel.register(selector, SelectionKey.OP_READ);
	if (message.length() > 0){
		//把客户端发送过来的信息广播到其他客户端
		System.out.println(message);
		castOtherClients(message,selector,socketChannel);
	}
}
//把客户端发送的消息广播给其他客户端
private void castOtherClients(String message, Selector selector, SocketChannel socketChannel) throws IOException {
	//获取所有已经接入的Channel
	Set<SelectionKey> selectionKeys = selector.keys();
	//对除自己以外的channel进行广播
	for (SelectionKey selectionKey:selectionKeys) {
		//获取每个Channel
	   Channel targetChannel = selectionKey.channel();
	   if (targetChannel instanceof SocketChannel && targetChannel != socketChannel){ //排除服务器以及发送方客户端自己
		   ((SocketChannel) targetChannel).write(Charset.forName("UTF-8").encode(message));
	   }
	}
}
//处理可接收状态的方法实现
private void acceptOperator(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
	//接入状态:创建socketChannel
	SocketChannel socketChannel = serverSocketChannel.accept();
	//socketChannel设置为非阻塞并注册到selector上
	socketChannel.configureBlocking(false);
	socketChannel.register(selector,SelectionKey.OP_READ);
	//回复给客户端信息
	socketChannel.write(Charset.forName("UTF-8")
			.encode("Welcome to MyChatRoom, Please notice your Info!")); //UTF-8编码
}
}

客户端

package com.bierce.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
//客户端实现
public class NIOChatClient {
public void startClient(String name) throws IOException {
	//客户端连接服务器端
	SocketChannel socketChannel =
			SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
	//接收服务器端响应数据
	Selector selector = Selector.open();
	socketChannel.configureBlocking(false);
	socketChannel.register(selector, SelectionKey.OP_READ);
	//创建客户端线程
	new Thread(new ClientThread(selector)).start();
	//模拟向服务器发送消息
	Scanner sc = new Scanner(System.in);
	while (sc.hasNextLine()){
		String msg = sc.nextLine();
		if (msg.length() >0){
			socketChannel.write(Charset.forName("UTF-8").encode(name + ":" + msg));
		}
	}
}
}
class ClientThread implements Runnable{
    private Selector selector;
    public ClientThread(Selector selector) {
        this.selector = selector;
    }
    @Override
    public void run() {
        try {
            for (;;) {
                //获取所有就绪的Channel
                int readChannels = selector.select();
                if (readChannels == 0){ //没有接入的客户端
                    continue;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                //遍历可用的Channel
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove(); //获取成功后没有必要保留需要移除
                    //readable状态
                    if (selectionKey.isReadable()){
                        readOperator(selector,selectionKey);
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //处理可读状态的方法实现
    private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException {
        //从SelectionKey获取到已经就绪的通道
        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //自旋读取客户端信息
        int readLength = socketChannel.read(byteBuffer);
        String message = "";
        if (readLength > 0){
            byteBuffer.flip(); //切换读模式
            message += Charset.forName("UTF-8").decode(byteBuffer);
        }
        //注册channel到selector,监听可读状态
        socketChannel.register(selector, SelectionKey.OP_READ);
        if (message.length() > 0){
            System.out.println(message); //该客户端控制台输出服务端发送过来的信息
        }
    }
}
//客户端A
package com.bierce.io;
import java.io.IOException;
public class TestAClient {
    public static void main(String[] args) {
        try {
            new NIOChatClient().startClient("A");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
//客户端B
package com.bierce.io;
import java.io.IOException;
public class TestBClient {
    public static void main(String[] args) {
        try {
            new NIOChatClient().startClient("B");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

效果图

Netty实现

服务端

package com.bierce.io.netty.chatGroup;
/**
 * 简易群聊功能Netty服务端实现
 */
public class ChatGroupServer {
    private int port;
    private String nowTime = "北京时间-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")) + ": ";
    public ChatGroupServer(int port) {
        this.port = port;
    }
    public void run(){
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup 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 {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("decoder",new StringDecoder());
                            pipeline.addLast("encoder",new StringEncoder());
                            pipeline.addLast(new ChatGroupServerHandler(nowTime)); //自定义业务处理handler
                        }
                    });
            System.out.println(nowTime + "NettyServer start Successful !!!");
            ChannelFuture cf = serverBootstrap.bind(port).sync();
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new ChatGroupServer(9999).run();
    }
}
class ChatGroupServerHandler extends SimpleChannelInboundHandler<String> {
    //定义Channel组,管理所有的Channel,其中GlobalEventExecutor.INSTANCE为全局事件执行器
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private String nowTime; //北京时间
    public ChatGroupServerHandler(String nowTime) {
        this.nowTime = nowTime;
    }
    //channelActive方法表示活动状态,如提示xxx上线
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(nowTime + "[客户端]" + ctx.channel().remoteAddress() + " 上线了~\n");
    }
    //channelActive方法表示非活动状态,如提示xxx离线
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(nowTime + "[客户端]" + ctx.channel().remoteAddress() + " 已离线~\n");
    }
    //handlerAdded方法表示连接建立后立刻执行
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();;
        //将该客户端发送的消息进行广播,writeAndFlush方法会遍历所有Channel并发送消息
        channelGroup.writeAndFlush(nowTime + "[客户端]" + channel.remoteAddress() + " 进入聊天室\n");
        channelGroup.add(channel);
        System.out.println(nowTime + "有新进来的客户端,当前在线客户端数量=" + channelGroup.size());
    }
    //handlerRemoved方法表示断开连接,如将该客户端离开信息发送给在线的其他客户端
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush(nowTime + "[客户端]" + ctx.channel().remoteAddress() + " 已离开了~\n");
        System.out.println(nowTime + "有刚离开的客户端,当前在线客户端数量=" + channelGroup.size());
    }
    //读取客户端信息并作出响应
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.forEach(ch -> {
            if (channel != ch){ //给非自己的其他客户端广播信息
                ch.writeAndFlush(nowTime + "[客户端]" + ctx.channel().remoteAddress() + "发送了消息:" + msg + "\n");
            }else {
                ch.writeAndFlush(nowTime + "[客户端自己]" + ctx.channel().remoteAddress() + "发送了消息:" + msg + "\n");
            }
        });
    }
    //发生异常时关闭该通道Channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

客户端

package com.bierce.io.netty.chatGroup;
/**
 * 简易群聊功能Netty客户端实现
 */
public class ChatGroupClient {
    private String host;
    private int port;
    private String nowTime = "北京时间-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")) + ": ";
    public ChatGroupClient(String host, int port){
        this.host = host;
        this.port = port;
    }
    public void run(){
        NioEventLoopGroup 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 {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("decoder",new StringDecoder());
                            pipeline.addLast("encoder",new StringEncoder());
                            pipeline.addLast(new ChatGroupClientHandler(nowTime)); //自定义业务处理handler
                        }
                    });
            ChannelFuture cf = bootstrap.connect(host, port).sync();
            Channel channel = cf.channel();
            System.out.println(nowTime + "Client"+ channel.localAddress()+ " start Successful!!!");
            Scanner sc = new Scanner(System.in);
            while (sc.hasNextLine()){
                String msg = sc.nextLine();
                channel.writeAndFlush(msg + "\r\n");
            }
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new ChatGroupClient("127.0.0.1",9999).run();
    }
}
class ChatGroupClientHandler extends SimpleChannelInboundHandler<String> {
    private String nowTime;
    public ChatGroupClientHandler(String nowTime) {
        this.nowTime = nowTime;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(nowTime + msg.trim() + "{,前面消息都是服务端广播的信息~~~~}");
    }
}

效果图

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

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

相关文章

记录--为什么要使用 package-lock.json?

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 随着JavaScript在现代软件开发中的日益重要地位&#xff0c;Node.js生态系统中的npm成为了不可或缺的工具。在npm管理依赖的过程中&#xff0c;package-lock.json文件的作用日益凸显。本文将深入…

校园跑腿小程序开发方案详解

校园跑腿小程序App的功能有哪些&#xff1f; 1、用户注册与登录 用户可以通过手机号、社交账号等方式进行注册和登录&#xff0c;以便使用跑腿服务。 2、下单与发布任务 用户可以发布各类跑腿任务&#xff0c;包括食品外卖、快递代收、文件送达、帮我买、帮我取、帮我送等等…

【沐风老师】如何在3dMax中将3D物体转化为样条线构成的对象?

在3dMax中如何把三维物体转化为由样条线构成的对象&#xff1f;通常这样的场景会出现在科研绘图或一些艺术创作当中&#xff0c;下面给大家详细讲解一种3dmax三维物体转样条线的方法。 第一部分&#xff1a;用粒子填充3D对象&#xff1a; 1.创建一个三维对象&#xff08;本例…

CSS中如何改变鼠标指针样式(cursor)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ CSS中改变鼠标指针样式&#xff08;cursor&#xff09;⭐ 示例&#xff1a;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅…

1.linux的常用命令

目录 一、Linux入门 二、Linux文件系统目录 三、Linux的vi和vim的使用 四、Linux的关机、重启、注销 四、Linux的用户管理 五、Linux的运行级别 六、Linux的文件目录指令 七、Linux的时间日期指令 八、Linux的压缩和解压类指令 九、Linux的搜索查找指令 ​​​​​​…

idea新建Java-maven项目时,出现Dependency ‘ xxx(jar包名)‘ not found的解决方案

项目场景&#xff1a; 项目场景&#xff1a;使用idea创建maven项目时&#xff0c;导入简单依赖时&#xff08;本文以mysql-connector-java为例&#xff09;。 问题描述 问题&#xff1a; 首先&#xff0c;在创建新的maven项目中&#xff0c;出现下列两种情况&#xff1a; &am…

什么是响应式图片?如何在网页中实现响应式图片?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式图片&#xff08;Responsive Images&#xff09;⭐ 实现响应式图片的方法1. 使用<img>标签的srcset属性2. 使用<picture>元素3. 使用CSS的max-width属性4. 使用响应式图片库 ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&…

cs231n assignment3 q4 Generative Adversarial Networks

文章目录 嫌墨迹直接看代码Q4 :Generative Adversarial Networkssample_noise题面解析代码输出 discriminator题面解析代码输出 generator题面解析代码输出 discriminator_loss题面解析代码输出 generator_loss题面解析代码 get_optimizer题面解析代码输出 ls_discriminator_lo…

【Unity3D赛车游戏】【四】在Unity中添加阿克曼转向,下压力,质心会让汽车更稳定

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

10*1000【2】

知识: -----------金融科技背后的技术---------------- -------------三个数字化趋势 1.数据爆炸&#xff1a;internet of everything&#xff08;iot&#xff09;&#xff1b;实时贡献数据&#xff1b;公有云服务->提供了灵活的计算和存储。 2.由计算能力驱动的&#x…

120791-76-6/N-(Fluorenylmethoxycarbonyl)-L-threonine tert-Butyl Ester的化学性质

N-(Fluorenylmethoxycarbonyl)-L-threonine tert-Butyl Ester是一种有机化合物&#xff0c;它的化学结构集成了多种官能团&#xff0c;包括苏氨酸和芴甲氧基羰基等。它经常被用于合成多肽类药物和抗癌药物等。 在合成多肽类药物的过程中&#xff0c;西安凯新生物科技有限公司的…

R语言lasso惩罚稀疏加法(相加)模型SPAM拟合非线性数据和可视化

全文链接&#xff1a;https://tecdat.cn/?p33462 本文将关注R语言中的LASSO&#xff08;Least Absolute Shrinkage and Selection Operator&#xff09;惩罚稀疏加法模型&#xff08;Sparse Additive Model&#xff0c;简称SPAM&#xff09;。SPAM是一种用于拟合非线性数据的强…

两年半机场,告诉我如何飞翔

为说明如何坐飞机离港&#xff0c;故此记录一篇。何为离港&#xff0c;顾名思义&#xff0c;离开港湾&#xff0c;那何为港湾&#xff0c;便是机场。 机场&#xff0c;一个你可能经常去&#xff0c;亦或不曾去之地。我想&#xff0c;管你去没去过&#xff0c;先说下怎么去&…

ZLMediaKit+SpringBoot+Vue+Geoserver实现拉取摄像头rtsp流并在web端播放

场景 SpringBoot+Vue+Openlayers实现地图上新增和编辑坐标并保存提交: SpringBoot+Vue+Openlayers实现地图上新增和编辑坐标并保存提交_霸道流氓气质的博客-CSDN博客 开源流媒体服务器ZLMediaKit在Windows上运行、配置、按需拉流拉取摄像头rtsp视频流)并使用http-flv网页播…

Git最简入门

文章目录 几个基本概念版本控制Git的由来分布式 vs 集中式GitSVN Git、GitHub、GitLab、GitWeb、Gitee的区别 动手进行版本控制初始化Git使用情景一&#xff1a;开发新项目使用情景二&#xff1a;在已有项目上开发设置代理 参考 几个基本概念 版本控制 在工作学习中&#xff…

AI + Milvus:将时尚应用搭建进行到底

在上一篇文章中&#xff0c;我们学习了如何利用人工智能技术&#xff08;例如开源 AI 向量数据库 Milvus 和 Hugging Face 模型&#xff09;寻找与自己穿搭风格相似的明星。在这篇文章中&#xff0c;我们将进一步介绍如何通过对上篇文章中的项目代码稍作修改&#xff0c;获得更…

Java中的枚举类,为什么要用枚举类以及使用注意事项和细节

要求&#xff1a;创建季节对象 分析&#xff1a;一年中只有4个季节&#xff0c;因此就不能让随意创建对象了 原本方法&#xff1a; 私有化构造器&#xff0c;避免了随意创建对象不提供setXxx方法&#xff0c;避免了随意赋值&#xff0c;因为枚举对象值通常为只读在本类中直接…

人工智能如何颠覆和改变信息安全格局

当谈及网络信息安全领域&#xff0c;人工智能&#xff08;AI&#xff09;正扮演着关键的角色。其作用是分析庞大的风险数据&#xff0c;以及企业信息系统中不同威胁之间的关联&#xff0c;从而识别出全新类型的攻击方式。这一过程的成果为各类网络安全团队提供了重要情报&#…

ModaHub魔搭社区:WinPlan经营大脑预算编制

目录 WinPlan经营大脑预算编制介绍 WinPlan经营大脑预算编制模版 WinPlan经营大脑预算模版管理 WinPlan经营大脑预算数据录入 WinPlan经营大脑预算编制介绍 预算编制时面向企业经营管理场景,创建各个业务单位的目标,包括销售目标、财务目标、人事目标等,实现各个业务单…

spring之Spring最佳实践与设计模式

Spring最佳实践与设计模式 Spring最佳实践与设计模式 摘要引言词汇解释详细介绍Spring最佳实践1. 使用依赖注入&#xff08;Dependency Injection&#xff09;2. 使用Spring Boot自动配置3. 使用日志框架 注意事项结合设计模式提升代码质量1. 单例模式2. 工厂模式 注意事项 总结…