自学内容网 自学内容网

使用Netty实现一个简单的聊天服务器

✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。
🍎个人主页:Hhzzy99
🍊个人信条:坚持就是胜利!
💞当前专栏:Netty
🥭本文内容:Netty实现一个简单的聊天服务器。


使用Netty实现一个简单的聊天服务器

在本篇教程中,我们将通过Java的Netty框架实现一个简单的聊天服务器。

Netty是一款基于NIO(非阻塞IO)开发的网络框架,适用于高性能、高并发的应用场景。本文会详细展示如何通过Netty实现聊天服务器,实现控制台版客户端。

一、项目初始化与依赖配置

1.1 创建Maven项目并添加依赖

我们使用Maven管理依赖,首先在pom.xml文件中添加Netty依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>netty-chat</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Netty 依赖 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.68.Final</version>
        </dependency>
    </dependencies>
</project>

这样配置完成后,Maven会自动下载Netty的相关包。

二、服务器端代码实现

服务器端负责接受客户端连接、广播消息,并在客户端断开时进行处理。接下来我们编写服务器端启动类和处理器。

2.1 ChatServer - 服务端启动类

ChatServer类用于配置Netty服务器的基础设置,包括监听的端口、通道类型(NIO模式)和处理器链。服务器会监听指定端口,并通过处理器将消息分发给各客户端。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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;

public class ChatServer {

    private final int port;

    public ChatServer(int port) {
        this.port = port;  // 设置服务器监听的端口
    }

    public void start() throws InterruptedException {
        // bossGroup:接受客户端连接的线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // workerGroup:处理客户端连接的数据传输
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // ServerBootstrap用于启动和配置Netty服务器
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)  // 设置boss和worker线程组
             .channel(NioServerSocketChannel.class)  // 指定NIO传输的通道类型
             .option(ChannelOption.SO_BACKLOG, 1024)  // 设置队列容量
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ch.pipeline().addLast(new StringDecoder());
                     ch.pipeline().addLast(new StringEncoder());
                     // 为每个客户端连接配置消息处理器
                     ch.pipeline().addLast(new ChatServerHandler());
                 }
             });

            // 绑定端口并启动服务器
            ChannelFuture f = b.bind(port).sync();
            System.out.println("Chat server started on port " + port);
            // 等待服务器关闭
            f.channel().closeFuture().sync();
        } finally {
            // 关闭资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;  // 定义服务器监听的端口号
        new ChatServer(port).start();  // 启动服务器
    }
}

2.2 ChatServerHandler - 消息处理器

ChatServerHandler类负责管理客户端连接和消息的广播。我们会在客户端加入时广播“用户加入”消息,并在消息接收时进行广播。

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

public class ChatServerHandler extends ChannelInboundHandlerAdapter  {

    // 使用ChannelGroup来保存所有的连接,便于消息广播
    private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    // 当新的客户端连接加入时调用
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        // 广播消息给所有已连接的客户端,通知新的连接加入
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " joined\n");
        }
        channels.add(incoming);  // 将新连接加入ChannelGroup
    }

    // 当客户端断开连接时调用
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel outgoing = ctx.channel();
        // 广播消息给所有已连接的客户端,通知连接已断开
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + outgoing.remoteAddress() + " left\n");
        }
        channels.remove(outgoing);  // 从ChannelGroup中移除断开的连接
    }

    // 当服务器接收到客户端发来的消息时调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
        Channel incoming = ctx.channel();
        // 使用StringDecoder与StringEncoder编码解码消息,所以直接字符串
        String msg = (String) message;
        System.out.println("[client] : " + msg);
        // 向其他客户端广播消息,当前发送者不接收自己的消息
        for (Channel channel : channels) {
            if (channel != incoming) {
                channel.writeAndFlush("[" + incoming.remoteAddress() + "] " + msg + "\n");
            } else {
                channel.writeAndFlush("[you] " + msg + "\n");
            }
        }
    }

    // 当出现异常时调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();  // 关闭连接
    }
}

三、控制台客户端实现

在实现浏览器客户端之前,我们先用Java创建一个简单的控制台客户端,以便在控制台中测试服务器的基本功能。通过这种方式,我们可以在不使用HTML页面的情况下,验证服务器的消息广播和客户端连接是否正常工作。

3.1 ChatClient - 客户端启动类

ChatClient类用于建立与服务器的连接,并在连接成功后允许用户发送消息。

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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;

import java.util.Scanner;

public class ChatClient {

    private final String host;
    private final int port;

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

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ch.pipeline().addLast(new StringDecoder());
                     ch.pipeline().addLast(new StringEncoder());
                     ch.pipeline().addLast(new ChatClientHandler());
                 }
             });

            Channel channel = b.connect(host, port).sync().channel();

            Scanner scanner = new Scanner(System.in);
            System.out.println("Enter your messages (type 'exit' to quit):");

            while (true) {
                String message = scanner.nextLine();
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                channel.writeAndFlush(message + "\n");
            }

        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ChatClient("localhost", 8080).start();
    }
}

3.2 ChatClientHandler - 客户端消息处理器

ChatClientHandler类用于接收服务器的消息并在控制台显示。

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

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        System.out.println(msg);  // 输出从服务器收到的消息
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

四、启动与测试

5.1 启动服务器和控制台客户端

  1. 启动ChatServer
  2. 启动ChatClient,进行消息测试。

我们启动服务端ChatServer
chatServer

接着再启动客户端ChatClient
chatClient

客户端ChatClient发送消息,并且服务端转发回来( [YOU] 那一行就是服务端转发回来的):
在这里插入图片描述
服务端接收到消息:
在这里插入图片描述


结语

本文通过实现简单的控制台客户端和HTML客户端,让大家更好地理解Netty服务器的工作原理。希望对大家有所帮助。


原文地址:https://blog.csdn.net/XUHUANGHOST/article/details/143486003

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!