双向rpc

原创 2016年11月27日 21:48:14

提起RPC(异步过程调用),大家马上想到的是java平台自带的RMI框架。那么什么是rpc呢,简单来说,就是服务端提供一系列接口,客户端可以透明的调用这些接口,就好像客户端直接调用本地的方法一样。

RMI虽然拥有贵族血统,奈何java自带的序列化方式速度较慢,占用内存也较多。在开源社区涌现了一批又一批高效的RPC框架,典型代表有WebService,Hession,Dubbo等等。

RPC本身采用C/S模式,注定了RPC天生是单向通信的。也就是说,客户端可以调用服务端的rpc接口,但反过来,服务端无法主动调用客户端的接口。在实际环境上,服务端需要主动通知客户端进行某些操作。因此实现双向的rpc框架是有必要的。

实现双向的rpc要求双方同时属于服务端和客户端。每一个节点都需要本身注册服务接口,同时作为客户端注册链接到另一个节点的链路。虽然有点麻烦,但不失为一个经得起考验的解决方案。

本文采用的RPC框架是baidu基于JProtobuff(通过注解实现Protobuff)的rpc实现,git地址为Jprotobuf-rpc-socket

下面分别介绍该框架所用到的主要类(代码略长^_^)

1.定义rpc服务接口(RpcTransferService.java)。该接口只需要一个服务方法即可。

package com.kingston.rpc.transfer;

import com.baidu.jprotobuf.pbrpc.ProtobufRPC;
import com.kingston.rpc.event.RpcEvent;

public interface RpcTransferService {

	@ProtobufRPC(serviceName = "RpcTransferService", onceTalkTimeout = 200)
	void transferRpc(RpcEvent evnet);
}
2.定义rpc服务接口的具体实现(RpcTransferServiceImpl.java)。

package com.kingston.rpc.transfer;

import com.baidu.jprotobuf.pbrpc.ProtobufRPCService;
import com.kingston.rpc.MessageDispatcher;
import com.kingston.rpc.event.RpcEvent;

public class RpcTransferServiceImpl {

	@ProtobufRPCService(serviceName = "RpcTransferService")
	public	void transferRpc(RpcEvent event){
		MessageDispatcher.INSTANCE.onEventReceived(event);
	}
	
} 
3.定义消息载体(RpcEvent.java)。

关于该类,需要注意的是在数据传输时,将所有的属性值序列化后存储在data字段。并且为了能够将属性的类型保持在可控的范围内,定义了SUPPORT_TYPES字段保存所有支持的数据类型。

package com.kingston.rpc.event;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
import com.kingston.rpc.ServerConfig;
import com.kingston.rpc.utils.JsonUtils;

public class RpcEvent {

	@Protobuf
	private String id;
	
	@Protobuf
	private int sourceServerId;
	
	@Protobuf
	private int command;
	
	@Protobuf
	private String data;
	
	@Protobuf
	private long roleId;
	
//	private RpcEvent callback;
	
	private static AtomicLong ID_GENERATOR = new AtomicLong();
	
	
	private Map<String,Object> attrbutes = new HashMap<>();
	
	/** attrbutes支持的value类型  */
	private static final  Set<Class<?>> SUPPORT_TYPES = new HashSet<Class<?>>();
	
	static {
		SUPPORT_TYPES.add(Boolean.class);
		SUPPORT_TYPES.add(Byte.class);
		SUPPORT_TYPES.add(Character.class);
		SUPPORT_TYPES.add(Short.class);
		SUPPORT_TYPES.add(Integer.class);
		SUPPORT_TYPES.add(Float.class);
		SUPPORT_TYPES.add(Double.class);
		SUPPORT_TYPES.add(String.class);
	}
	
	public RpcEvent(){
		
	}
	
	public static RpcEvent build(int command,long roleId) {
		RpcEvent event = new RpcEvent();
		event.command  = command;
		event.roleId   = roleId;
		event.sourceServerId = ServerConfig.serverId;
		event.id = getNextSeq(event.sourceServerId);
		return event;
	}
	
	public static RpcEvent createResponse(String requestId) {
		RpcEvent event = new RpcEvent();
		event.id = requestId;
		return event;
	}
	
	
	public String getId() {
		return this.id;
	}
	
	public long getRoleId() {
		return this.roleId;
	}
	
