手写百万并发连接的RPC框架之Netty篇

前言

前面我们已经介绍了,使用JDK原生自带的Socket门面模式手写了注册中心、远程客户端、远程服务端三个模块来构成一个RPC框架,但是性能不是很高,远远无法支撑起大型的分布式系统之间的调用,于是我们今天来分享下如何用Netty来手写RPC框架,并用它来支撑起百万并发,为了便于理解,暂时先不实现注册中心。在该项目中还跟Spring进行了整合。

结构示意图

手写百万并发连接的RPC框架之Netty篇_第1张图片

核心组件

  • LengthFieldBasedFrameDecoder:解决网络传输中的粘包半包问题
  • KryoEncoder/Decoder:负责网络数据的序列化和反序列化的功能,性能比较高
  • ReadTimeoutHandler: ByteBuf数据读取超时处理器,如果长时间没有数据可读,则会抛出异常
  • LoginAuthHandler:服务端维护一个白名单,客户端发出认证请求时需要认证,通过才可以建立连接
  • HeartBeatHandler:心跳检测机制,可以用来更新客户端缓存的服务提供者列表
  • BusiHandler:我们具体调用的业务
  • Jdk动态代理:由这个代理对象通过网络进行实际的调用

Client端

maven

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>4.1.75.Finalversion>
        dependency>

BeanConfig配置

@Configuration
public class BeanConfig {

    @Autowired
    private RpcClientFrame rpcClientFrame;
    @Bean
    public SendSms getSmsService() {
        return rpcClientFrame.getRemoteProxyObject(SendSms.class);
    }
}

UserInfo实体类

package com.cover.rpcnetty.remote.vo;

import java.io.Serializable;

public class UserInfo implements Serializable {
    
    private String name;
    
    private String phone;
    
    public UserInfo() {
        
    }
    
    public UserInfo(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }
}

SendSms服务消费者

package com.cover.rpcnetty.remote;

import com.cover.rpcnetty.remote.vo.UserInfo;

public interface SendSms {
    
    boolean sendMail(UserInfo userInfo);
}

Kryo序列化/反序列化

public class KryoDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object obj = KryoSerializer.deserialize(in);
        out.add(obj);
    }
}
public class KryoEncoder  extends MessageToByteEncoder<MyMessage> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MyMessage message,
                          ByteBuf out) throws Exception {
        KryoSerializer.serialize(message, out);
        ctx.flush();
    }
}


public class KryoFactory {
    
    
    
    
    public static Kryo createKryo() {
        
        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
        kryo.register(GregorianCalendar.class, new GregorianCalendarSerializer());
        kryo.register(InvocationHandler.class, new JdkProxySerializer());
        kryo.register(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer());
        kryo.register(BigInteger.class, new DefaultSerializers.BigIntegerSerializer());
        kryo.register(Pattern.class, new RegexSerializer());
        kryo.register(BitSet.class, new BitSetSerializer());
        kryo.register(URI.class, new URISerializer());
        kryo.register(UUID.class, new UUIDSerializer());
        UnmodifiableCollectionsSerializer.registerSerializers(kryo);
        SynchronizedCollectionsSerializer.registerSerializers(kryo);
        
        kryo.register(HashMap.class);
        kryo.register(ArrayList.class);
        kryo.register(LinkedList.class);
        kryo.register(HashSet.class);
        kryo.register(TreeSet.class);
        kryo.register(Hashtable.class);
        kryo.register(Date.class);
        kryo.register(Calendar.class);
        kryo.register(ConcurrentHashMap.class);
        kryo.register(SimpleDateFormat.class);
        kryo.register(GregorianCalendar.class);
        kryo.register(Vector.class);
        kryo.register(BitSet.class);
        kryo.register(StringBuffer.class);
        kryo.register(StringBuilder.class);
        kryo.register(Object.class);
        kryo.register(Object[].class);
        kryo.register(String[].class);
        kryo.register(byte[].class);
        kryo.register(char[].class);
        kryo.register(int[].class);
        kryo.register(float[].class);
        kryo.register(double[].class);
        
        return kryo;
    }
}
public class KryoSerializer {
    
