Netty实现基础群聊功能(仅功能实现)

1.服务端实现

我们通过前期知识了解到,netty主要保证请求的数据和发送方想要发送的数据是一样的,对数据要进行业务处理,主要靠不同的处理器。

这里实现群聊功能主要靠两个处理器,创建群聊处理器和发送群聊消息处理器。下面将介绍每个处理器处理的业务。

1.创建群聊处理器

这里创建群聊处理器主要就是收到某个用户发送的创建群聊请求后,处理请求的主要步骤为:

1.拿到群聊名称

2.拿到群聊初始化成员

3.创建群聊

4.给创建群聊的用户发送创建成功的消息

5.给群聊里面的用户发送,你已被拉入群聊的消息。

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import message.GroupCreateRequestMessage;
import message.GroupCreateResponseMessage;
import server.session.Group;
import server.session.GroupSession;
import server.session.GroupSessionFactory;

import java.util.List;
import java.util.Set;

/**
 * 组创建请求消息处理程序
 *
 * @date 2023/12/04--08:49:25
 */
@ChannelHandler.Sharable
public class GroupCreateRequestMessageHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, GroupCreateRequestMessage msg) throws Exception {
        //获得群的名字
        String groupName = msg.getGroupName();
        //得到群初始化的一些人员
        Set<String> members = msg.getMembers();
        //创建群聊
        GroupSession groupSession = GroupSessionFactory.getGroupSession();
        Group group = groupSession.createGroup(groupName, members);
        //创建成功,就像创建群的人发送消息创建成功,还要给这个群里面其他的成员发送消息
        if (group==null){
            ctx.writeAndFlush(new GroupCreateResponseMessage(true,"创建成功"));
            List<Channel> membersChannel = groupSession.getMembersChannel(groupName);
            membersChannel.forEach(channel -> {
                channel.writeAndFlush(new GroupCreateResponseMessage(true,"你已被拉入"+groupName+"群"));
            });
        }else {
            ctx.writeAndFlush(new GroupCreateResponseMessage(false,"创建失败:"+groupName+"已经存在"));
        }
    }
}

2.群聊天消息处理器

发送群聊消息这里的实质就是给在这个群里面的每个人都发送了一个消息(排除自己,但是这里没有实现,这个比较简单,可以自行实现),下面是主要实现的步骤:

1.拿到发送消息的用户名

2.拿到群聊的名称

3.根据群聊的名称,通过一个群聊会话管理器,拿到这个群里面的所有人的在线连接

4.给这个群里面的每个人发送消息(发送者+消息)

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import message.GroupChatRequestMessage;
import message.GroupChatResponseMessage;
import server.session.GroupSessionFactory;

import java.util.List;

@ChannelHandler.Sharable
public class GroupChatRequestMessageHandler extends SimpleChannelInboundHandler<GroupChatRequestMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, GroupChatRequestMessage msg) throws Exception {
        List<Channel> channels = GroupSessionFactory.getGroupSession()
                .getMembersChannel(msg.getGroupName());

        for (Channel channel : channels) {
            channel.writeAndFlush(new GroupChatResponseMessage(msg.getFrom(), msg.getContent()));
        }
    }
}

2.客户端实现

客户端的代码基本没有改变

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import message.*;
import protocol.MessageCodecSharable;
import protocol.ProcotolFrameDecoder;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class ChatClient {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        CountDownLatch LOGIN_WAIT=new CountDownLatch(1);
        try {
            final boolean[] flag = {false};
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(group);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProcotolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC);
                    ch.pipeline().addLast("clientHandler",new ChannelInboundHandlerAdapter(){
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {

                            ExecutorService executorService = Executors.newSingleThreadExecutor();
                            try {
                                executorService.execute(()->{
                                    Scanner scanner = new Scanner(System.in);
                                    System.out.println("请输入用户名");
                                    String username = scanner.nextLine();
                                    System.out.println("请输入密码");
                                    String password = scanner.nextLine();
                                    LoginRequestMessage message = new LoginRequestMessage(username, password);
                                    ctx.writeAndFlush(message);
                                    System.out.println("等待");
                                    try {
                                        LOGIN_WAIT.await();

                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                    //登录失败
                                    if (!flag[0]){
                                        System.out.println("登录失败");
                                        ctx.channel().close();
                                        return;
                                    }
                                    //登录成功
                                    System.out.println("登录成功");
                                    while (true){
                                        System.out.println("========");
                                        System.out.println("send [to] [content]");
                                        String s = scanner.nextLine();
                                        String[] s1 = s.split(" ");
                                        switch (s1[0]){
                                            case "send":
                                                ctx.writeAndFlush(new ChatRequestMessage(username, s1[1], s1[2]));
                                                break;
                                            case "gsend":
                                                ctx.writeAndFlush(new GroupChatRequestMessage(username, s1[1], s1[2]));
                                                break;
                                            case "gcreate":
                                                Set<String> set = new HashSet<>(Arrays.asList(s1[2].split(",")));
                                                set.add(username); // 加入自己
                                                ctx.writeAndFlush(new GroupCreateRequestMessage(s1[1], set));
                                                break;
                                            case "gmembers":
                                                ctx.writeAndFlush(new GroupMembersRequestMessage(s1[1]));
                                                break;
                                            case "gjoin":
                                                ctx.writeAndFlush(new GroupJoinRequestMessage(username, s1[1]));
                                                break;
                                            case "gquit":
                                                ctx.writeAndFlush(new GroupQuitRequestMessage(username, s1[1]));
                                                break;
                                            case "quit":
                                                ctx.channel().close();
                                                return;
                                            default:
                                                System.out.println("我还没有这个功能");
                                                break;
                                        }
                                    }
                                });
                            } catch (Exception e) {
                                e.printStackTrace();
                            }finally {
                                executorService.shutdown();
                            }
                        }

                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            if (msg instanceof LoginResponseMessage){
                                LoginResponseMessage message= (LoginResponseMessage) msg;
                                if (message.isSuccess()){
                                    flag[0] =true;
                                }
                                LOGIN_WAIT.countDown();
                            }
                            log.debug("{}",msg);
                        }
                    });
                }
            });
            Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("client error", e);
        } finally {
            group.shutdownGracefully();
        }
    }
}

