前言
前面我们已经介绍了,使用JDK原生自带的Socket门面模式手写了注册中心、远程客户端、远程服务端三个模块来构成一个RPC框架,但是性能不是很高,远远无法支撑起大型的分布式系统之间的调用,于是我们今天来分享下如何用Netty来手写RPC框架,并用它来支撑起百万并发,为了便于理解,暂时先不实现注册中心。在该项目中还跟Spring进行了整合。
结构示意图
核心组件
- LengthFieldBasedFrameDecoder:解决网络传输中的粘包半包问题
- KryoEncoder/Decoder:负责网络数据的序列化和反序列化的功能,性能比较高
- ReadTimeoutHandler: ByteBuf数据读取超时处理器,如果长时间没有数据可读,则会抛出异常
- LoginAuthHandler:服务端维护一个白名单,客户端发出认证请求时需要认证,通过才可以建立连接
- HeartBeatHandler:心跳检测机制,可以用来更新客户端缓存的服务提供者列表
- BusiHandler:我们具体调用的业务
- Jdk动态代理:由这个代理对象通过网络进行实际的调用
Client端
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.75.Final</version>
</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文件结构
Server端
maven
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.75.Final</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.42</version>
</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);
}
}
文件结构
结果展示
Server
Client
在Client访问时会打印出相关的报文