	public int getSourceServerId(){
		return this.sourceServerId;
	}
	
	public static String getNextSeq(int serverId){
		return serverId + "_" + ID_GENERATOR.getAndIncrement();
	}
	
	public int getCommand(){
		return this.command;
	}
	
	/**
	 * 将attribute序列为json格式
	 */
	public void serialize() {
		this.data = JsonUtils.object2String(this.attrbutes);
	}
	
	/**
	 * 反序列化为attributes
	 */
	public void unserialize() {
		this.attrbutes = JsonUtils.string2Object(this.data, Map.class);
	}
	
	public void setAttribute(String key, Object value) {
		Objects.requireNonNull(key);
		Objects.requireNonNull(value);
		if (!SUPPORT_TYPES.contains(value.getClass())) {
			throw new IllegalArgumentException("属性不支持"+value.getClass());
		}
		this.attrbutes.put(key, value);
	}
	
	public Object getAttibute(String key) {
		return this.attrbutes.get(key);
	}
	
}

4.定义消息分发器(MessageDispatcher.java)

package com.kingston.rpc;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.kingston.rpc.event.RpcEvent;
import com.kingston.rpc.handler.LoginEventHandler;
import com.kingston.rpc.handler.MessageHandler;
import com.kingston.rpc.handler.RpcHandler;
import com.kingston.rpc.utils.ClassFilter;
import com.kingston.rpc.utils.ClassScanner;

/**
 * 消息分发器
 */
public enum MessageDispatcher {
	
	INSTANCE;
	
	private int CORE = Runtime.getRuntime().availableProcessors()/2;
	
	/** 发送消息的线程池 */
	private IOHandler[] ioHandlers = new IOHandler[CORE]; 
	/** 处理消息的线程池 */
	private EventHandler[] eventHandlers = new EventHandler[CORE]; 
	private final Map<Integer,MessageHandler> RPC_EVENT_HANDLERS = new HashMap<>();
	
	private MessageDispatcher() {
		initHandlerPools();
		initHandlers();
	}
	
	
	/**
	 * 初始化所有的handler处理器
	 */
	private void initHandlers() {
		ClassFilter filter = new ClassFilter() {
			@Override
			public boolean accept(Class<?> clazz) {
				return MessageHandler.class.isAssignableFrom(clazz)
						&& !clazz.isInterface();
			}
		};
		Set<Class<?>> handlers = ClassScanner.getClasses("com.kingston",filter);
		for (Class c:handlers) {
			registerHandler(new LoginEventHandler());
		}
	}
	
	
	/**
	 * 初始化读写任务线程池
	 */
	private void initHandlerPools() {
		for(int i=0;i<CORE;i++) {
			IOHandler handler = new IOHandler();
			ioHandlers[i] = handler;
			new Thread(handler).start();
		}
		
		for(int i=0;i<CORE;i++) {
			EventHandler handler = new EventHandler();
			eventHandlers[i] = handler;
			new Thread(handler).start();
		}
	}
	
	
	public void onEventReceived(RpcEvent event){
		int index = (int) (event.getRoleId()%CORE);
		eventHandlers[index].addEvent(event);
	}
	
	public void sendRequest(Session session, RpcEvent request) {
		if(session == null || request == null) {
			return ;
		}
		int key = (int) request.getRoleId();
		int index = key%CORE;
		ioHandlers[index].addTask(session,request);
		
	}
	
	/**
	 * 注册handler
	 * @param handler
	 */
	public void registerHandler(MessageHandler handler){
		RpcHandler annotation = handler.getClass().getAnnotation(RpcHandler.class);
		int key = annotation.cmd();
		if(RPC_EVENT_HANDLERS.containsKey(key)){
			throw new IllegalStateException("handler key repeated");
		}
		RPC_EVENT_HANDLERS.put(key, handler);
	}
	
	private class IOHandler implements Runnable {

		/** 发送消息的生产者队列 */
		private BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
		
		void addTask(Session client, RpcEvent event) {
			Task newTask = new Task();
			newTask.client = client;
			newTask.event = event;
			this.queue.add(newTask);
		}
		