    private static Kryo kryo = KryoFactory.createKryo();
    
    public static void serialize(Object object, ByteBuf out) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Output output = new Output(baos);
        kryo.writeClassAndObject(output, object);
        output.flush();
        output.close();

        byte[] b = baos.toByteArray();
        try {
            baos.flush();
            baos.close();
        } catch (Exception e ){
            e.printStackTrace();
        }

        out.writeBytes(b);
    }
    
    public static Object deserialize(ByteBuf out) {
        if (out == null) {
            return null;
        }

        Input input = new Input(new ByteBufInputStream(out));
        return kryo.readClassAndObject(input);
    }
}

消息实体设计

public enum MessageType {
    
    // 业务请求消息
    SERVICE_REQ((byte) 0),
    
    // 业务应答消息
    SERVICE_RESP((byte) 1),
    
    // 无需应答的业务请求消息
    ONE_WAY((byte) 2),
    
    // 登录请求消息
    LOGIN_REQ((byte) 3),
    // 登录响应消息
    LOGIN_RESP((byte) 4),
    
    //心跳请求消息
    HEARTBEAT_REQ((byte) 5),
    
    // 心跳应答消息
    HEARTBEAT_RESP((byte) 6)
    ;
    
    private byte value;
    
    private MessageType(byte value) {
        this.value = value;
    }
    
    public byte value() {
        return this.value;
    }
}
package com.cover.rpcnetty.rpc.base.vo;

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

// 消息头
public class MyHeader {
    
    private int crcCode = 0xabef0101;

    // 消息长度
    private int length; 

    // 会话ID
    private long sessionID; 

    // 消息类型
    private byte type; 

    // 消息优先级
    private byte priority; 
    
    // 附件
    private Map<String, Object> attachment = new HashMap<>();

    public int getCrcCode() {
        return crcCode;
    }

    public void setCrcCode(int crcCode) {
        this.crcCode = crcCode;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public long getSessionID() {
        return sessionID;
    }

    public void setSessionID(long sessionID) {
        this.sessionID = sessionID;
    }

    public byte getType() {
        return type;
    }

    public void setType(byte type) {
        this.type = type;
    }

    public byte getPriority() {
        return priority;
    }

    public void setPriority(byte priority) {
        this.priority = priority;
    }

    public Map<String, Object> getAttachment() {
        return attachment;
    }

    public void setAttachment(Map<String, Object> attachment) {
        this.attachment = attachment;
    }

    @Override
    public String toString() {
        return "MyHeader{" +
                "crcCode=" + crcCode +
                ", length=" + length +
                ", sessionID=" + sessionID +
                ", type=" + type +
                ", priority=" + priority +
                ", attachment=" + attachment +
                '}';
    }
}

package com.cover.rpcnetty.rpc.base.vo;

public class MyMessage {
    
    private MyHeader myHeader;
    
    private Object body;

    public MyHeader getMyHeader() {
        return myHeader;
    }

    public void setMyHeader(MyHeader myHeader) {
        this.myHeader = myHeader;
    }

    public Object getBody() {
        return body;
    }

    public void setBody(Object body) {
        this.body = body;
    }

    @Override
    public String toString() {
        return "MyMessage{" +
                "myHeader=" + myHeader +
                ", body=" + body +
                '}';
    }
}

package com.cover.rpcnetty.rpc.base.vo;

public class NettyConstant {
    
    public static final String REMOTE_IP = "127.0.0.1";
    
