[经验] Java 使用 netty 框架, 向 Unity 客户端的 C# 实现通信[2]
在前一篇文章中, 我们实现了从Java netty 服务端到 unity 客户端的通讯, 但是在过程中也发现有一些问题是博主苦苦无法解决的, 但是还好终于有些问题还是被我找刀方法解决了, 现在把这些解决方案提出来, 虽然是很简陋的方法, 但是应该可以有一些帮助, 然后呢, 如果大家有更好的解决方案也欢迎留言,
ok 话不多说, 开始代码的表演
首先呢, 先来写一个缓存的部分
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.Weighers; /*
*@Description //TODO 实体缓存对象$
*@Author 吾王剑锋所指 吾等心之所向
*@Date 2019/8/22 10:39
*/
public abstract class EntityCachedObject<T, PK extends Serializable> {
private static final Logger LOGGER = LoggerFactory.getLogger(EntityCachedObject.class);
private static final ConcurrentLinkedHashMap.Builder<String, ReentrantReadWriteLock.WriteLock> BUILDER = new ConcurrentLinkedHashMap.Builder<>();
private static final ConcurrentLinkedHashMap<String, ReentrantReadWriteLock.WriteLock> LOCK_MAP = BUILDER.maximumWeightedCapacity(100).weigher(Weighers.singleton()).build(); private static final ConcurrentLinkedHashMap.Builder<String, Cache> ENTITY_CACHE_BUILDER = new ConcurrentLinkedHashMap.Builder<>();
private static final ConcurrentLinkedHashMap<String, Cache> ENTITY_CACHE_MAP = ENTITY_CACHE_BUILDER.maximumWeightedCapacity(20000).weigher(Weighers.singleton()).build(); private static final ConcurrentLinkedHashMap.Builder<String, ConcurrentHashMap<String, Cache>> COMMON_CACHE_BUILDER = new ConcurrentLinkedHashMap.Builder<>();
private static final ConcurrentLinkedHashMap<String, ConcurrentHashMap<String, Cache>> COMMON_CACHE_MAP = COMMON_CACHE_BUILDER.maximumWeightedCapacity(5000).weigher(Weighers.singleton()).build(); /**
* 取得当前组的 HashKey
* */
protected abstract String getHashKey(); /**
* 从数据库获得数据对象
*
* @param id 获得数据ID
* @return Object 数据库的缓存对象
* */
protected abstract T getEntityFromDB(PK id); protected List<T> getEntityFromIdList(List<PK> idList, Class<T> clazz) {
List<T> entityList = new ArrayList<>(idList == null ? 0 : idList.size());
if(idList != null && !idList.isEmpty()) {
for (PK id : idList) {
T entityFromCache = this.get(id, clazz);
if (entityFromCache != null) {
entityList.add(entityFromCache);
}
}
}
return entityList;
} /**
* 获得通用缓存
*
* @param subKey
* @return
*/
protected Object getFromCommonCache(String subKey) {
Cache cache = null;
String hashKey = this.getHashKey();
ConcurrentHashMap<String, Cache> cacheMap = COMMON_CACHE_MAP.get(hashKey);
if(cacheMap != null && !cacheMap.isEmpty()) {
cache = cacheMap.get(subKey);
}
return cache == null || cache.isTimeout() ? null : cache.getValue();
} /**
* 把存储信息加到缓存中
*
* @param subKey
* @param value
*/
protected void putToCommonHashCache(String subKey, Object value) {
String hashKey = this.getHashKey();
Map<String, Cache> cacheMap = COMMON_CACHE_MAP.get(hashKey);
if(cacheMap == null) {
COMMON_CACHE_MAP.put(hashKey, new ConcurrentHashMap<>());
cacheMap = COMMON_CACHE_MAP.get(hashKey);
}
cacheMap.put(subKey, Cache.valueOf(value));
} /**
* 移除主KEY
*
* @param hashkey 主KEY
*/
protected void removeFromCommonHashCache(String hashkey) {
COMMON_CACHE_MAP.remove(hashkey);
} /**
* 移除SUBKEY
*
* @param hashKey 主KEY
* @param subkey 子KEY
*/
protected void removeFromCommonSubCache(String hashKey, String subkey) {
Map<String, Cache> hashMap = COMMON_CACHE_MAP.get(hashKey);
if(hashMap != null) hashMap.remove(subkey);
} /**
* 从缓存中移除实体对象
*
* @param id
* @param clazz
*/
protected void removeEntityFromCache(PK id, Class<T> clazz) {
ENTITY_CACHE_MAP.remove(this.getEntityIdKey(id, clazz));
} /**
* 取得对象的读写锁
*
* @param clazz 类对象
* @return WriteLock 写锁
*/
private ReentrantReadWriteLock.WriteLock getWriteLock(Class<T> clazz) {
String clazzNameKey = clazz.getName();
ReentrantReadWriteLock.WriteLock writeLock = LOCK_MAP.get(clazzNameKey);
if(writeLock == null) {
LOCK_MAP.putIfAbsent(clazzNameKey, new ReentrantReadWriteLock().writeLock());
writeLock = LOCK_MAP.get(clazzNameKey);
}
return writeLock;
} /**
* 取得实体IDKEY
*
* @param id
* @param clazz
* @param <T>
* @return
*/
private <T, PK> String getEntityIdKey(PK id, Class<T> clazz) {
return new StringBuilder().append(clazz.getName()).append("_").append(id).toString();
} /**
* 从缓存获得实体对象
*
* @param id
* @param clazz
* @return
*/
protected T get(PK id, Class<T> clazz) {
String entityIdKey = this.getEntityIdKey(id, clazz);
Cache cachedVO = ENTITY_CACHE_MAP.get(entityIdKey);
if(cachedVO != null && !cachedVO.isTimeout()) {
return (T) cachedVO.getValue();
} ReentrantReadWriteLock.WriteLock writeLock = this.getWriteLock(clazz);
try {
writeLock.lock();
cachedVO = ENTITY_CACHE_MAP.get(entityIdKey);
if(cachedVO == null || cachedVO.isTimeout()) {
Object entityFromDB = this.getEntityFromDB(id);
ENTITY_CACHE_MAP.put(entityIdKey, Cache.valueOf(entityFromDB));
return (T) entityFromDB;
}
} catch (Exception e) {
LOGGER.error("{}", e);
} finally {
writeLock.unlock();
}
return null;
}
}
然后为上面的抽象类创建一个缓存对象
/*
*@Description //TODO 缓存对象$
*@Author 吾王剑锋所指 吾等心之所向
*@Date 2019/8/22 10:47
*/
public class Cache {
private static final Integer TTL_NULL = 60 * 1000;
private static final Integer TTL_SIMPLE = 24 * 60 *TTL_NULL; private Integer ttl;
private Object value;
private Long startTime; public Integer getTtl() {
return ttl;
} public Object getValue() {
if(this.isTimeout()){
this.value = null;
}
return this.value;
} public Long getStartTime() {
return startTime;
} public Boolean isTimeout(){
return System.currentTimeMillis() >= startTime + ttl;
} public void updateCache(Object value){
this.value = value;
this.startTime = System.currentTimeMillis();
this.ttl = value == null ? TTL_NULL : TTL_SIMPLE;
} public static Cache valueOf(Object value){
Cache cache = new Cache();
cache.updateCache(value);
return cache;
}
}
再来一个类, 用于对 ctx 的缓存, 当然不一定只是能缓存CTX , 很多数据只要你想缓存的就可以丢进去
import cn.gzserver.common.Result;
import cn.gzserver.operate.vo.ItemVO;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; /*
*@Description //TODO 缓存帮助类$
*@Author 吾王剑锋所指 吾等心之所向
*@Date 2019/8/30 11:11
*/
public class EntityCacheHelp {
private static final Logger LOGGER = LoggerFactory.getLogger(EntityCacheHelp.class); public static Cache<Integer, ChannelHandlerContext> channelIdCache = CacheBuilder
.newBuilder()
.maximumSize(99999)
.expireAfterAccess(365, TimeUnit.DAYS)
.build(); public static boolean arrayEquals(String[] a,String[] b){
return Arrays.equals(a, b);
} public static String[] getItemVosByIndicex(List<ItemVO> itemVos){
Set<String> set = new HashSet<>();
return set.toArray(new String[set.size()]);
} }
然后这个缓存就算是完成了, 现在我们去netty服务启动的地方, 将 ctx 对象添加到缓存中
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
LOGGER.info("RemoteAddress"+ ctx.channel().remoteAddress() + " active !");
LOGGER.info("msg send active !"+ctx.channel().writeAndFlush("123456"));
ctx.writeAndFlush("啦啦啦!");
EntityCacheHelp.channelIdCache.put(56193,ctx);
super.channelActive(ctx);
}
这样, 这个 ctx 就被加到缓存中了, 大家可以看见在 Map 类型的缓存中, ctx 只是作为 value 的存在, 而前面的那段数字 就是key , 这个key 可以把它理解为客户端连接上服务端后的唯一标识, 由客户端在连接成功的时候提供, 这样方便在服务端向客户端发送信息的时候好准确的找到相应客户端的ID
当我们要主动发送信息的时候, 就在方法里使用
ChannelHandlerContext ctx = EntityCacheHelp.channelIdCache.getIfPresent(clientId);
ctx.writeAndFlush("jsonObject");
这样就可以了, 当 clientId = 56139 的时候, 就能拿到想要的 ctx, 这样的话, 我们的 netty 服务端也就活过来了, 而不是像网上的大多数教程那样, 只能在服务启动的时候, 客户端和服务端使用固定的信息进行通信, 只要能在 netty 服务启动类之外的地方拿到 ctx, 我们就能为所欲为了哈哈哈哈哈哈哈哈哈
当然, 这个解决方案还是存在一些问题
1: 缓存的时间是有限的, 但是netty作为服务端, 而客户端又是固定的情况下, 一般连接上了就不会断开了, 然后通过用户的手机程序向服务端发送信息, 服务端再把信息发送到客户端, 所以这个方法只能在测试的情况下使用, 而在生产环境中, 当一个服务器控制全国各地几百几千台客户端机器的时候, 是不可能重启服务器让客户端从新连入的
2: 我的解决方案的话是有两种, 一种是通过 redis 将 ctx 缓存起来, 或者永久性的缓存, 要不就直接保存到数据服务器, 也就是数据库中, 当用户需要给哪个客户端发送信息的时候, 服务端直接拿着用户给的 clientId 去数据库服务器查找, 找到 ctx 之后再发送给客户端,
3: 这样的话就还有一个疑惑, ctx 每一次好像都不一样, 那么, 如果网络条件发送变化, 这种情况又应该怎么解决呢?
我现在还没有想到, 等我想到了就一定会分享给大家, 如果有哪位大佬了解这种情况的解决方案, 也望不吝赐教.
一直以来都是我去看别人的博客别人的教程, 我终于也可以自己写博客了, 做为一名程序员, 我很自豪, 也很骄傲, 我愿意为这个开源精神奉献我所有的想法和经验!
[经验] Java 使用 netty 框架, 向 Unity 客户端的 C# 实现通信[2]的更多相关文章
- [经验] Java 使用 netty 框架, 向 Unity 客户端的 C# 实现通信 [1]
这是一个较为立体的思路吧 首先是技术选型: 前端 : HTML5 + jQuery ,简单暴力, 不解释 服务端 : Spring Boot + Netty + Redis/Cache 客户端 ...
- 基于netty框架的Socket传输
一.Netty框架介绍 什么是netty?先看下百度百科的解释: Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开 ...
- [经验] Java 服务端 和 C# 客户端 实现 Socket 通信
由于项目需要, 我需要通过 Java 开发的服务端对 C# 作为脚本语言开发的 unity 项目实现控制 话不多说, 直接上代码 首先, 我们先来构建服务端的代码, 服务端我们使用 Java 语言 i ...
- 架构-Java-Netty:Netty框架
ylbtech-架构-Java-Netty:Netty框架 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网 ...
- Netty入门——客户端与服务端通信
Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...
- Http 调用netty 服务,服务调用客户端,伪同步响应.ProtoBuf 解决粘包,半包问题.
实际情况是: 公司需要开发一个接口给新产品使用,需求如下 1.有一款硬件设备,客户用usb接上电脑就可以,但是此设备功能比较单一,所以开发一个服务器程序,辅助此设备业务功能 2.解决方案,使用Sock ...
- Java微服务框架一览
引言:本文首先简单介绍了微服务的概念以及使用微服务所能带来的优势,然后结合实例介绍了几个常见的Java微服务框架. 微服务在开发领域的应用越来越广泛,因为开发人员致力于创建更大.更复杂的应用程序,而这 ...
- Netty框架
Netty框架新版本号:3.0.2.GA,于2008年11月19日公布.Netty项目致力于提供一个异步的.事件驱动的网络应用框架和工具,用于高速开发可维护的.高性能的.高扩展性的server和cli ...
- NetCore Netty 框架 BT.Netty.RPC 系列随讲 二 WHO AM I 之 NETTY/NETTY 与 网络通讯 IO 模型之关系?
一:NETTY 是什么? Netty 是什么? 这个问题其实百度上一搜一堆. 这是官方话的描述:Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个 ...
随机推荐
- 刷题76. Minimum Window Substring
一.题目说明 题目76. Minimum Window Substring,求字符串S中最小连续字符串,包括字符串T中的所有字符,复杂度要求是O(n).难度是Hard! 二.我的解答 先说我的思路: ...
- 全面了解 Java 原子变量类
目录 一.原子变量类简介 二.基本类型 三.引用类型 四.数组类型 五.属性更新器类型 参考资料
- office2019与Visio2016不能共存解决办法
我们电脑已经安装office2019,可是安装visio2016就会安装不了.只要两个软件同时安装就可以解决了,简单粗暴. 首先把电脑的offic或者visio都卸载了,删除干净. 然后先打开visi ...
- my bug of VG algorithm
def visibility_graph(series): g = nx.Graph() # convert list of magnitudes into list of tuples that h ...
- (转)数据索引BTree
.B-tree 转自:http://blog.csdn.net/hbhhww/article/details/8206846 B-tree又叫平衡多路查找树.一棵m阶的B-tree (m叉树)的特性如 ...
- hadoop cdh 后启动群起脚本总是起不起来的一些坑
最近都在流行大数据什么的,然后偶然之间加入了一个物联网的小公司,可以使用hadoop 来做数据分析,于是心中窃喜,可以有机会接触大数据了,从此走上人生巅峰赢取白富美. 可是成功的道路总不是一帆风顺滴, ...
- C++内存管理(new operator/operator new/operator delete/placement new)
new operator 我们平时使用的new是new操作符(new operator),就像sizeof一样是语言内置的,不能改变它的含义,功能也是一样的 比如: string *ps = new ...
- dyt说反话(注意字符串输入)
题目内容: dyt喜欢对lrh说的话说反话,现给出lrh说的k句话,输出dyt所说的反话. 输入格式 第一行是样例个数k(k<10) 接下来k行,每行包含lrh说的一句话(每句话长度不超过50, ...
- PHP随机生成名字 电话号码
封装函数 随机生成电话号码 function generate_name($count,$type="array",$white_space=false) {$arr = arra ...
- bzoj 4196:[NOI2015] 软件包管理器 (树链剖分)
第一次做树剖 找同学要了模板 + 各种借鉴 先用dfs在划分轻重链并编号, install的时候就从查询的节点到根寻找标记的点有多少个,再用深度减去标记的点的个数,并把路径上所有点都标记 uninst ...