Netty实现简单的双向通信(聊天)

本文介绍了如何用Netty构建一个简单的聊天服务器,包括登录验证、消息转发功能,以及客户端交互过程。重点展示了Netty在实际应用中的基础架构和处理器设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.写作目的

这篇文章只实现了最基本的netty聊天功能,真实的业务不是这样实现的,完成这个功能主要为了学习netty相关的使用。

2.具体实现

2.1服务端实现

1.随着进一步的学习,我对服务端的功能和认识有有了进一步的理解,最开始学习netty的时候以为netty是什么神秘莫测的东西,能完成许多神奇的事情,但随着现在的学习,我认为netty作为服务端往往扮演着一个中转站的作用,他接受消息,发送消息,但是很多的时候他对消息做的是一些基础处理,很多实现的细节要我们通过编写不同的处理器和不同的服务端来处理具体的请求和和功能。

2.这里的netty主要通过登录处理器和聊天消息处理器实现了对登录的验证,以及登录会话的管理,还有就是对聊天消息的接受和转发。

3代码具体的实现。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.MessageCodecSharable;
import protocol.ProcotolFrameDecoder;
import server.hander.ChatRequestMessageHandler;
import server.hander.LoginRequestMessageHandler;

@Slf4j
public class ChatServer {
    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        LoginRequestMessageHandler loginRequestMessageHandler = new LoginRequestMessageHandler();
        ChatRequestMessageHandler chatRequestMessageHandler = new ChatRequestMessageHandler();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(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(loginRequestMessageHandler);
                    ch.pipeline().addLast(chatRequestMessageHandler);
                }
            });
            Channel channel = serverBootstrap.bind(8080).sync().channel();
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}

这里需要用到两个自定义的处理器和一个会话管理

1.LoginRequestMessageHandler(登录处理器)
@ChannelHandler.Sharable
public class LoginRequestMessageHandler extends SimpleChannelInboundHandler<LoginRequestMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage message) throws Exception {
        String username = message.getUsername();
        String password = message.getPassword();
        boolean login = UserServiceFactory.getUserService().login(username, password);
        LoginResponseMessage loginResponseMessage = null;
        if (login) {
            SessionFactory.getSession().bind(ctx.channel(),username);
            loginResponseMessage = new LoginResponseMessage(true, "登录成功");
        } else {
            loginResponseMessage = new LoginResponseMessage(false, "用户名或密码错误");
        }
        ctx.writeAndFlush(loginResponseMessage);
    }
}

2.ChatRequestMessageHandler(聊天消息处理器)

@ChannelHandler.Sharable
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage chatRequestMessage) throws Exception {
        Channel channel = SessionFactory.getSession().getChannel(chatRequestMessage.getTo());
        //发送方在线
        if (channel!=null){
            channel.writeAndFlush(new ChatResponseMessage(chatRequestMessage.getFrom(),chatRequestMessage.getContent()));
        }else {
            //发送方不在线
            channel.writeAndFlush(new ChatResponseMessage(false,"对方不在线"));
        }
    }
}

3.SessionFactory(会话管理)

public abstract class SessionFactory {

    private static Session session = new SessionMemoryImpl();

    public static Session getSession() {
        return session;
    }
}

以上就是具体的服务端的实现

2.2客户端实现

1.客户端启动后进行登录,用账号和密码登录和会把登录的信息发送给服务端进行处理,消息经过前面的处理后到达登录处理器,首先验证账号密码的正确,验证成功后就把当前的会话就是一个channel和用户名进行绑定,放进会话管理工具里面,失败的话就会发送登录失败的相关信息,客户端接受到服务端发送的登录失败后就断开连接,成功后就可以发送消息。