    public static final int REMOTE_PORT = 8989;
}

ChannelPipeline初始化

package com.cover.rpcnetty.rpc.client;

import com.cover.rpcnetty.rpc.base.kryocodec.KryoDecoder;
import com.cover.rpcnetty.rpc.base.kryocodec.KryoEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 客户端Handler的初始化
 * 交给Spring托管,clientBusiHandler用注入方式实例化化加入Netty的pipeline
 */
@Service
public class ClientInit extends ChannelInitializer<SocketChannel> {

    @Autowired
    private ClientBusiHandler clientBusiHandler;
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // 剥离接收到的消息的长度字段,拿到实际的消息保温的字节数组
        ch.pipeline().addLast("frameDecoder",
                new LengthFieldBasedFrameDecoder(65535,
                        0,2,0,2));
        // 给发送出去的消息增加长度字段
        ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
        // 反序列化,将字节数组转换为消息实体
        ch.pipeline().addLast(new KryoDecoder());
        // 序列化,将消息尸体转换为字节数组准备进行网络传输
        ch.pipeline().addLast("MessageEncoder", new KryoEncoder());
        // 超时检测
        ch.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(10));
        // 发出登录请求
        ch.pipeline().addLast("LoginAuthHandler", new LoginAuthReqHandler());
        // 发出心跳请求
        ch.pipeline().addLast("HeartBeatHandler", new HeartBeatReqHandler());
        // 业务处理
        ch.pipeline().addLast("ClientBusiHandler", clientBusiHandler);
    }
}

ClientBusiHandler业务处理

package com.cover.rpcnetty.rpc.client;

import com.cover.rpcnetty.rpc.base.vo.MessageType;
import com.cover.rpcnetty.rpc.base.vo.MyHeader;
import com.cover.rpcnetty.rpc.base.vo.MyMessage;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;

@Service
@ChannelHandler.Sharable
public class ClientBusiHandler extends SimpleChannelInboundHandler<MyMessage> {
    private static final Log LOG = LogFactory.getLog(ClientBusiHandler.class);
    
    private ChannelHandlerContext ctx;
    
    private final ConcurrentHashMap<Long, BlockingQueue<Object>> responseMap 
            = new ConcurrentHashMap<>();

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        this.ctx = ctx;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) throws Exception {
            if (msg.getMyHeader() != null && msg.getMyHeader().getType() == MessageType.SERVICE_RESP.value())  {
                long sessionId = msg.getMyHeader().getSessionID();
                boolean result = (boolean) msg.getBody();
                BlockingQueue<Object> msgQueue = responseMap.get(sessionId);
                msgQueue.put(result);
            }
    }
    
    public Object send(Object message) throws InterruptedException {
        if (ctx.channel() == null || !ctx.channel().isActive()) {
            throw new IllegalStateException("和服务器还未建立起有效连接! 请稍后再试!!");
        }

        MyMessage msg = new MyMessage();
        MyHeader myHeader = new MyHeader();
        Random r = new Random();
        long sessionId = r.nextLong() + 1;
        myHeader.setSessionID(sessionId);
        myHeader.setType(MessageType.SERVICE_REQ.value());
        msg.setMyHeader(myHeader);
        msg.setBody(message);
        BlockingQueue<Object> msgQueue = new ArrayBlockingQueue<>(1);
        responseMap.put(sessionId, msgQueue);
        ctx.writeAndFlush(msg);
        Object result = msgQueue.take();
        LOG.info("获取到服务端的处理结果" + result);
        return result;
    }
}

HeartBeatHandler

package com.cover.rpcnetty.rpc.client;

import com.cover.rpcnetty.rpc.base.vo.MessageType;
import com.cover.rpcnetty.rpc.base.vo.MyHeader;
import com.cover.rpcnetty.rpc.base.vo.MyMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 心跳请求处理
 */
public class HeartBeatReqHandler extends ChannelInboundHandlerAdapter {

    private static final Log LOG = LogFactory.getLog(HeartBeatReqHandler.class);