		@Override
		public void run() {
			while(true) {
				try{
					Task task = queue.take();
					if(task != null){
						handleTask(task);
					}
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
		
		private void handleTask(Task task) {
			RpcEvent event = task.event;
			Session client = task.client;
			MessageHandler handler = RPC_EVENT_HANDLERS.get(event.getCommand());
			client.write(event);
		}
		
	}
	
	
	private class Task {
		
		private Session client;
		
		private RpcEvent event;
		
	}
	
	private class EventHandler implements Runnable {

		/** 接受消息的消费者队列 */
		private BlockingQueue<RpcEvent> queue = new LinkedBlockingQueue<>();
		
		public void addEvent(RpcEvent event) {
			if(event != null) {
				queue.add(event);
			}
		}
		
		@Override
		public void run() {
			while(true) {
				try{
					RpcEvent event = queue.take();
					if(event == null) {
						continue;
					}
					String key = event.getId();
					handlerReceive(event);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
		
		
		private void handlerReceive(RpcEvent event) {
			MessageHandler handler = RPC_EVENT_HANDLERS.get(event.getCommand());
			handler.receiveEvent(event);
		}
		
	}
}

该类需要特别注意的是发送和处理消息所采用的线程模型。以处理消息的线程池eventHandlers为例,在接受消息的时候,通过roleid字段,将事件映射到特定的线程队列。这种做法能够保证玩家的个人请求有序,省去同步的消耗。


5.定义消息类型与处理器的映射关系

消息的类型绑定在RpcEvent对象的command字段。对于每一种类型,都有它对应的处理器。我们可以通过注解来绑定两者之间的关系。

5.1定义处理器接口(MessageHandler.java)

package com.kingston.rpc.handler;

import com.kingston.rpc.event.RpcEvent;

public interface MessageHandler {

	/**
	 * client发送前的数据处理
	 * @param event 发送的event对象
	 * @param attachment 附加数据
	 */
	void transmitEvent(RpcEvent event, Object attachment);
	
	
	/**
	 * server端收到数据的处理
	 * @param event
	 */
	void receiveEvent(RpcEvent event);

}
5.2 定义处理器注解(RpcHandler.java)

package com.kingston.rpc.handler;

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

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcHandler {
	
	int cmd() default 0;
}
5.3 定义具体的处理器子类(LoginEventHandler.java),以登录逻辑作为示例。

package com.kingston.rpc.handler;

import com.kingston.rpc.command.CommandKinds;
import com.kingston.rpc.event.RpcEvent;

@RpcHandler(cmd=CommandKinds.LOGIN)
public class LoginEventHandler implements MessageHandler{

	@Override
	public void transmitEvent(RpcEvent event, Object attachment) {
		
	}
	
	@Override
	public void receiveEvent(RpcEvent event) {
		System.err.println("-----receive message----------");
	}


}

5.4  定义command类型的常量池(CommandKinds.java)

package com.kingston.rpc.command;

/**
 * command常量池
 */
public class CommandKinds {
	
	private CommandKinds() {} //禁止实例化
	
	/**登录*/
	public static final int LOGIN = 1;

	
}


6 为了让程序在初始化的时候智能匹配类型与对应的处理器,可以在程序启动时使用类扫描器,扫描制定的注解。

定义类扫描工具(ClassScanner.java)。该工具类是在网上搜索的,自己加了个过滤器。
消息分发器MessageDispatcher的initHandlers() 在初始化的时候就会扫描并注册所有的handlers。

package com.kingston.rpc.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.kingston.rpc.handler.MessageHandler;

/**
 * 类扫描器
 */
public class ClassScanner {

	/**
	 * 默认过滤器(无实现)
	 */
	private final static ClassFilter defaultFilter = new ClassFilter() {
		@Override
		public boolean accept(Class<?> clazz) {
			return true;
		}
	};

	/**
	 * 扫描目录下的所有class文件
	 * @param pack 包路径
	 * @return
	 */
	public static Set<Class<?>> getClasses(String pack) {
		return getClasses(pack,defaultFilter);
	}


	/**
	 * 扫描目录下的所有class文件
	 * @param pack 包路径
	 * @param filter 自定义类过滤器
	 * @return
	 */
	public static Set<Class<?>> getClasses(String pack, ClassFilter filter) {  
		Set<Class<?>> result = new LinkedHashSet<Class<?>>();  
		// 是否循环迭代  
		boolean recursive = true;  
		// 获取包的名字 并进行替换  
		String packageName = pack;  
		String packageDirName = packageName.replace('.', '/');  
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things  
		Enumeration<URL> dirs;  
		try {  
			dirs = Thread.currentThread().getContextClassLoader().getResources(  
					packageDirName);  
			// 循环迭代下去  
			while (dirs.hasMoreElements()) {  
				// 获取下一个元素  
				URL url = dirs.nextElement();  
				// 得到协议的名称  
				String protocol = url.getProtocol();  
				// 如果是以文件的形式保存在服务器上  
				if ("file".equals(protocol)) {  
					// 获取包的物理路径  
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");  
					// 以文件的方式扫描整个包下的文件 并添加到集合中  
					findAndAddClassesInPackageByFile(packageName, filePath,  
							recursive, result,filter);  
				} else if ("jar".equals(protocol)) {  
					// 如果是jar包文件  
					Set<Class<?>> jarClasses = findClassFromJar(url,packageName,packageDirName,recursive,filter);
					result.addAll(jarClasses);
				}  
			}  
		} catch (IOException e) {  
			e.printStackTrace();  
		}  

		return result;  
	}  

	private static Set<Class<?>> findClassFromJar(URL url,String packageName,String packageDirName,
			boolean recursive, ClassFilter filter) {
		Set<Class<?>> result = new LinkedHashSet<Class<?>>(); 
		try {  
			// 获取jar  
			JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();  
			// 从此jar包 得到一个枚举类  
			Enumeration<JarEntry> entries = jar.entries();  
			// 同样的进行循环迭代  
			while (entries.hasMoreElements()) {  
				// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件  
				JarEntry entry = entries.nextElement();  
				String name = entry.getName();  
				// 如果是以/开头的  
				if (name.charAt(0) == '/') {  
					// 获取后面的字符串  
					name = name.substring(1);  
				}  
				// 如果前半部分和定义的包名相同  
				if (name.startsWith(packageDirName)) {  
					int idx = name.lastIndexOf('/');  
					// 如果以"/"结尾 是一个包  
					if (idx != -1) {  
						// 获取包名 把"/"替换成"."  
						packageName = name.substring(0, idx)  
								.replace('/', '.');  
					}  
					// 如果可以迭代下去 并且是一个包  
					if ((idx != -1) || recursive) {  
						// 如果是一个.class文件 而且不是目录  
						if (name.endsWith(".class")  
								&& !entry.isDirectory()) {  
							// 去掉后面的".class" 获取真正的类名  
							String className = name.substring(packageName.length() + 1,
									name.length() - 6);  
							try {  
								// 添加到classes  
								Class c = Class.forName(packageName+'.'+className);
								if (filter.accept(c)) {
									result.add(c);  
								}
							} catch (ClassNotFoundException e) {  
								e.printStackTrace();  
							}  
						}  
					}  
				}  
			}  
		} catch (IOException e) {  
			e.printStackTrace();  
		}  
		return result;
	}

	private static void findAndAddClassesInPackageByFile(String packageName,  
			String packagePath, final boolean recursive, Set<Class<?>> classes,
			ClassFilter filter) {  
		// 获取此包的目录 建立一个File  
		File dir = new File(packagePath);  
		// 如果不存在或者 也不是目录就直接返回  
		if (!dir.exists() || !dir.isDirectory()) {  
			// log.warn("用户定义包名 " + packageName + " 下没有任何文件");  
			return;  
		}  
		// 如果存在 就获取包下的所有文件 包括目录  
		File[] dirfiles = dir.listFiles(new FileFilter() {  
			// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)  
			public boolean accept(File file) {  
				return (recursive && file.isDirectory())  
						|| (file.getName().endsWith(".class"));  
			}  
		});  
		// 循环所有文件  
		for (File file : dirfiles) {  
			// 如果是目录 则继续扫描  
			if (file.isDirectory()) {  
				findAndAddClassesInPackageByFile(packageName + "."  
						+ file.getName(), file.getAbsolutePath(), recursive,  
						classes,filter);  
			} else {  
				// 如果是java类文件 去掉后面的.class 只留下类名  
				String className = file.getName().substring(0,  
						file.getName().length() - 6);  
				try {  
					// 添加到集合中去  
					Class clazz = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className);
					if (filter.accept(clazz)) {
						classes.add(clazz);
					}
				} catch (ClassNotFoundException e) {  
					// log.error("添加用户自定义视图类错误 找不到此类的.class文件");  
					e.printStackTrace();  
				}  
			}  
		}  
	} 

}
扫描过滤器的接口定义(ClassFilter.java)

package com.kingston.rpc.utils;

/**
 * 类扫描的过滤器
 */
public interface ClassFilter {

	/**
	 * 返回true表示添加到目标集合
	 * @param clazz
	 * @return
	 */
	boolean accept(Class<?> clazz);
}

7. 定义服务节点的链接抽象(Session.java)。该类封装了rpc服务接口的发送与链接。
package com.kingston.rpc;

import com.baidu.jprotobuf.pbrpc.client.ProtobufRpcProxy;
import com.baidu.jprotobuf.pbrpc.transport.RpcClient;
import com.kingston.rpc.event.RpcEvent;
import com.kingston.rpc.transfer.RpcTransferService;

public class Session {

	private RpcClient client;
	
	private RpcTransferService rpcTransfer;
	
	private ProtobufRpcProxy<RpcTransferService> rpcProxy;
	
	private String ip;
	
	private int port;
	
	private long createTime;
	
	
	public Session(String ip,int port) {
		this.ip = ip;
		this.port = port;
		this.createTime = System.currentTimeMillis();
		this.connect();
		
	}
	
	private void connect(){
		RpcClient rpcClient = new RpcClient();
		// 创建EchoService代理
		ProtobufRpcProxy<RpcTransferService> pbrpcProxy = new ProtobufRpcProxy<RpcTransferService>(rpcClient, RpcTransferService.class);
		pbrpcProxy.setHost(ip);
		pbrpcProxy.setPort(port);
		// 动态生成代理实例
		RpcTransferService echoService = pbrpcProxy.proxy();
		pbrpcProxy.setLookupStubOnStartup(false);
		this.client = rpcClient;
		this.rpcProxy = pbrpcProxy;
		this.rpcTransfer = echoService;
	}
	
	
	public void write(RpcEvent message){
		rpcTransfer.transferRpc(message);
	}
	
}

8. 定义服务节点的路由表(Router.java)

package com.kingston.rpc;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class Router {

	/** serverid与session的映射*/
	private static ConcurrentMap<Integer,Session> CLIENTS = new ConcurrentHashMap<>();
	/** 路由节点映射表*/
	private static ConcurrentMap<Integer,Node> NODES = new ConcurrentHashMap<>();
	
	//测试节点信息
	static{
		NODES.put(22, new Node(22,"localhost",8122));
		NODES.put(23, new Node(23,"localhost",8123));
	}
	
	public static Session getClientSessionBy(int serverId){
		Session client = CLIENTS.get(serverId);
		if(client != null) {
			return client;
		}
		synchronized (Router.class) {
			client = CLIENTS.get(serverId);
			if(client != null) {
				return client;
			}
			Node node = NODES.get(serverId);
			client = new Session(node.ip, node.port);
			return client;
		}
	}
	
	private static class Node {
		int serverId;
		String ip;
		int port;
		
		public Node(int serverId, String ip, int port) {
			super();
			this.serverId = serverId;
			this.ip = ip;
			this.port = port;
		}
		
	}

}

9. 定义服务配置类(ServerConfig.java)

package com.kingston.rpc;

public class ServerConfig {
	
	/** 服务器id*/
	public static int serverId = 22;  
	/** rpc服务端口*/
	public static int serverPort = 8122;

}

10 定义json的序列化工具(JsonUtils.java)。采用jackson工具库。

package com.kingston.rpc.utils;

import java.io.StringWriter;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

public class JsonUtils {
	
	private static final ObjectMapper  mapper = new ObjectMapper();
	
	private static TypeFactory typeFactory = TypeFactory.defaultInstance();
	
	
	public static String object2String(Object object) {
		StringWriter writer = new StringWriter();
		try{
			mapper.writeValue(writer, object);
		}catch(Exception e) {
			e.printStackTrace();
			return null;
		}
		return writer.toString();
	}
	
	
	public static <T> T string2Object(String content, Class<T> clazz) {
		JavaType type = typeFactory.constructType(clazz);
		try{
			return (T)mapper.readValue(content, type);
		}catch(Exception e) {
			e.printStackTrace();
			return null;
		}
	}

}

11. 定义rpc服务类(CrossRpcServer.java)

package com.kingston.rpc.service;

import com.baidu.jprotobuf.pbrpc.transport.RpcServer;
import com.kingston.rpc.ServerConfig;
import com.kingston.rpc.transfer.RpcTransferServiceImpl;

public class CrossRpcServer {

	private RpcServer server;
	
	public void start() {
		init();
		System.err.println("rpc server start at port " + getServerPort());
	}
	
	private void init() {
		try{
			server = new RpcServer();
			server.getRpcServerOptions().setKeepAliveTime(60*5);
			server.registerService(new RpcTransferServiceImpl());
			server.start(getServerPort());
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public int getServerPort() {
		return ServerConfig.serverPort;
	}
	
	
}


12.服务器启动入口(ServerMain.java)

package com.kingston.rpc.service;

public class ServerMain {

	public static void main(String[] args) {
		
		new CrossRpcServer().start();
	}
}


13.定义客户端入口(ClientMain.java)

package com.kingston.rpc.client;

import com.kingston.rpc.MessageDispatcher;
import com.kingston.rpc.Session;
import com.kingston.rpc.command.CommandKinds;
import com.kingston.rpc.event.RpcEvent;
import com.kingston.rpc.service.CrossRpcServer;

public class ClientMain {

	public static void main(String[] args) {
		//客户端也需要注册rpc服务
		new CrossRpcServer().start();
		Session client = new Session("localhost", 8122);
		RpcEvent request = RpcEvent.build(CommandKinds.LOGIN, 1);
		MessageDispatcher.INSTANCE.sendRequest(client, request);
	}
}


测试方法:

将同样的代码部署在两个工程里,一个工程启动ServerMain,一个工程启动ClientMain。

需要注意的地方:

1.客户端本身也需要注册rpc服务端口;

2.ServerConfig配置里serverid与serverPort要避免冲突


一个简单的双向rpc就打造完成了。

但还有需要优化的地方。比如客户端在发送消息之后,可能需要服务端执行逻辑后能把执行结果回调给客户端。也就是说,客户端需要实现回调功能。限于篇幅,这里就不再贴代码了。




版权声明:本文为博主原创文章,未经博主允许不得转载。

golang使用gRPC创建双向流模式

gRPC库介绍gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,...
  • Paddy90
  • Paddy90
  • 2017年05月03日 14:31
  • 2552

让Thrift支持双向通信

【问题】 Thrift采用了C/S模型,不支持双向通信:client只能远程调用server端的RPC接口,但client端则没有RPC供server端调用,这意味着,client端能够...

Thrift双向异步远程调用(外文翻译)学习第一步

最近在钻研thrift协议. 经过浅薄的学习后发现thrift在设计时是针对应答的模式,这样的方式使得服务端显得很被动,因为只有在客户端发送过来请求时服务端才能返回一条消息到客户端.经过在网上查询资...

简单RPC之Socket实现

最近看到Dubbo大神写得使用Socket实现的简单的RPC调用,对RPC的理解更简单了,然后根据大神的代码自己也重构了一下。RPC Server端代码,主要是使用ServerSocket获得rpc调...

游戏服务端防御式开发

游戏服务端承担着游戏复杂业务逻辑实现,玩家数据持久化等重要作用。作为一个合格的服务端开发人员,我们有必要遵守一些好的防御手段,让自己的代码少踩些坑,或者当出现了bug,能够在第一时间进行抢救。本文是我...

手游服务端框架之GM金手指的设计

游戏开发需要一些命令,能够像金山游侠这种软件一样,修改游戏里的玩家或公共服务的数据。在游戏项目里,这些命令统称为GM命令。本文将介绍实现gm系统的一种方式。...

dubbo rest rpc相关jar包

  • 2017年11月21日 15:02
  • 1.33MB
  • 下载

rpc项目工程

  • 2017年11月19日 13:57
  • 108KB
  • 下载

异步机制(Asynchronous) -- (二)异步消息机制兼谈Hadoop RPC

上篇说了半天,却回避了一个重要的问题:为什么要用异步呢,它有什么样的好处?坦率的说,我对这点的认识不是太深刻(套句俗语,只可意会,不可言传)。还是举个例子吧: 比如Client向Server发送一个...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:双向rpc
举报原因:
原因补充:

(最多只允许输入30个字)