这是这个项目的gitee地址:https://gitee.com/CGM666/netty-demo.git

这个项目还在不断更新中。

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
首先,需要了解什么是NettyNetty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。 接下来,我们可以开始实现聊天室功能。一个简单的聊天室应该具备以下功能: 1. 用户连接和断开连接的处理; 2. 用户发送消息和接收消息的处理; 3. 消息广播给所有在线用户。 下面是一个简单的实现: 1. 用户连接和断开连接的处理 Netty提供了ChannelHandlerAdapter和ChannelInboundHandlerAdapter两个抽象类,我们可以继承其中一个来实现自己的Handler。这里我们使用ChannelInboundHandlerAdapter。 ```java public class ChatServerHandler extends ChannelInboundHandlerAdapter { // 用户列表,用于保存所有连接的用户 private static List<Channel> channels = new ArrayList<>(); // 新用户连接时,将连接加入用户列表 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 上线了"); } // 用户断开连接时,将连接从用户列表中移除 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 下线了"); } } ``` 2. 用户发送消息和接收消息的处理 Netty的数据传输是通过ByteBuf来实现的,因此我们需要将ByteBuf转换为字符串进行处理。 ```java public class ChatServerHandler extends ChannelInboundHandlerAdapter { // 用户列表,用于保存所有连接的用户 private static List<Channel> channels = new ArrayList<>(); // 新用户连接时,将连接加入用户列表 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 上线了"); } // 用户断开连接时,将连接从用户列表中移除 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 下线了"); } // 接收用户发送的消息并处理 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; String received = buf.toString(CharsetUtil.UTF_8); System.out.println(ctx.channel().remoteAddress() + ": " + received); broadcast(ctx, received); } // 将消息广播给所有在线用户 private void broadcast(ChannelHandlerContext ctx, String msg) { for (Channel channel : channels) { if (channel != ctx.channel()) { channel.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); } } } } ``` 3. 消息广播给所有在线用户 我们可以使用broadcast方法将接收到的消息广播给所有在线用户。 ```java public class ChatServerHandler extends ChannelInboundHandlerAdapter { // 用户列表,用于保存所有连接的用户 private static List<Channel> channels = new ArrayList<>(); // 新用户连接时,将连接加入用户列表 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 上线了"); } // 用户断开连接时,将连接从用户列表中移除 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 下线了"); } // 接收用户发送的消息并处理 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; String received = buf.toString(CharsetUtil.UTF_8); System.out.println(ctx.channel().remoteAddress() + ": " + received); broadcast(ctx, received); } // 将消息广播给所有在线用户 private void broadcast(ChannelHandlerContext ctx, String msg) { for (Channel channel : channels) { if (channel != ctx.channel()) { channel.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); } } } } ``` 接下来我们需要编写一个启动类,用于启动聊天室服务器。 ```java public class ChatServer { public static void main(String[] args) throws Exception { // 创建EventLoopGroup EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // 创建ServerBootstrap ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChatServerHandler()); } }); // 启动服务器 ChannelFuture channelFuture = serverBootstrap.bind(8888).sync(); System.out.println("服务器启动成功"); // 关闭服务器 channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ``` 现在,我们就完成了一个简单的聊天室服务器。可以通过运行ChatServer类启动服务器,然后使用telnet命令连接服务器进行聊天。 ```sh telnet localhost 8888 ``` 输入发送的消息,即可将消息广播给所有在线用户。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值