    private volatile ScheduledFuture<?> heartBeat;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        MyMessage message = (MyMessage) msg;
        // 握手或者说登陆成功,主动发送心跳消息
        if (message.getMyHeader() != null && message.getMyHeader().getType()
                == MessageType.LOGIN_RESP.value()) {
            heartBeat = ctx.executor()
                    .scheduleAtFixedRate(new HeartBeatReqHandler
                                    .HeartBeatTask(ctx),
                    0, 5000, TimeUnit.MILLISECONDS);

            ReferenceCountUtil.release(msg);
        } else if (message.getMyHeader() != null
                && message.getMyHeader().getType() == MessageType.HEARTBEAT_RESP.value()) {
            // 如果是心跳应答
            LOG.info("Client receive server heart beat message : ----->");
            ReferenceCountUtil.release(msg);
        } else {
            // 如果是其他报文,传播给后面的Handler
            ctx.fireChannelRead(msg);
        }
    }


    private class HeartBeatTask implements Runnable {

        private final ChannelHandlerContext ctx;

        // 心跳计数,可用可不用,已经有超时处理机制
        private final AtomicInteger heartBeatCount;

        public HeartBeatTask(ChannelHandlerContext ctx) {
            this.ctx =ctx;
            heartBeatCount = new AtomicInteger(0);
        }

        @Override
        public void run() {
            MyMessage heartBeat = buildHeartBeat();
            ctx.writeAndFlush(heartBeat);
        }

        private MyMessage buildHeartBeat() {
            MyMessage message = new MyMessage();
            MyHeader myHeader = new MyHeader();
            myHeader.setType(MessageType.HEARTBEAT_REQ.value());
            message.setMyHeader(myHeader);
            return message;
        }
    }

}

LoginAuthHandler

package com.cover.rpcnetty.rpc.client;

import com.cover.rpcnetty.rpc.base.vo.MessageType;
import com.cover.rpcnetty.rpc.base.vo.MyHeader;
import com.cover.rpcnetty.rpc.base.vo.MyMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 发起登录请求
 */
public class LoginAuthReqHandler extends ChannelInboundHandlerAdapter {

