2025.5.16

Netty 小型聊天服务器项目

下面是一个基于Netty框架实现的简单聊天服务器项目,包含服务端和客户端代码。

1. 项目概述

这是一个简单的聊天系统,支持:

  • 多客户端连接
  • 广播消息给所有用户
  • 简单的昵称设置
  • 用户加入/离开通知

2. 服务端实现

2.1 服务端主类

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;

public class ChatServer {
    private final int port;

    public ChatServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline pipeline = ch.pipeline();
                     
                     // 添加编解码器
                     pipeline.addLast("decoder", new StringDecoder());
                     pipeline.addLast("encoder", new StringEncoder());
                     
                     // 添加业务处理器
                     pipeline.addLast("handler", new ChatServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)
             .childOption(ChannelOption.SO_KEEPALIVE, true);
            
            System.out.println("ChatServer started on port " + port);
            
            // 绑定端口,开始接收连接
            ChannelFuture f = b.bind(port).sync();
            
            // 等待服务器socket关闭
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("ChatServer stopped");
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }

        new ChatServer(port).run();
    }
}

2.2 服务端处理器

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;

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    // 用于管理所有连接的Channel
    private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        
        // 通知所有客户端有新用户加入
        channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入聊天室\n");
        
        channels.add(incoming);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        
        // 通知所有客户端有用户离开
        channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 离开聊天室\n");
        
        // 自动从ChannelGroup中移除,不需要手动操作
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel incoming = ctx.channel();
        
        // 如果是设置昵称的命令
        if (msg.startsWith("/nick ")) {
            String nickname = msg.substring(6).trim();
            incoming.attr(NICKNAME).set(nickname);
            incoming.writeAndFlush("[SERVER] 你的昵称已设置为: " + nickname + "\n");
            return;
        }
        
        // 广播消息给所有客户端
        String nickname = incoming.attr(NICKNAME).get();
        if (nickname == null) {
            nickname = incoming.remoteAddress().toString();
        }
        
        for (Channel channel : channels) {
            if (channel != incoming) {
                channel.writeAndFlush("[" + nickname + "] " + msg + "\n");
            } else {
                channel.writeAndFlush("[你] " + msg + "\n");
            }
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("客户端连接: " + incoming.remoteAddress());
        incoming.writeAndFlush("欢迎加入聊天室! 使用/nick <昵称>设置你的昵称\n");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("客户端断开: " + incoming.remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
    
    // 用于存储昵称的属性键
    private static final AttributeKey<String> NICKNAME = AttributeKey.valueOf("nickname");
}

3. 客户端实现

3.1 客户端主类

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;

import java.io.BufferedReader;
import java.io.InputStreamReader;

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 run() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline pipeline = ch.pipeline();
                     
                     pipeline.addLast("decoder", new StringDecoder());
                     pipeline.addLast("encoder", new StringEncoder());
                     
                     pipeline.addLast("handler", new ChatClientHandler());
                 }
             });
            
            Channel channel = b.connect(host, port).sync().channel();
            System.out.println("Connected to chat server at " + host + ":" + port);
            
            // 读取控制台输入
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                String line = in.readLine();
                if (line == null || "bye".equalsIgnoreCase(line)) {
                    break;
                }
                channel.writeAndFlush(line + "\r\n");
            }
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int port = 8080;
        
        if (args.length > 0) {
            host = args[0];
        }
        if (args.length > 1) {
            port = Integer.parseInt(args[1]);
        }
        
        new ChatClient(host, port).run();
    }
}

3.2 客户端处理器

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

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 直接输出服务器返回的消息
        System.out.print(msg);
    }

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

4. 项目运行说明

4.1 编译和运行

  1. 首先编译所有Java文件:
javac -cp ".:netty-all-4.1.68.Final.jar" *.java

(假设你已经下载了Netty的JAR文件并放在当前目录)

  1. 启动服务器:
java -cp ".:netty-all-4.1.68.Final.jar" ChatServer
  1. 启动客户端(可以启动多个):
java -cp ".:netty-all-4.1.68.Final.jar" ChatClient

4.2 使用说明

  1. 连接后,可以使用/nick 你的昵称来设置你的昵称
  2. 直接输入消息回车即可发送给所有用户
  3. 输入"bye"退出客户端

5. 项目扩展建议

这个简单的聊天服务器可以进一步扩展:

  1. 添加私聊功能(如/msg 用户名 消息内容
  2. 添加用户列表查询(如/list
  3. 添加身份验证功能
  4. 支持更丰富的消息格式(如表情、图片等)
  5. 添加消息历史记录
  6. 支持房间/频道功能

6. 依赖说明

你需要Netty框架的JAR文件来运行这个项目。可以从Netty官网下载:https://netty.io/

这个项目使用的是Netty 4.1.x版本,你可以根据需要选择最新版本。

希望这个Netty小项目对你有所帮助!你可以基于这个框架继续扩展更复杂的功能。

posted @ 2025-05-16 22:48  258333  阅读(12)  评论(0)    收藏  举报