netty通讯框架

netty通信框架,如果考虑使用protubuf进行通讯,则protubuf的设计的封装非常重要,对于单机,于分布式项目来考虑,protubuf有不同的设计方案。

可以考虑,根据枚举+配置文件,进行路由封装消息体。考虑请求于响应,通知封装在一个消息体。

syntax = "proto3";

option java_multiple_files = false;

option java_package = "com.example.demo.message";

option java_outer_classname = "GDM";

package example;

enum MSG {
    Test_Request=0;
    Test_Response=1;
    Login_Request = 2;
    Login_Response = 3;
    Logout_Request = 4;
    Logout_Response = 5;
    Keepalive_Request = 6;
    Keepalive_Response = 7;
    Get_Friends_Request = 8;
    Get_Friends_Response = 9;
    Send_Message_Request = 10;
    Send_Message_Response = 11;
    Friend_Notification = 12;
    Message_Notification = 13;
    Welcome_Notification = 14;
}



/*下面定义具体的消息内容,MSG枚举中的每个消息ID,如果有消息体,则会对应一个message 定义,如果无消息体则不必要*/
/*Login_Request 消息ID对应的消息名称为LoginRequest ; 规则为取掉下划线,有利于某些自动化编码工具编写自动化代码*/
message LoginRequest
{
    string username = 1;
    string password = 2;
}

message LoginResponse
{
    fixed32 ttl = 1;
}

message TestRequest {
    string data = 1;
}

message TestResponse {
    string result = 1;
}

/*没有对应的MSG id,则为其它 消息的字段,作为子消息,可以消息嵌套定义,也可以放在外面,个人习惯放在外部。*/
message Friend
{
    bytes name = 1;
    bool online = 2;
}

message GetFriendsResponse
{
    Friend friends = 1;
}

message SendMessageRequest
{
    bytes receiver = 1;
    bytes text = 2;
}

message FriendNotification
{
    bytes name = 1;
    bool online = 2;
}

message MessageNotification
{
    bytes sender = 1;
    bytes text = 2;
    string timestamp = 3;
}

message WelcomeNotification
{
    bytes text = 1;
}

/*请求消息集合,把所有的 XxxxxRequest消息全部集合在一起,使用起来类似于C语言的联合体,全部使用optional字段,任何时刻根据MSG 的id值,最多只有一个有效性, 从程序的逻辑上去保证,编译器(不管是protoc还是具体语言的编译器都无法保证)*/
message Request
{
    oneof request_oneof {
        LoginRequest login = 1;
        TestRequest test_request = 2;
        SendMessageRequest send_message = 3;
    }

}

/*与Request作用相同,把所有的XxxResponse消息集合在一起,当作联合体使用,不过额外多了几个字段用于表示应答的结果*/
message Response
{
    bool result = 1;  //true表示应答成功,false表示应答失败
    bool last_response = 2;// 一个请求可以包含多个应答,用于指示是否为最后一个应答
    bytes error_describe = 3;// result == false时,用于描述错误信息
    LoginResponse login = 4;
    TestResponse test_response = 5;
    GetFriendsResponse get_friends = 6;
}

/*与Request相同,把所有的XxxxxNotification消息集合在一起当作联合体使用.*/
message Notification
{
    FriendNotification friend = 1;
    MessageNotification msg = 2;
    WelcomeNotification welcome = 3;
}

/*顶层消息,包含所有的Request,Response,Notification,
具体包含哪个消息又 MSG msg_type字段决定,程序逻辑去保证msg_type和具体的消息进行匹配*/
message Message
{
    MSG msg_type = 1;//根据消息序号确定特定消息
    fixed32 sequence = 2;//消息系列号,主要用于Request和Response,Response的值必须和Request相同,使得发送端可以进行事务匹配处理
    fixed32 session_id = 3;
    Request request = 4;
    Response response = 5;
    Notification notification = 6;
}

service XxxService {
    rpc getXXX (TestRequest) returns (TestResponse) {}
}

netty服务器编写

package com.example.demo.rpc;

import com.example.demo.message.GDM;
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.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;

@Component
public class NettyServer {
    public NettyServer() throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    start(8888);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

    }

    public void start(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    // 指定Channel
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    //使用指定的端口设置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //将小的数据包包装成更大的帧进行传送,提高网络的负载,即TCP延迟传输
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            // 添加长度解码器
                            p.addLast(new ProtobufVarint32FrameDecoder());
                            // 添加ProtoBuf解码器
                            p.addLast(new     
                            ProtobufDecoder(GDM.Message.getDefaultInstance()));
                            p.addLast(new ProtobufVarint32LengthFieldPrepender());
                            // 添加ProtoBuf编码器
                            p.addLast(new ProtobufEncoder());
                            // 添加自定义的业务处理器
                            p.addLast(new MyServerHandler());
                        }
                    });
            ChannelFuture f = b.bind().sync();
            System.out.println("=====服务器启动成功=====");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }




}
MyServerHandler处理。
package com.example.demo.rpc;

import com.example.demo.annotation.RouteExecution;
import com.example.demo.interfaceT.monitor1.EventPublisher;