    private static final Log LOG = LogFactory.getLog(LoginAuthReqHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(buildLoginReq());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        MyMessage message = (MyMessage) msg;
        // 如果是握手应答消息,需要判断是否认证成功
        if (message.getMyHeader() != null
                && message.getMyHeader().getType()
                == MessageType.LOGIN_RESP.value()) {
            byte loginResult = (byte) message.getBody();
            if (loginResult != (byte)0) {
                // 握手失败,关闭连接
                ctx.close();
            } else {
                LOG.info("Login is ok:" + message);
                ctx.fireChannelRead(msg);
            }
        } else {
            ctx.fireChannelRead(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("LoginAuthReqHandler has error......");
        cause.printStackTrace();
        ctx.fireExceptionCaught(cause);
    }

    private MyMessage buildLoginReq() {
        MyMessage myMessage = new MyMessage();
        MyHeader myHeader = new MyHeader();
        myHeader.setType(MessageType.LOGIN_REQ.value());
        myMessage.setMyHeader(myHeader);
        return myMessage;
    }
}

RpcClientFrame框架

package com.cover.rpcnetty.rpc;

import com.cover.rpcnetty.rpc.base.vo.NettyConstant;
import com.cover.rpcnetty.rpc.client.ClientBusiHandler;
import com.cover.rpcnetty.rpc.client.ClientInit;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * RPC框架的客户端代理部分,交给Spring托管
 * 1.动态代理的实现中,不再连接服务器,而是直接发送请求
 * 2.客户端网络部分的主题,包括Netty组件的初始化
 */
@Service
public class RpcClientFrame implements Runnable {
    
    private static final Log LOG = LogFactory.getLog(RpcClientFrame.class);
    
    private ScheduledExecutorService executor =
            Executors.newScheduledThreadPool(1);
    
    private Channel channel;
    
    private EventLoopGroup group = new NioEventLoopGroup();
    
    // 是否用户主动关闭连接的标志值
    private volatile boolean userClose = false;
    // 连接是否成功关闭的标志值
    private volatile boolean connected = false;

    @Autowired
    private ClientInit clientInit;
    
    @Autowired
    private ClientBusiHandler clientBusiHandler;
    
    // 远程服务的代理对象,参数为客户端要调用的服务
    public <T> T getRemoteProxyObject(Class<?> serviceInterface) {
        // 拿到一个代理对象,由这个代理对象通过网络进行实际的服务调用
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
                new Class<?>[]{serviceInterface},
                new DynProxy(serviceInterface, clientBusiHandler));
    }
    
    // 动态代理,实现对远程服务的访问
    private static class DynProxy implements InvocationHandler {
        
        private Class<?> serviceInterface;
        
        private ClientBusiHandler clientBusiHandler;
        
        public DynProxy(Class<?> serviceInterface, ClientBusiHandler clientBusiHandler) {
            this.serviceInterface = serviceInterface;
            this.clientBusiHandler = clientBusiHandler;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Map<String, Object> content = new HashMap<>();
            content.put("siName", serviceInterface.getName());
            content.put("methodName", method.getName());
            content.put("paraTypes", method.getParameterTypes());
            content.put("args", args);
            return clientBusiHandler.send(content);
        }
    }
    
    public boolean isConnected() {
        return connected;
    }
    
    // 连接服务器
    public void connect(int port, String host) throws InterruptedException {
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(clientInit);
            
            // 发起异步连接操作
            ChannelFuture future = b.connect(new InetSocketAddress(host, port)).sync();
            
            channel = future.sync().channel();
            // 连接成功后通知等待线程,连接已经建立
            synchronized (this) {
                this.connected = true;
                this.notifyAll();
            }
            future.channel().closeFuture().sync();
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (!userClose) {
                // 非用户主动关闭,说明了网络问题,需要进行重连操作
                System.out.println("发现异常,可能发生了服务器异常或网络问题, 准备进行重连....." );
                
                // 再次发起重连
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            TimeUnit.SECONDS.sleep(1);
                            try {
                                connect(NettyConstant.REMOTE_PORT, NettyConstant.REMOTE_IP);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        } catch (InterruptedException e) {
//                            throw new RuntimeException(e);
                            e.printStackTrace();
                        }
                    }
                });
            } else {
                // 用户主动关闭,释放资源
                channel = null;
                group.shutdownGracefully().sync();
                connected = false;
            }
        }
    } 
    

    @Override
    public void run() {
        try {
            connect(NettyConstant.REMOTE_PORT, NettyConstant.REMOTE_IP);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void close() {
        userClose = true;
        channel.close();
    }
    
    @PostConstruct
    public void startNet() throws InterruptedException {
        new Thread(this).start();
        while (!this.isConnected()) {
            synchronized (this) {
                this.wait();
            }
        }
        
        LOG.info("网络通信已准备好,可以进行业务操作了............");
    }
    
    @PreDestroy
    public void stopNet() {
        close();
    }
}

业务测试

@SpringBootTest
class RpcNettyClientApplicationTests {

    @Autowired
    private SendSms sendSms;
    @Test
    void contextLoads() throws InterruptedException {
        long start = System.currentTimeMillis();
        // 发送邮件
        UserInfo userInfo = new UserInfo("Cover", "133");
        System.out.println("Send mail:" + sendSms.sendMail(userInfo));
        System.out.println("共耗时 :" + (System.currentTimeMillis() - start));
        Thread.sleep(3000);
    }


}

Client文件结构

手写百万并发连接的RPC框架之Netty篇_第2张图片

Server端

maven

        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>4.1.75.Finalversion>
        dependency>

        <dependency>
            <groupId>de.javakaffeegroupId>
            <artifactId>kryo-serializersartifactId>
            <version>0.42version>
        dependency>

UserInfo实体类

package com.cover.rpcnetty.remote.vo;

import java.io.Serializable;

/**
 * 用户的实体类,已实现序列化
 * @author xieh
 * @date 2024/02/04 21:02
 */
public class UserInfo implements Serializable {
    private String name;

    private String phone;

    public UserInfo() {
    }

    public UserInfo(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

SendSms接口及实现类

package com.cover.rpcnetty.remote;

import com.cover.rpcnetty.remote.vo.UserInfo;

/**
 * 短信息发送接口
 * @author xieh
 * @date 2024/02/04 21:01
 */

public interface SendSms {

    // 发送短信
    boolean sendMail(UserInfo userInfo);
}

package com.cover.rpcnetty.rpc.sms;

import com.cover.rpcnetty.remote.SendSms;
import com.cover.rpcnetty.remote.vo.UserInfo;
import org.springframework.stereotype.Service;

/**
 * 短信息发送服务的实现
 * @author xieh
 * @date 2024/02/04 21:05
 */
@Service
public class SendSmsImpl implements SendSms {

    @Override
    public boolean sendMail(UserInfo userInfo) {
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("以发送短信息给:" + userInfo.getName() + "到【" + userInfo.getPhone());
        return true;
    }
}

Kryo序列化相关

与Client一样,这里不再赘述

消息实体

与Client一样,这里不再赘述

注册服务

/**
 * 注册服务到本地缓存
 * @author xieh
 * @date 2024/02/04 21:09
 */
@Service
public class RegisterService {

    // 本地可以提供服务的一个容器
    private static final Map<String, Class> serviceCache = new ConcurrentHashMap<>();

    // 注册本服务
    public void regService(String serviceName, Class impl) {
        serviceCache.put(serviceName, impl);
    }

    // 获取服务
    public Class getLocalService(String serviceName) {
        return serviceCache.get(serviceName);
    }
}

RpcServerFrame框架

package com.cover.rpcnetty.rpc.base;

import com.cover.rpcnetty.remote.SendSms;
import com.cover.rpcnetty.rpc.base.vo.NettyConstant;
import com.cover.rpcnetty.rpc.server.ServerInit;
import com.cover.rpcnetty.rpc.sms.SendSmsImpl;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * RPC框架的服务端部分,交给Spring托管
 * 包括Netty组件的初始化,监听端口,实际服务的注册等等
 *
 * @author xieh
 * @date 2024/02/04 21:07
 */
@Service
public class RpcServerFrame implements Runnable{
    @Autowired
    private RegisterService registerService;

    @Autowired
    private ServerInit serverInit;

    private static final Log LOG = LogFactory.getLog(RpcServerFrame.class);
    private EventLoopGroup bossGroup = new NioEventLoopGroup();
    private EventLoopGroup workerGroup = new NioEventLoopGroup();


    public void bind() throws InterruptedException {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(serverInit);

        // 绑定端口,同步等待成功
        b.bind(NettyConstant.REMOTE_PORT).sync();
        LOG.info("网络服务已准备好,可以进行业务操作了........:"
                + (NettyConstant.REMOTE_IP + ":" + NettyConstant.REMOTE_PORT));
    }
    @Override
    public void run() {
        try {
            bind();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @PostConstruct
    public void startNet() {
        registerService.regService(SendSms.class.getName(), SendSmsImpl.class);
        new Thread(this).start();
    }

    @PreDestroy
    public void stopNet() throws InterruptedException {
        bossGroup.shutdownGracefully().sync();
        workerGroup.shutdownGracefully().sync();
    }


}

HeartBeatHandler

package com.cover.rpcnetty.rpc.server;

import com.cover.rpcnetty.rpc.base.vo.MessageType;
import com.cover.rpcnetty.rpc.base.vo.MyHeader;
import com.cover.rpcnetty.rpc.base.vo.MyMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 心跳应答
 * @author xieh
 * @date 2024/02/04 21:34
 */
public class HeartBeatRespHandler extends ChannelInboundHandlerAdapter {

    private static final Log LOG
   			= LogFactory.getLog(HeartBeatRespHandler.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        MyMessage message = (MyMessage) msg;
        // 返回心跳应答消息
        if (message.getMyHeader() != null && message.getMyHeader().getType() == MessageType.HEARTBEAT_REQ.value()) {
            LOG.info("Receive client heart beat message : ----->");
            MyMessage heartBeat = buildHeartBeat();
            LOG.info("Send heart beat response message to : ----->");
            ctx.writeAndFlush(heartBeat);
            ReferenceCountUtil.release(msg);
        } else {
            ctx.fireChannelRead(msg);
        }
//        super.channelRead(ctx, msg);
    }

    private MyMessage buildHeartBeat() {
        MyMessage message = new MyMessage();
        MyHeader myHeader = new MyHeader();
        myHeader.setType(MessageType.HEARTBEAT_RESP.value());
        message.setMyHeader(myHeader);
        return message;
    }
}

LoginAuthHandler

package com.cover.rpcnetty.rpc.server;

import com.cover.rpcnetty.rpc.base.vo.MessageType;
import com.cover.rpcnetty.rpc.base.vo.MyHeader;
import com.cover.rpcnetty.rpc.base.vo.MyMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 登陆检查
 * @author xieh
 * @date 2024/02/04 21:34
 */
public class LoginAuthRespHandler extends ChannelInboundHandlerAdapter {
    private final static Log LOG
                = LogFactory.getLog(LoginAuthRespHandler.class);

    // 用以检查用户是否重复登陆的缓存
    private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<>();

    // 用户登录的白名单
    private String[] whiteList = {"127.0.0.1"};

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        MyMessage message = (MyMessage) msg;
        // 如果是握手请求消息,处理,其他消息,透传
        if (message.getMyHeader() != null
            && message.getMyHeader().getType()
                == MessageType.LOGIN_REQ.value()) {
            String nodeIndex = ctx.channel().remoteAddress().toString();
            MyMessage loginResp = null;
            // 重复登录,拒绝
            if (nodeCheck.containsKey(nodeIndex)) {
                loginResp = buildResponse((byte) -1);
            } else {
                // 检查用户是否在白名单中,在则允许登录,并写入缓存
                InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();

                String ip = address.getAddress().getHostAddress();
                boolean isOK = false;
                for (String WIP : whiteList) {
                    if (WIP.equals(ip)) {
                        isOK = true;
                        break;
                    }
                }
                loginResp = isOK ? buildResponse((byte) 0) : buildResponse((byte) -1);
                if (isOK) {
                    nodeCheck.put(nodeIndex, true);
                }
            }

            LOG.info("The login response is :" + loginResp
                    + " body [" + loginResp.getBody() + " ]");
            ctx.writeAndFlush(loginResp);
            ReferenceCountUtil.release(msg);
        }
        // 注释后,可延时消息不往下传递的情况
        else {
            ctx.fireChannelRead(msg);
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        super.exceptionCaught(ctx, cause);
        // 删除缓存
        nodeCheck.remove(ctx.channel().remoteAddress().toString());
        ctx.close();
        ctx.fireExceptionCaught(cause);

    }

    private MyMessage buildResponse(byte result) {
        MyMessage message = new MyMessage();
        MyHeader myHeader = new MyHeader();
        myHeader.setType(MessageType.LOGIN_RESP.value());
        message.setMyHeader(myHeader);
        message.setBody(result);
        return message;
    }
}

ServerBusiHandler

package com.cover.rpcnetty.rpc.server;

import com.cover.rpcnetty.rpc.base.RegisterService;
import com.cover.rpcnetty.rpc.base.vo.MessageType;
import com.cover.rpcnetty.rpc.base.vo.MyHeader;
import com.cover.rpcnetty.rpc.base.vo.MyMessage;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 业务处理类
 * channelRead0方法中有了实际的业务处理,负责具体的业务方法的调用
 * @author xieh
 * @date 2024/02/04 21:34
 */
@Service
@ChannelHandler.Sharable
public class ServerBusiHandler extends SimpleChannelInboundHandler<MyMessage> {

    private static final Log LOG
            = LogFactory.getLog(ServerBusiHandler.class);

    @Autowired
    private RegisterService registerService;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) throws Exception {
        LOG.info(msg);
        MyMessage message = new MyMessage();
        MyHeader myHeader = new MyHeader();
        myHeader.setSessionID(msg.getMyHeader().getSessionID());
        myHeader.setType(MessageType.SERVICE_RESP.value());
        message.setMyHeader(myHeader);

        Map<String, Object> content = (HashMap<String,Object>)msg.getBody();
        // 方法所在类名接口名
        String serviceName = (String) content.get("siName");
        // 方法的名字
        String methodName = (String) content.get("methodName");
        Class<?>[] paramTypes = (Class<?>[]) content.get("paraTypes");
        //方法的入参的值
        Object[] args = (Object[]) content.get("args");
        // 从容器中拿到服务的Class对象
        Class serviceClass = registerService.getLocalService(serviceName);
        if (serviceClass == null) {
            throw new ClassNotFoundException(">>>>>>>" + serviceName + "not found");
        }

        // 通过反射,执行实际的服务
        Method method = serviceClass.getMethod(methodName, paramTypes);
        boolean result = (boolean) method.invoke(serviceClass.newInstance(), args);
        message.setBody(result);
        ctx.writeAndFlush(message);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        LOG.info(ctx.channel().remoteAddress() + "主动断开了连接");
//        super.channelInactive(ctx);
    }
}

ChannelPipeline

package com.cover.rpcnetty.rpc.server;

import com.cover.rpcnetty.rpc.base.kryocodec.KryoDecoder;
import com.cover.rpcnetty.rpc.base.kryocodec.KryoEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 服务端Handler的初始化
 * 交给Spring托管,ServerBusiHandler用注入方式实例化后加入Netty的pipeline
 * @author xieh
 * @date 2024/02/04 21:11
 */
@Service
public class ServerInit extends ChannelInitializer<SocketChannel> {

    @Autowired
    private ServerBusiHandler serverBusiHandler;

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // Netty提供的日志打印Handler,可以展示发送接收出去的字节
        ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));

        // 剥离接收到的消息的长度字段,拿到实际的消息报文的字节数组
        ch.pipeline().addLast("frameDecoder",
                new LengthFieldBasedFrameDecoder(65535,
                        0,2,
                        0,2));
        // 给发送出去的消息增加长度字段
        ch.pipeline().addLast("frameEncoder",
                new LengthFieldPrepender(2));
        // 反序列化,将字节数组转换为消息实体
        ch.pipeline().addLast(new KryoDecoder());
        // 序列化
        ch.pipeline().addLast("MessageEncoder", new KryoEncoder());
        // 超时检测
        ch.pipeline().addLast("readTimeoutHandler",
                new ReadTimeoutHandler(50));
        // 登陆应答
        ch.pipeline().addLast(new LoginAuthRespHandler());
        // 心跳应答
        ch.pipeline().addLast("HeartBeatHandler",
                new HeartBeatRespHandler());
        // 服务端业务处理
        ch.pipeline().addLast("ServerBusiHandler",
                serverBusiHandler);

    }
}

文件结构

手写百万并发连接的RPC框架之Netty篇_第3张图片

结果展示

Server
手写百万并发连接的RPC框架之Netty篇_第4张图片
Client
手写百万并发连接的RPC框架之Netty篇_第5张图片
在Client访问时会打印出相关的报文
手写百万并发连接的RPC框架之Netty篇_第6张图片

你可能感兴趣的:(Netty,网络IO,rpc,网络协议,网络,java)