NIO框架Netty之编码初体验

NIO框架Netty之编码初体验

引言

Netty是一个基于Java NIO的高性能网络框架,以其高效的线程模型和丰富的功能广泛应用于各种网络应用开发。在本篇文章中,我们将通过一个完整的聊天室案例,深入体验Netty的强大功能。这个案例包括服务端和客户端的实现,通过WebSocket协议实现登录验证、心跳检测、信息发送、转发、接收等功能。我们将结合常用的信息报文格式设计,并通过详细的源码注释一步一步完成这个案例。

环境准备

在开始编写代码之前,请确保已经安装以下环境:

  • JDK 8或以上版本
  • Maven 3.3或以上版本

项目结构

我们将创建一个Maven项目,项目结构如下:

netty-chatroom/
├── pom.xml
├── src/
    ├── main/
        ├── java/
            └── com/
                └── example/
                    └── chat/
                        ├── server/
                        ├── client/
                        └── common/
                            ├── Message.java
                            ├── MessageType.java
                            └── ...
        ├── resources/
    └── test/

POM文件配置

首先,我们需要在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-chatroom</artifactId>
    <version>1.0-SNAPSHOT</version>

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

信息报文格式设计

我们使用JSON格式来设计信息报文。每个报文包含以下字段:

  • type:消息类型(登录、心跳、消息等)
  • sender:发送者
  • receiver:接收者
  • content:消息内容
  • timestamp:时间戳

示例报文:

{
  "type": "message",
  "sender": "user1",
  "receiver": "user2",
  "content": "Hello, user2!",
  "timestamp": 1620000000000
}

服务端实现

创建服务端主类

com.example.chat.server包中创建ChatServer类:

package com.example.chat.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

public class ChatServer {
    private final int port;

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

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline pipeline = ch.pipeline();
                     pipeline.addLast(new HttpServerCodec());
                     pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                     pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
                     pipeline.addLast(new IdleStateHandler(60, 0, 0)); // 心跳检测
                     pipeline.addLast(new ChatServerHandler());
                 }
             });

            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new ChatServer(8080).start();
    }
}

创建服务端处理器

com.example.chat.server包中创建ChatServerHandler类:

package com.example.chat.server;

import com.example.chat.common.Message;
import com.example.chat.common.MessageType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.util.HashMap;
import java.util.Map;

public class ChatServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final Map<String, ChannelHandlerContext> userMap = new HashMap<>();

    @Override
    public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        String text = msg.text();
        Message message = mapper.readValue(text, Message.class);
        handleMessage(ctx, message);
    }

    private void handleMessage(ChannelHandlerContext ctx, Message message) {
        switch (message.getType()) {
            case LOGIN:
                handleLogin(ctx, message);
                break;
            case HEARTBEAT:
                handleHeartbeat(ctx, message);
                break;
            case MESSAGE:
                handleMessageForward(message);
                break;
            default:
                break;
        }
    }

    private void handleLogin(ChannelHandlerContext ctx, Message message) {
        userMap.put(message.getSender(), ctx);
        Message response = new Message();
        response.setType(MessageType.LOGIN);
        response.setContent("Login successful");
        ctx.writeAndFlush(new TextWebSocketFrame(encodeMessage(response)));
    }

    private void handleHeartbeat(ChannelHandlerContext ctx, Message message) {
        Message response = new Message();
        response.setType(MessageType.HEARTBEAT);
        response.setContent("Heartbeat received");
        ctx.writeAndFlush(new TextWebSocketFrame(encodeMessage(response)));
    }

    private void handleMessageForward(Message message) {
        ChannelHandlerContext receiverCtx = userMap.get(message.getReceiver());
        if (receiverCtx != null) {
            receiverCtx.writeAndFlush(new TextWebSocketFrame(encodeMessage(message)));
        }
    }

    private String encodeMessage(Message message) {
        try {
            return mapper.writeValueAsString(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleEvent = (IdleStateEvent) evt;
            if (idleEvent.state() == IdleStateHandler.READER_IDLE_STATE_EVENT.state()) {
                ctx.close(); // 关闭超时连接
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

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

创建消息类和枚举

com.example.chat.common包中创建Message类和MessageType枚举:

package com.example.chat.common;

public class Message {
    private MessageType type;
    private String sender;
    private String receiver;
    private String content;
    private long timestamp;

    // Getters and setters
    public MessageType getType() { return type; }
    public void setType(MessageType type) { this.type = type; }
    public String getSender() { return sender; }
    public void setSender(String sender) { this.sender = sender; }
    public String getReceiver() { return receiver; }
    public void setReceiver(String receiver) { this.receiver = receiver; }
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}

public enum MessageType {
    LOGIN, HEARTBEAT, MESSAGE
}

客户端实现

创建客户端主类

com.example.chat.client包中创建ChatClient类:

package com.example.chat.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.websocketx.Web

SocketClientProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import java.net.URI;

public class ChatClient {
    private final String host;
    private final int port;
    private final String username;

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

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline pipeline = ch.pipeline();
                     pipeline.addLast(new HttpClientCodec());
                     pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                     pipeline.addLast(new WebSocketClientProtocolHandler(new URI("ws://" + host + ":" + port + "/ws")));
                     pipeline.addLast(new IdleStateHandler(0, 30, 0)); // 心跳检测
                     pipeline.addLast(new ChatClientHandler(username));
                 }
             });

            b.connect(host, port).sync().channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

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

创建客户端处理器

com.example.chat.client包中创建ChatClientHandler类:

package com.example.chat.client;

import com.example.chat.common.Message;
import com.example.chat.common.MessageType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;

public class ChatClientHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final ObjectMapper mapper = new ObjectMapper();
    private final String username;

    public ChatClientHandler(String username) {
        this.username = username;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 发送登录消息
        Message loginMessage = new Message();
        loginMessage.setType(MessageType.LOGIN);
        loginMessage.setSender(username);
        ctx.writeAndFlush(new TextWebSocketFrame(encodeMessage(loginMessage)));
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        String text = msg.text();
        Message message = mapper.readValue(text, Message.class);
        System.out.println("Received: " + message.getContent());
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            // 发送心跳消息
            Message heartbeatMessage = new Message();
            heartbeatMessage.setType(MessageType.HEARTBEAT);
            heartbeatMessage.setSender(username);
            ctx.writeAndFlush(new TextWebSocketFrame(encodeMessage(heartbeatMessage)));
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

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

    private String encodeMessage(Message message) {
        try {
            return mapper.writeValueAsString(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

客户端和服务端交互

启动服务端:

java -cp target/netty-chatroom-1.0-SNAPSHOT.jar com.example.chat.server.ChatServer

启动客户端:

java -cp target/netty-chatroom-1.0-SNAPSHOT.jar com.example.chat.client.ChatClient

总结

通过本篇文章,我们详细介绍了如何使用Netty实现一个简单的WebSocket聊天室案例。该案例包括了登录验证、心跳检测、信息发送、转发和接收等功能。通过详细的源码注释和逐步讲解,相信你对Netty的使用有了更深入的了解。Netty的强大之处在于其灵活性和高性能,掌握Netty将极大地提升你的网络编程能力。希望本文对你有所帮助,欢迎继续探索Netty的更多高级功能和应用场景。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值