2.当前用户登录成功后就用同样的方法登录另外一位用户,等待另一位用户登录成功后就可以开始聊天了,现在发送的聊天的消息很固定,当前张三用户发送"send lisi 你好",会把当前发送的消息用split(" ")分割为3个字符串的数组,数组的第一个字符意识就是进行send操作,第二个 lisi就是发送的对象,第三个你好就是要发送的消息的内容,这条消息和发送这条消息的用户,拼接成(zhansgan,lisi,你好)发送给服务端,这三个意思就是(发送者,接收者,内容),发送到服务端后就会进行解析,拿到接收者后,服务端就会用会话管理器根据接受者的用户名,拿到接收者对应的channel然后发送(zhansgan,你好)就是(发送者,内容),这条消息李四哪里就会接收到(zhansgan,你好),李四就能知道是张三给他发送了你好,然后根据同样的操作,李四就能给张三回消息,如过这个时候张三给李四发送消息的时候,李四未登录或者下线了,就会提示张三对方不在线。(发送离线功能请期待后续更新)

好了这就是客户端基本的逻辑实现。下面是代码具体实现

3。客户端代码具体实现

@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;
                                        }
                                    }
                                });
                            } 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();
        }
    }
}

3.总结

好了这就是具体的实现了,有什么不对或者可以跟好的地方可以评论指出。

### 回答1: Netty是一个开源的、异步的、事件驱动的网络框架,适用于开发高性能、可扩展的网络服务器和客户端。Netty可以支持多种协议,包括TCP、UDP等。 UDP(User Datagram Protocol)是一种面向无连接的传输层协议,它TCP不同,不保证数据的可靠传输。UDP适用于对实时性要求较高的应用场景,如音频、视频等。 在Netty中,实现UDP双向通信简单,只需对Channel对象进行合适的配置即可。首先,创建一个Bootstrap对象,用于配置和启动UDP客户端或服务器。然后,设置Channel类型为NioDatagramChannel,因为UDP是无连接的,不需要像TCP那样建立连接。接着,设置事件处理器,通过实现ChannelInboundHandlerAdapter类的相应方法来处理接收到的数据和事件。 对于UDP客户端,通过调用Bootstrap的bind方法来绑定本地地址和端口,然后通过Channel对象的writeAndFlush方法发送数据到指定的服务器。对于UDP服务器,通过调用Bootstrap的bind方法来绑定服务器的端口,然后通过实现的事件处理器来处理接收到的数据和事件,并通过ChannelHandlerContext对象的writeAndFlush方法发送数据给客户端。 在UDP双向通信中,客户端和服务器可以互相发送和接收数据。通过在事件处理器中对接收到的数据进行处理,可以实现双向通信的需求。 总而言之,Netty提供了强大的功能和灵活的配置选项,使得实现UDP双向通信变得简单和高效。无论是开发UDP客户端还是服务器,通过合适的配置和事件处理器,可以轻松地实现双向通信的需求。 ### 回答2: Netty是一个网络编程框架,它提供了对UDP协议的支持,可以实现UDP的双向通信。 在Netty实现UDP的双向通信可以通过以下步骤完成: 1. 创建一个Bootstrap实例,用于引导和配置客户端或服务端。 2. 设置相关的参数,如通信协议、通信地址和端口等。 3. 创建一个ChannelHandler实例,并重写对应的方法,用于处理收到的消息或发送的消息。 4. 将ChannelHandler实例添加到ChannelPipeline中,用于处理网络I/O事件。 5. 绑定端口并启动程序,等待外部连接。 在双向通信过程中,客户端和服务端分别可以作为接收方和发送方。当客户端发送消息时,服务端可以通过ChannelPipeline中的ChannelHandler接收到消息,并进行相应的处理。而当服务端发送消息时,客户端同样可以通过ChannelPipeline中的ChannelHandler接收到消息,并进行相应的处理。 双向通信实现需要注意以下几点: 1. 要保证客户端和服务端分别有自己独立的ChannelPipeline,以防止消息的混淆。 2. 在消息发送和接收的过程中,需要处理好消息的解码和编码,以保证消息的准确传递。 3. 双向通信中,客户端和服务端可以通过设置ChannelOption参数来进行配置,如设置消息的缓冲区大小、超时时间等。 总结起来,Netty可以通过其提供的UDP支持,实现双向通信。在实现过程中,需要创建Bootstrap实例,配置相关参数,创建并添加ChannelHandler实例到ChannelPipeline中,最后通过绑定端口并启动程序来实现双向通信。同时,需要注意消息的解码和编码,以及设置适当的ChannelOption参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值