import com.example.demo.message.GDM;
import com.example.demo.BeanTool;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class MyServerHandler extends SimpleChannelInboundHandler<GDM.Message> implements Process {
    private static final Logger logger = LoggerFactory.getLogger(MyServerHandler.class);
    private EventPublisher eventPublisher =eventPublisher = BeanTool.getBean(EventPublisher.class);;
    private RouteExecution scanner = BeanTool.getBean(RouteExecution.class);

    /**
     * 服务端读取消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, GDM.Message msg) throws Exception {
        GDM.Request request = msg.getRequest();
        System.out.println("====服务器收到信息======");
        route(ctx, msg);
    }

    /**
     * 服务端异常处理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 记录异常到日志
        System.out.println(ctx + "====服务器收到异常======" + cause);
        // 关闭连接,释放资源
        ctx.close();
        // 注意:通常不需要调用super.exceptionCaught(ctx, cause),因为ChannelInboundHandlerAdapter的此方法为空实现
    }

    @Override
    public void route(ChannelHandlerContext ctx, GDM.Message message) throws Exception {
        GDM.Request request = message.getRequest();
        GDM.MSG msgType = message.getMsgType();
        String nameClass = msgType.name();
        //执行方法
        scanner.getBeanAndExecute(nameClass, ctx, request);
    }

}

路由处理,对应分布式项目,这里可以使用负载均衡指定找到对应的服务器,然后再找到的对应的处理器即可。但是对于单机来说仅仅使用反射,找到对应的处理器即可。

这里使用单机演示,这里使用了自定义注解,方便业务的快速开发。

package com.example.demo.annotation;

import com.example.demo.annotation.route.RouteServer;
import com.example.demo.BeanTool;
import com.example.demo.message.GDM;
import io.netty.channel.ChannelHandlerContext;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Component
public class RouteExecution  {
    private static final Map<String, String> ENUMTOCLASSMAP = new HashMap<>();
    private static  String HANDLERPATH ="";
    static {
        try {
            // 使用ClassLoader来加载资源
            InputStream inputStream = RouteExecution.class.getClassLoader().getResourceAsStream("route.xml");
            if (inputStream == null) {
                throw new RuntimeException("Resource not found: route.xml");
            }
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(inputStream);
            doc.getDocumentElement().normalize();

            NodeList entries = doc.getElementsByTagName("entry");
            for (int i = 0; i < entries.getLength(); i++) {
                Element entry = (Element) entries.item(i);
                String key = entry.getAttribute("key");
                String value = entry.getTextContent().trim(); // 可能需要trim()来去除前后的空白字符
                ENUMTOCLASSMAP.put(key, value);
            }
            NodeList handlerPath = doc.getElementsByTagName("handlerPath");
            Node item = handlerPath.item(0);
            HANDLERPATH = item.getTextContent().trim();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void getBeanAndExecute(String param, ChannelHandlerContext ctx, GDM.Request request) {
        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .setUrls(ClasspathHelper.forPackage(HANDLERPATH))
                .setScanners(new SubTypesScanner(false)));

        Set<Class<? extends RouteServer>> classes = reflections.getSubTypesOf(RouteServer.class);
        for (Class<?> clazz : classes) {
            if (clazz.isAnnotationPresent(ToServer.class)) {
                ToServer toServer = clazz.getAnnotation(ToServer.class);
                // 注解参数对比
                String methodName = toServer.value();
                String orDefault = ENUMTOCLASSMAP.getOrDefault(param, null);
                if (orDefault == null) {
                    continue;
                }
                if (!methodName.equals(orDefault)) {
                    continue;
                }
                Object bean = BeanTool.getBean(clazz);
                // 查找并调用方法
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(ToMethod.class)) {
                        ToMethod annotation = method.getAnnotation(ToMethod.class);
                        if (!annotation.value().equals(param)) {
                            continue;
                        }
                        try {
                            method.setAccessible(true);
                            method.invoke(bean, ctx, request);
                            ctx.close();
                            return;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

}

注解实现

package com.example.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ToMethod {
    String value();
}


package com.example.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) // 类级别注解
@Retention(RetentionPolicy.RUNTIME)
public @interface ToServer {
    String value();
}

最终测试

package com.example.demo.handler;

import com.example.demo.annotation.ToMethod;
import com.example.demo.annotation.ToServer;
import com.example.demo.annotation.route.RouteServer;
import com.example.demo.message.GDM;
import io.netty.channel.ChannelHandlerContext;
import org.springframework.stereotype.Component;

@Component
@ToServer("Test")
public class Test extends RouteServer {
    @ToMethod("Login_Request")
    public void p( ChannelHandlerContext ctx, GDM.Request request) {
        GDM.LoginRequest login = request.getLogin();
        System.out.println(ctx + "oooooooooooooo" + login);
    }

    @ToMethod("Test_Request")
    public void ff( ChannelHandlerContext ctx, GDM.Request request) {
        GDM.TestRequest testRequest = request.getTestRequest();
        System.out.println(ctx + "oooooooooooooo" + testRequest);

    }

    @ToMethod("Send_Message_Request")
    public void ddd( ChannelHandlerContext ctx, GDM.Request request) {
        GDM.SendMessageRequest sendMessage = request.getSendMessage();
        System.out.println(ctx + "oooooooooooooo" + sendMessage);
    }

}

对于分布式来说仅仅是,添加一个网关的处理逻辑即可。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值