Jump to content
七龙的秘密基地
Search In
  • More options...
Find results that contain...
Find results in...

elstp_gf@foxmail.com

Administrators
  • Posts

    43
  • Joined

  • Last visited

Everything posted by elstp_gf@foxmail.com

  1. 目录: Battleye过滤器是什么? Battleye过滤器的7种执行操作 Battleye过滤器的2种匹配类型 过滤器实战 异常函数约定 添加异常匹配 错误分析 找到规则触发者 Kick Restriction #000 关于 BEServer.cfg 中配置基本规则限制 1.BattlEye 过滤器是什么: 是什么? BE 过滤器是Arma 游戏的BattlEye 反作弊的可选功能。它们提供了一些额外的保护,可以由服务器管理员定制。 我服务器开了BE就安全了吗? 是防君子不能防小人,BE并不是全能,外挂还是能轻松注入脚本的,比如炸服脚本,但凡执行的脚本,就可以使用过滤器来阻止外挂并且记录在案,放到封神榜! 我用的infiSTAR能反外挂吗? infistart是国外付费的一款反黑客+管理工具,它的确能做一些反脚本注入,但是很有局限性,通常来说国内的人用的infi并没有完全使用它的反作弊功能,也就开了一点简单的默认 并且服务端不一,需要会技术的人来专门针对性做深度配置才能到达理想效果! 远程执行白名单CfgRemoteExec能反外挂吗? 远程执行白名单,通俗来说就是发起远程调用的函数给他加以限制,让他只能在指定的范围内使用,它确实能达到一些效果,但是对于注入脚本来说,显得很脆弱!但是绝对不能忽视CfgRemoteExec,如果不对函数加以限制,会导致非常恐怖的后果,作弊者可以让任何玩家执行代码!甚至可以让特定玩家触发BE被踢出!,除了开启函数白名单,针对一些公共的服务端,由于非常多的人知道此端的函数名和执行范围,建议对函数进行更名。 Battleye过滤器是什么?它在arma3服务端上发挥什么作用? BattlEye 过滤器是波西米亚使用的反作弊,当你的服务器启用它时,他会扫描客户端上所有正在运行的脚本,和服务端命令上特定的关键词,如果匹配到就会执行操作,至于什么操作待会在说。 如何启用Battleye过滤器? 那么,怎么样才能防止外挂? 这里介绍Battleye,国内服主基本上不会去动这个东西,因为不懂... 在你开服参数中的-profiles 指向 的文件夹内有一个BattlEye文件夹,在里面建立 scripts.txt 里面书写反作弊规则 当然,里面不单单只有scripts.txt 还有: 2.BattlEye 过滤器7种执行操作 be过滤器有7个操作,分别如下: 1 = 仅记录到 .log 文件。 2 = 仅记录到控制台窗口。 3 = 记录到 .log 文件和控制台窗口。 4 = 踢人而不生成 .log 条目。 5 = 仅踢人并记录到 .log 文件。 6 = 仅踢人并登录到控制台窗口。 7 = 踢人并登录到 .log 和控制台窗口。 控制台窗口指的就是你开arma3服务端的白色小窗口,log是你配置的过滤器文件下生成的.log文件 3. BattlEye 过滤器两种匹配类型 上下文中说的异常,指的是当过滤器抓取到了sqf,假设你过滤器写了一个函数和规则, 恰好客户端触发了,这就产生了异常sqf,而异常通常代指你写的规则 != 完全匹配符 当规则有这条,就表示参数必须是完全匹配,意思是如果你执行了某函数,在scripts里定义的关键词函数整个语句都必须与异常完全匹配 ! 包含匹配符 当规则有这一条,就表示参数里包含了异常 4.过滤器规则实践 添加一个关键词,当发现这个关键词时T人打开scripts.txt 在开头第一行写//new2 这个是协议规定,不能改的 然后另起一行 异常是支持正则表达式的,然后 凡是异常中的代码带引号都要反斜杠,带空格的代码都要带引号 异常规则 如何写一个规则?大概格式是这样 要执行的操作ID(1-7) 关键词 规则 在scripts中是这样: 执行方式 触发关键词 匹配类型 异常 1 "allPlayers" 意思是,当有人执行了allPlayers函数,会被记录,为什么用引号,其实用不用无所谓吧,有空格就要引号 1.使用scripts.txt通过记录作弊者执行的脚本来做反作弊过滤器 1 "" 里面是空的,表示客户端执行的所有脚本都会被记录到.log文件 为什么要怎么做?因为这样将有利于分析外挂使用者注入的脚本,他执行的脚本将会被记录! 2.使用createvehicle.txt过滤器 禁止某些东西在服务器内生成 假如我服务器是没有坦克的,想当玩家生成了一辆坦克,就把这个人踢掉,并且记录他执行的脚本代码该如何做呢? 这里拿T100来说,T100的class是 O_Truck_03_medical_F 创建载具的函数是 createVehicle createvehicle.txt 是当玩家执行创建对象的时候执行,这个对象可以是载具,或者其他 ,假如我在 createvehicle.txt 过滤器里写了这一句规则 5 " O_Truck_" !" O_Truck_03" 解释: 表示从玩家客户端捕获脚本以 O_Truck_ 开头的. ! 感叹号上面说过,规则里要包含这一条,规则是 O_Truck_03 然后某玩家执行了创建载具函数: createVehicle ["O_Truck_03_ABC_F",getPosATL player,[],10,"NONE"]; 是不会被T和记录日志,为什么? 因为 ! 的匹配机制是包含,上面玩家执行的创建sqf确实包含了 O_Truck_03_medical,所以他不会被T 什么情况下会被踢出去? createVehicle ["O_Truck_04_ABC_F",getPosATL player,[],10,"NONE"]; 当玩家执行这句的时候会被T,因为捕获到了O_Truck,但是O_Truck_03 这个规则 内不包含O_Truck_04_ABC_F 如果把匹配类型改成 != 5 "O_Truck" !="O_Truck_03_medical_F" 上面的两个实例都会被T,因为参数不完全匹配为O_Truck_03_medical_F,什么情况下不会被T? createVehicle ["O_Truck_03_medical_F",getPosATL player,[],10,"NONE"]; 是不会被T的,因为他完全匹配,一般来说,在createvehicle.txt使用 ! 异常比 != 更有效,但是在scripts中尽量使用!= 上面说的这些规则,在scripts.txt里的工作方式是不一样的! 5.异常函数约定 这个其实不是官方的内容,官方也没有,这是我的想法,我称之为约定,什么叫异常sqf代码函数约定,假设我服务端的代码中遍历了全体玩家 这是外挂注入脚本用的最多的方式,我们来约定一种函数,只能通过这种函数来调用,否则识别为黑客代码T掉 我们在任务里封装一个函数假设为: destiny_fnc_allPlayers ,其内容为 _DestinyAllPlayers = allPlayers _DestinyAllPlayers 调用该函数返回一个全部玩家数组,任务混淆,防止外挂使用者扒取代码,然后在过滤器中写 5 "allPlayers" !="_DestinyAllPlayers = allPlayers" 然后代码中全部使用到allPlayers的函数全部替换为 destiny_fnc_allPlayers 当外挂注入者执行了一个杀死全体玩家的脚本,他肯定会这样写 { _x setDamage 1; } forEach allPlayers; 意思是,循环全体玩家,然后全部杀死,但是在执行前会被反作弊扫描到,从而匹配到我们上面写的规则,触发异常, 异常是forEach allPlayers; 然后被服务器T掉 因为他使用了allPlayers,但是它使用的规则不是 _DestinyAllPlayers = allPlayers,只要不是全部T掉,这就是我说的约定,客户端脚本与我写的规则做了一个约定,约定只能使用这种格式来获取全体玩家! 凡是违反约定的执行代码方式,都会被T,从而达到反作弊效果! 6.添加异常为完全匹配 使用上面的方式因为是!=,完全匹配会遇到一个问题,就是可能官方也用到了这个,或者你用了别人的任务服务端也会出现这个,不好加以约束,我该如何呢? 我们可以这样,1 "allPlayers" 来记录用到allPlayers的函数 然后我们会在scripts.log中看到触发的代码,例如: 22.04.2022 23:11:16: RIO (192.168.0.111:2304) b8a252f8aabd48365cf071e5a2bf011d - #31 "xxx_fncxxx = { params ["_object"]; _p = allPlayers;" 复制最外面的引号之间的代码,如下: "xxx_fncxxx = { params ["_object"]; _p = allPlayers;" 遵循上述规则,在每次出现的引号前"加上反斜杠\。由于这是针对scripts.txt,我们可以忽略正则表达式元字符。Notepad++ 等文本编辑器可用于快速替换所有出现的"with \"。 \"xxx_fncxxx = { \n params ["_object"]; \n _p = allPlayers;\" 格式化为一行 \"xxx_fncxxx = {\nparams ["_object"];\n_p = allPlayers;\" 由于包含空格,就要引号阔起来,这个异常就好了,然后需要添加一个! 或者 != 让他识别为异常,我们使用!= 因为更安全 !="\"xxx_fncxxx = {\nparams ["_object"];\n_p = allPlayers;\"" 最后,必须将整行代码添加到与相对应的地方,假如T人 5 allPlayers !="\"xxx_fncxxx = {\nparams ["_object"];\n_p = allPlayers;\"" 7.错误分析 找到规则触发者 Kick Restriction #000 有时候进去服务器被T,例如显示 23:05:25 | Player #31 RIO (192.168.0.111:2304) (b8a252f8aabd48365cf071e5a2bf011d) has been kicked by BattlEye: Script Restriction #110 显示一些正常的函数都触发了,怎么找到并且排除这个问题呢? 这里提供了3个消息, 发生时间,玩家名称, 被T的消息和编号 首先找到触发日志.这里提示我触发了scripts.txt,就去找到scripts.log 根据时间找到 编号为 #110的,就可以看见我们触发了什么! 22.04.2022 23:05:25: RIO (192.168.0.111:2304) b8a252f8aabd48365cf071e5a2bf011d - #110 "namespace getvariable ["BIS_fnc_arsenal_light",objnull]; deletevehicle _center; deletevehicle _sphere; deletevehicle _light; 上面就是触发的代码, 如果想完全包含,按照低6章教程即可! 8.配置BEServer.cfg的基本规则 很多人仅仅知道在这个文件里配置rcon,其实它还可以提供强大的规则,有些服主rcon无法连接,此文件的规则不生效,请开启你的BE反作弊! 在你开服参数中的-profiles 指向 的文件夹内有一个BattlEye文件夹,在服务器运行时,它会自动重命名为 BEServer_active_xxxx.cfg 以确保安全。它必须至少包含服务器 RconPort 和 RConPassword。如果你没有此文件可以新建一个txt文件将文件格式改为cfg,使用x64开服就命名为:BEServer_x64.cfg,如果不是x64将它命名为BEServer.cfg 首先我们来看一个基本示例: RConPassword rcon123456789 RconPort 7410 MaxAddBackpackCargoPerInterval 20 1 MaxAddMagazineCargoPerInterval 400 1 MaxAddWeaponCargoPerInterval 75 1 MaxCreateVehiclePerInterval 150 1 MaxDeleteVehiclePerInterval 100 1 MaxSetDamagePerInterval 3 1 MaxSetPosPerInterval 10 1 格式为: Max[Command]PerInterval [最大使用次数] [以秒为单位的时间] 在示例中,如果 setDamage 在 1 秒或更短的时间内使用了 30 次或更多次,它将触发 setDamage 计数限制,并且会被T出服务器。 用于每秒最大使用次数的值会因 mod 的不同而有很大差异。上述值是一个松散示例。为了获得最佳值,您需要从可能的最严格(最低)限制开始试验每个命令,并增加它直到没有引起踢出服务器。这将需要大量测试,因为只有某些动作或事件可能会触发踢出玩家。也可以提高时间,让限制更加严格。 命令: 最后 BE过滤器指南看到这里我相信你已经拥有了会自己配置反作弊规则的能力! 来首BGM庆祝一下! 参考: https://github.com/AsYetUntitled/Framework/wiki/BattlEye-Filters https://opendayz.net/threads/a-guide-to-battleye-filters.21066/
  2. Version 1.0.0

    485 downloads

    使用Fiddler抓取抖音弹幕插件 我做成了插件,不适合小白!如果你动手能力较强可以下载插件使用!
  3. Version 1.0.1

    30 downloads

    注意编辑config.yml里面的配置信息! 这个机器人能做什么? 输入#帮助: 输入服主设置的自定义关键词可查询服务器 功能列表 输入 #全体玩家 服务器关键词 |来获取全部玩家 输入 #换图 服务器关键词 地图名称 |来更换服务器地图 输入 #换图列表 |来查看服务器地图列表 输入 #踢出 服务器关键词 玩家名称 理由 |来踢出一名玩家 输入 #封锁 服务器关键词 steam64ID 小时(0表示永久) 理由 |来封锁一个玩家 输入 #解ban 服务器关键词 steam64ID |来解封一名玩家 输入 #VIP列表 服务器关键词 |来查看VIP列表 输入 #添加VIP 服务器关键词 玩家名称 steam64ID |来添加一个VIP 输入 #删除VIP 服务器关键词 玩家名称 steam64ID |来删除一个VIP 输入 #处罚 服务器关键词 玩家名称 理由 |来处罚一名玩家 输入 #重置投票触发阈值 服务器关键词 |来重置 输入 #广播消息 消息内容 |向服务器发送一条消息 输入 #管理员列表 服务器关键词 |查询服务器的全部管理员 输入 #添加管理员 服务器关键词 玩家名称 steam64ID 角色(owner是服主) |来添加一个管理员 输入 #删除管理员 服务器关键词 玩家名称 steam64ID 角色(owner是服主) |来删除一个管理员
  4. 打开UE4,点击左上角文件>然后点击新建C++类 添加一个蓝图函数库 起一个名字,类型改成公共,让其他可访问 等待打开VS2019 在右侧我们可以看见刚刚建立的文件,点展开Source里面就可以看见你自己建立的函数库,有2个,一个是头文件,即为.h主要工作是暴露接口给UE蓝图用, 另一个为.cpp 是我们主要实现业务逻辑的地方 比如我的是MyBlueprintFunctionLibrary.h和MyBlueprintFunctionLibrary.cpp 首先打开MyBlueprintFunctionLibrary.h 声明一个方法 UFUNCTION(BlueprintCallable表示蓝图可使用, Category = "就是你右击面板的时候显示在哪一个分组", meta = (DeterminesOutputType = "是什么类型的" )) 然后给了一个静态修饰,值得注意的是,UE里面每一个类型开头都是大写的,假如Actor 类型前面就要加A,例如AActor, 假如你的函数蓝图是 MyBlueprintFunctionLibrary 前面就要加 U 即为UMyBlueprintFunctionLibrary 这些是千万要注意的! 好了,这里给这个函数起名为CloneActor,输入类型是Actor 返回类型也是Actor,这时候会看见CloneActor函数报错,那是因为我们没有实现它,现在打开MyBlueprintFunctionLibrar.cpp 实现功能 UFUNCTION(BlueprintCallable, Category = "ActorFuncions", meta = (DeterminesOutputType = "InputActor")) static AActor* CloneActor(AActor* InputActor); /// <summary> /// 克隆actor /// </summary> /// <param name="InputActor"></param> /// <returns></returns> AActor* UMyBlueprintFunctionLibrar::CloneActor(AActor* InputActor) // 注意: 在库名称前加一个U,前面说了的 { UWorld* World = InputActor->GetWorld();//获取世界主要用来调用 SpawnActor 函数,因为SpawnActor是动态的,在世界中生成的动态actor FActorSpawnParameters params;//传递给 SpawnActor 函数的可选参数的结构 params.Template = InputActor;//把原来的actor参数拷贝起来 UClass* ItemClass = InputActor->GetClass();//获取原来的actor的类型 AActor* const SpawnedActor = World->SpawnActor<AActor>(ItemClass, params);//生成一个新的Actor 并且把参数给加上 return SpawnedActor;//返回新的actor } 点击编译 等待编译完成 如果你编译出错,请检查,没有问题出现莫名其妙的问题,点文件刷新Visual Studio项目试试或者请重启项目,再不行直接重建,我也不知道为什么有时候没问题就是死活无法编译 保存后,在蓝图里右击调用 至此,使用C++制作一个蓝图函数库教程完成!
  5. 使用rcon可以用 "github.com/verocity-gaming/rcon" 的rcon依赖,新建Go项目,然后导入 import ( "github.com/verocity-gaming/rcon" ) 初始化rcon连接 IP例如127.0.0.1:27015 c, err := rcon.New(ip, password) if err != nil { println("连接服务器失败!") panic(err) } 假如我要执行RCON指令来换图 mapName 这个变量就是地图名了 p := c.SetMap(rcon.MapName(mapName)) if p == nil { fmt.Sprintf("操作成功!") } else { fmt.Sprintf("%s", p) } 然后下面是可用的功能 可以自行实现: func New(addr string, password string) (*Conn, error) func (c *Conn) AdminAdd(a Admin) error func (c *Conn) AdminGroups() ([]string, error) func (c *Conn) AdminRemove(a Admin) error func (c *Conn) Admins() ([]Admin, error) func (c *Conn) AutoBalance() (bool, error) func (c *Conn) AutoBalanceThreshold() (int, error) func (c *Conn) BanPermanently(p Player, reason, admin string) error func (c *Conn) BanRemove(p Player) error func (c *Conn) BanTemporarily(p Player, hours int, reason, admin string) error func (c *Conn) Close() error func (c *Conn) IdleTime() (time.Duration, error) func (c *Conn) Kick(p Player, reason string) error func (c *Conn) Map() (Map, error) func (c *Conn) Maps() ([]Map, error) func (c *Conn) MaxPing() (time.Duration, error) func (c *Conn) Name() (string, error) func (c *Conn) PermanentlyBanned() ([]Ban, error) func (c *Conn) Player(username string) (Player, error) func (c *Conn) Players() ([]Player, error) func (c *Conn) Profanities() ([]string, error) func (c *Conn) Punish(p Player, reason string) error func (c *Conn) ResetVoteKickThreshold() error func (c *Conn) Rotation() ([]Map, error) func (c *Conn) RotationAdd(n MapName) error func (c *Conn) RotationRemove(n MapName) error func (c *Conn) SetAutoBalance(enabled bool) error func (c *Conn) SetAutoBalanceThreshold(diff int) error func (c *Conn) SetBroadcast(message string) error func (c *Conn) SetIdleTime(m time.Duration) error func (c *Conn) SetMap(n MapName) error func (c *Conn) SetMaxPing(ms time.Duration) error func (c *Conn) SetProfanities(words ...string) error func (c *Conn) SetQueueLength(length int) error func (c *Conn) SetSwitchTeamCooldown(m time.Duration) error func (c *Conn) SetSwitchTeamNow(p Player) error func (c *Conn) SetSwitchTeamOnDeath(p Player) error func (c *Conn) SetVIPSlots(slots int) error func (c *Conn) SetVoteKick(enabled bool) error func (c *Conn) SetVoteKickThreshold(pairs ...VoteKickThreshold) error func (c *Conn) Slots() (numerator, denominator int, err error) func (c *Conn) SwitchTeamCooldown() (time.Duration, error) func (c *Conn) TemporarilyBanned() ([]Ban, error) func (c *Conn) UnsetProfanities(words ...string) error func (c *Conn) VIPAdd(v VIP) error func (c *Conn) VIPRemove(v VIP) error func (c *Conn) VIPSlots() (int, error) func (c *Conn) VIPs() ([]VIP, error) func (c *Conn) VoteKick() (bool, error) 写的一个小栗子: TEST.go
  6. 查询steam游戏服务器信息API,可查询arma3,dayz等steam游戏 新建类 ServerPlayer.java 封装玩家信息 package com.destiny.kaiheila.destinybot.SteamServerQuery; public class ServerPlayer { public int PlayerIndex; public String PlayerName; public long PlayerScore; public float PlayerDuration; public ServerPlayer(int Index, String Name, long Score, float Duration) { this.PlayerIndex = Index; this.PlayerName = Name; this.PlayerScore = Score; this.PlayerDuration = Duration; } public int getIndex() { return this.PlayerIndex; } public String getName() { return this.PlayerName; } public long getScore() { return this.PlayerScore; } public float getDuration() { return this.PlayerDuration; } } 新建 SteamServerChallenge.java steam查询头 package com.destiny.kaiheila.destinybot.SteamServerQuery; public class SteamServerChallenge { public static int HEADER = (byte)0x41; } 新建 SteamServerEnvironment.java 专用服务器操作系统标识 package com.destiny.kaiheila.destinybot.SteamServerQuery; public class SteamServerEnvironment { public static int LINUX = 108; public static int WINDOWS = 119; public static int MAC = 109; } 新建 SteamServerInfo.java 服务器信息 package com.destiny.kaiheila.destinybot.SteamServerQuery; import java.nio.ByteBuffer; import java.nio.ByteOrder; public class SteamServerInfo { private int position = 0; public static byte HEADER = (byte)0x49; private int ServerProtocol; private byte[] ServerName; private byte[] ServerMap; private byte[] ServerFolder; private byte[] ServerGame; private short ServerAppID; private int ServerPlayers; private int ServerMaxPlayers; private int ServerBots; private int ServerType; private int ServerEnvironment; private int ServerVisibility; private int ServerVAC; private byte[] ServerVersion; private int ServerEDF; public SteamServerInfo(byte[] Buffer) { this.ServerProtocol = Buffer[0]; this.position++; int ServerNameLength = this.getStringLenght(this.position, Buffer); this.ServerName = new byte[ServerNameLength]; System.arraycopy(Buffer, this.position, this.ServerName, 0, ServerNameLength); this.position = this.position + ServerNameLength + 1; int ServerMapLength = this.getStringLenght(this.position, Buffer); this.ServerMap = new byte[ServerMapLength]; System.arraycopy(Buffer, this.position, this.ServerMap, 0, ServerMapLength); this.position = this.position + ServerMapLength + 1; int ServerFolderLength = this.getStringLenght(this.position, Buffer); this.ServerFolder = new byte[ServerFolderLength]; System.arraycopy(Buffer, this.position, this.ServerFolder, 0, ServerFolderLength); this.position = this.position + ServerFolderLength + 1; int ServerGameLength = this.getStringLenght(this.position, Buffer); this.ServerGame = new byte[ServerGameLength]; System.arraycopy(Buffer, this.position, this.ServerGame, 0, ServerGameLength); this.position = this.position + ServerGameLength + 1; this.ServerAppID = ByteBuffer.wrap(Buffer, this.position, this.position + 1).order(ByteOrder.LITTLE_ENDIAN).getShort(); this.position = this.position + 2; this.ServerPlayers = Buffer[this.position]; this.position++; this.ServerMaxPlayers = Buffer[this.position]; this.position++; this.ServerBots = Buffer[this.position]; this.position++; this.ServerType = Buffer[this.position]; this.position++; this.ServerEnvironment = Buffer[this.position]; this.position++; this.ServerVisibility = Buffer[this.position]; this.position++; this.ServerVAC = Buffer[this.position]; this.position++; int ServerVersionLength = getStringLenght(this.position, Buffer); this.ServerVersion = new byte[ServerVersionLength]; System.arraycopy(Buffer, this.position, this.ServerVersion, 0, ServerVersionLength); this.position = this.position + ServerVersionLength + 1; this.ServerEDF = Buffer[this.position]; } public int getProtocol() { return this.ServerProtocol; } public String getName() { return new String(this.ServerName); } public String getMap() { return new String(this.ServerMap); } public String getFolder() { return new String(this.ServerFolder); } public String getGame() { return new String(this.ServerGame); } public short getAppID() { return this.ServerAppID; } public int getPlayers() { return this.ServerPlayers; } public int getMaxPlayers() { return this.ServerMaxPlayers; } public int getBots() { return this.ServerBots; } public int getType() { return this.ServerType; } public int getEnvironment() { return this.ServerEnvironment; } public int getVisibility() { return this.ServerVisibility; } public int getVAC() { return this.ServerVAC; } public String getVersion() { return new String(this.ServerVersion); } public int getEDF() { return this.ServerEDF; } private int getStringLenght(int start, byte[] buffer) { for (int i = start; i < buffer.length; i++) { if (buffer[i] == 0) { return i - start; } } return 0; } @Override public String toString() { return "Protocol : " + this.getProtocol() + "\nName : " + this.getName() + "\nMap : " + this.getMap() + "\nFolder : " + this.getFolder() + "\nGame : " + this.getGame() + "\nAppID : " + this.getAppID() + "\nPlayers : " + this.getPlayers() + "\nMax Players : " + this.getMaxPlayers() + "\nBots : " + this.getBots() + "\nServer Type : " + (char)this.getType() + " (d = DEDICATED|l = NON-DEDICATED|p = SourceTV/proxy)\nEnvironment : " + (char)this.getEnvironment() + " (l = Linux|w = Windows|m = MAC)\nVisibility : " + this.getVisibility() + " (0 = Public|1 = Private)\nVAC : " + this.getVAC() + " (0 = UNSECURED|1 = SECURED)\nVersion : " + this.getVersion() + "\nExtra Data Flag (EDF) : " + this.getEDF(); } } 新建 SteamServerPlayer.java 服务器玩家信息 package com.destiny.kaiheila.destinybot.SteamServerQuery; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class SteamServerPlayer { private int position = 0; public static byte HEADER = (byte)0x44; public int PlayersLength; public ServerPlayer[] Players; public SteamServerPlayer(byte[] Buffer) { this.PlayersLength = Buffer[this.position]; this.Players = new ServerPlayer[this.getPlayersLength()]; this.position++; for (int i = 0; i < this.getPlayersLength(); i++) { int PlayerIndex = Buffer[this.position]; this.position++; int PlayerNameLength = getStringLenght(this.position, Buffer); byte[] PlayerName = new byte[PlayerNameLength]; System.arraycopy(Buffer, this.position, PlayerName, 0, PlayerNameLength); this.position = this.position + PlayerNameLength + 1; long PlayerScore = ((Buffer[this.position + 3] & 0xFFL) << 24) | ((Buffer[this.position + 2] & 0xFFL) << 16) | ((Buffer[this.position + 1] & 0xFFL) << 8) | ((Buffer[this.position + 0] & 0xFFL) << 0); this.position = this.position + 4; float PlayerDuration = ByteBuffer.wrap(new byte[] {Buffer[this.position], Buffer[this.position+1], Buffer[this.position+2], Buffer[this.position+3]}).order(ByteOrder.LITTLE_ENDIAN).getFloat(); this.position = this.position + 4; this.Players[i] = new ServerPlayer(PlayerIndex, new String(PlayerName), PlayerScore, PlayerDuration); } } public int getPlayersLength() { return this.PlayersLength; } public ServerPlayer[] getPlayers() { return this.Players; } private int getStringLenght(int start, byte[] buffer) { for (int i = start; i < buffer.length; i++) { if (buffer[i] == 0) return i - start; } return 0; } public List<Map<String,Object>> playerList (){ List<Map<String,Object>> listMap = new ArrayList<>(); for (ServerPlayer Player : this.getPlayers()) { Map<String,Object> map = new HashMap<>(); map.put("playerName",Player.getName() ); map.put("Score", Long.parseLong(String.valueOf(Player.getScore()))); map.put("Duration",Math.round(Player.getDuration() / 3600) + ":" + Math.round((Player.getDuration() % 3600) / 60) + ":" + Math.round((Player.getDuration() % 3600) % 60)); listMap.add(map); } return listMap; } public String toString() { String PlayerTable = ""; for (ServerPlayer Player : this.getPlayers()) { PlayerTable += Player.getName() + ((Player.getName().length() <= 7) ? "\t\t\t\t" : ((Player.getName().length() <= 15) ? "\t\t\t" : ((Player.getName().length() <= 23) ? "\t\t" : "\t"))); PlayerTable += new Long(Player.getScore()).intValue() + "\t\t"; PlayerTable += Math.round(Player.getDuration() / 3600) + ":" + Math.round((Player.getDuration() % 3600) / 60) + ":" + Math.round((Player.getDuration() % 3600) % 60) + "\n"; } return "Players : " + this.getPlayersLength() + "\nPlayer Name :\t\t\tScore :\t\tDuration:\n" + PlayerTable; } } 新建SteamServerQuery.java steam主服务器查询 package com.destiny.kaiheila.destinybot.SteamServerQuery; import java.net.*; public class SteamServerQuery { private InetAddress ServerAddress; private int ServerPort; private DatagramSocket UDPClient; public SteamServerQuery(InetAddress Address, int Port) { try { this.UDPClient = new DatagramSocket(); this.ServerAddress = Address; this.ServerPort = Port; } catch (SocketException e) { e.printStackTrace(); } } public SteamServerQuery(String Address, int Port) throws UnknownHostException { this(InetAddress.getByName(Address), Port); } public SteamServerQuery(String Address) throws UnknownHostException { this(Address.split(":")[0], Integer.parseInt(Address.split(":")[1])); } public DatagramSocket getDatagramSocket() { return UDPClient; } public SteamServerInfo getInfo() throws Exception { byte[] InfoHeader = new byte[25]; InfoHeader[0] = (byte)0xFF; InfoHeader[1] = (byte)0xFF; InfoHeader[2] = (byte)0xFF; InfoHeader[3] = (byte)0xFF; InfoHeader[4] = (byte)0x54; byte[] SourceString = new String("Source Engine Query").getBytes(); System.arraycopy(SourceString, 0, InfoHeader, 5, SourceString.length); InfoHeader[5 + SourceString.length] = (byte) 0x00; DatagramPacket SendInfoPacket = new DatagramPacket(InfoHeader, InfoHeader.length, this.ServerAddress, this.ServerPort); this.UDPClient.setSoTimeout(3000); this.UDPClient.send(SendInfoPacket); byte[] ReceivedBuffer = new byte[512]; DatagramPacket ReceivedInfoPacket = new DatagramPacket(ReceivedBuffer, ReceivedBuffer.length); UDPClient.setSoTimeout(3000); this.UDPClient.receive(ReceivedInfoPacket); if (ReceivedBuffer[0] == (byte)0xFF && ReceivedBuffer[1] == (byte)0xFF && ReceivedBuffer[2] == (byte)0xFF && ReceivedBuffer[3] == (byte)0xFF && ReceivedBuffer[4] == SteamServerInfo.HEADER) { byte[] ServerInfoBuffer = new byte[ReceivedBuffer.length - 5]; System.arraycopy(ReceivedBuffer, 5, ServerInfoBuffer, 0, ServerInfoBuffer.length); return new SteamServerInfo(ServerInfoBuffer); } return null; } public SteamServerPlayer getPlayer() throws Exception{ byte[] PlayerHeader = this.getChallenge(); PlayerHeader[4] = (byte)0x55; DatagramPacket SendPlayerPacket = new DatagramPacket(PlayerHeader, PlayerHeader.length, this.ServerAddress, this.ServerPort); this.UDPClient.send(SendPlayerPacket); byte[] ReceivedPlayerBuffer = new byte[1024]; DatagramPacket ReceivedPlayerPacket = new DatagramPacket(ReceivedPlayerBuffer, ReceivedPlayerBuffer.length); this.UDPClient.setSoTimeout(3000); this.UDPClient.receive(ReceivedPlayerPacket); if (ReceivedPlayerBuffer[0] == (byte)0xFF && ReceivedPlayerBuffer[1] == (byte)0xFF && ReceivedPlayerBuffer[2] == (byte)0xFF && ReceivedPlayerBuffer[3] == (byte)0xFF && ReceivedPlayerBuffer[4] == SteamServerPlayer.HEADER) { byte[] ServerPlayerBuffer = new byte[ReceivedPlayerBuffer.length - 5]; System.arraycopy(ReceivedPlayerBuffer, 5, ServerPlayerBuffer, 0, ServerPlayerBuffer.length); return new SteamServerPlayer(ServerPlayerBuffer); } else { System.err.println("ERROR Player Packet !"); return null; } } public byte[] getChallenge() throws Exception { byte[] ChallengeHeader = new byte[9]; ChallengeHeader[0] = (byte)0xFF; ChallengeHeader[1] = (byte)0xFF; ChallengeHeader[2] = (byte)0xFF; ChallengeHeader[3] = (byte)0xFF; ChallengeHeader[4] = (byte)0x55; ChallengeHeader[5] = (byte)0xFF; ChallengeHeader[6] = (byte)0xFF; ChallengeHeader[7] = (byte)0xFF; ChallengeHeader[8] = (byte)0xFF; DatagramPacket SendChallengePacket = new DatagramPacket(ChallengeHeader, ChallengeHeader.length, this.ServerAddress, this.ServerPort); this.UDPClient.send(SendChallengePacket); byte[] ReceivedChallengeBuffer = new byte[9]; DatagramPacket ReceivedChallengePacket = new DatagramPacket(ReceivedChallengeBuffer, ReceivedChallengeBuffer.length); this.UDPClient.setSoTimeout(3000); this.UDPClient.receive(ReceivedChallengePacket); if(ReceivedChallengeBuffer[0] == (byte)0xFF && ReceivedChallengeBuffer[1] == (byte)0xFF && ReceivedChallengeBuffer[2] == (byte)0xFF && ReceivedChallengeBuffer[3] == (byte)0xFF && ReceivedChallengeBuffer[4] == SteamServerChallenge.HEADER) { return ReceivedChallengeBuffer; } else { System.err.println("ERROR Challenge Packet !"); return new byte[9]; } } } 使用方法: //参数 IP:查询端口 SteamServerQuery ServerQuery = new SteamServerQuery("0.0.0.0:2303"); //从服务器获取服务器信息 SteamServerInfo ServerInfo = ServerQuery.getInfo(); //从服务器获取玩家信息 SteamServerPlayer player = ServerQuery.getPlayer(); ServerInfo..... player.... SteamServerQuery.7z
  7. 1.骨骼蒙皮绑定 先导入一个模型,删除自带的骨骼(这是为了使用UE4的小白人骨骼,方便重定向骨骼到小白人身上,如果你不是为了UE引擎,可以不这么做)。 移动到右侧面板,找到单位, 缩放单位输入0.01, 这是为了保持和UE4人物大小一致 点击条目, 在尺寸处可查看模型大小, Z轴表示高度, 按住S加鼠标移动 拉伸为正常人体高度,例如1.63,这是为了避免一些问题 然后ctrl+A 应用全部变换 右侧面板添加人形骨骼 Human 切换到物体模式,选中模型 点击Get Sekected Smart>Full Body 现在,开始添加头部骨骼节点与脖子,点击Add Neck/Add Shoulders,然后在模型位置左键添加 按照方式,添加头 ,脖子,手臂,手腕,臀部,脚部,点击GO! 开始生成骨骼,等待一会 骨骼已生成,在这里如果骨骼位置不对,你可以调整一下 效果例子: 然后点击Match to Rig,来匹配蒙皮位置 自动摆正蒙皮 效果例子: 现在开始把蒙皮和模型绑定,切换到物体模式,按住shift多选,选中模型与蒙皮,点击右侧 蒙皮 按钮,其他设置不用管, 直接点击 Bind,稍等一会儿 绑定好了,切换到姿态模式,试试现在的姿态是否存在问题(点击一个位置按 R 可以调整姿态) 成果预览: 看起来没问题,现在把调动的姿态还原到最初的姿态 2.自动权重 由于我把原先的模型骨骼删除了,现在没有模型权重,下面开始附加自动权重,但是会遇到问题 典型的问题就是独立的物体导致姿态移动时候有一些面在原地不动,撕裂等各种奇葩问题... 我们可以进行拆分再刷权重,然后合并 进入编辑模式 按 P 选择按松散块 在右侧场景集合可以看见,都变成一个小块了 然后加入物体模式,按 A 全选 然后按Ctrl+ P 选择附带自动权重 现在进入姿态模式,调整姿态查看权重效果,但是已经解决大部分问题,当然还是会有局部的各种物体有问题,这时候小问题就需要你自己手动再刷有问题的地方权重了 现在我可以合并块,为一个整体,全选 按Ctrl+J 合并块 现在已经是一个整体了 到此,模型骨骼,蒙皮,权重都已完成,如果导出为UE4的,下面继续! 3.导出为UE4骨骼模型 为了能成功的将模型骨骼重定向到UE4官方小白人骨骼上,我们导出的模型骨骼名称对应官方 找到右侧面板的 Auto-Rig Pro:Export 点击展开 选择要导出的物体,直接按 A 全选 然后再点击 Check Rig 检查一下,然后点击Fix Rig 修正一下 然后点击 Export FBX 按钮 ,导出模型为FBX格式 选择 Unreal Engine Humanoid 勾上下面两个 点击杂项 ,选择面 然后点击 Auto-Rig Pro FBX Export 导出模型 4.UE4模型导入骨骼重定向 导入我们刚刚Blender导出的模型 打开骨骼,我们选择骨骼查看,我们可以看见骨骼名称与UE4官方小白人一致 左侧骨骼树也一模一样 现在我们骨骼直接替换,重定向到官方小白人的骨骼上。 右击骨骼网格体,选择骨骼,选择指定骨骼 指定官方骨骼。UE4_Mannequin_Skeleton 下边左侧是你模型的骨骼名称,右侧是你要指定,也就是重定向到的骨骼名称,因为我们是一直的名称,所以 不需要调整,直接确定 到此,全部工作已经做完! 我也是研究了很久,还是有权重问题,使用蓝图模型会乱,我还得研究研究,研究出来了再发!
  8. 找到动画文件,直接复制粘贴一份打开 或者使用创建按钮来创建一个动画,第一个是直接从零开始... 第二个是当前姿势开始 UE4 手K动画非常简单(太难了...要是有动捕就好了!) 首先看左侧,骨骼树,这里就是各种骨骼,你K动画就是调节骨骼 中间Key 创建一帧,apply应用保存 创建了就调姿势,调好了就保存 下边就是你调节的骨骼线条 更细节?右击线条,进入编辑 Translation 的XYZ 个人理解就是位置吧 rotation的XYZ 就是各个XYZ坐标的旋转 Scale的XYZ 就是大小 然后开始了漫长的手K动画之路... 保存好动画该如何播放? 可以使用动画蒙太奇 创建动画蒙太奇,选择你的这个角色的骨架 将做好的动画拖上去 值得注意的是default这个勾勾,你勾3了,这个动画就会重复播放 什么时候勾?就是你的动画需要重复播放的时候,比如开火。 保存后,我们转到角色动画蓝图 在动画事件里右击创建一个默认插槽 然后输出姿势 在人物蓝图事件里写一个按下任意按钮播放人物动画 自此已经实现了播放手K动画...
  9. 先到项目根目录,在Config文件夹中的DefaultEngine.ini文件加上两行:   [OnlineSubsystem]   DefaultPlatformService=Null 物体移动需要点击物体,转到Details 中有一个Static Mesh Replicate Move 勾上这个才可以让物体同步移动 Replication 中文翻译是复制,个人理解为同步 对象的 Replication 用处是主机复制给客户端 对象的 ReplicationMovement 用处是主机与客户端复制移动,就可以实现我看得见你移动, 你也可以看见我移动,这些功能在对象属性上要勾上 然后是变量的复制: 可以看见,有2种,第一种就是Replicated ,这个当你选择了这个,会把这个变量复制给所有客户端 我们说第二种,第二种是复制并通知,然后你点了,会发现自动创建一个函数 当这个变量被改变时这函数会被调用 然后是服务器广播,服务器广播数据 只能是事件,自定义一个事件,属性中可以看见, Multicast 就是多播的意思,广播给全部客户端 Run on server 就是只给服务器运行这个函数 Reliable 这个勾勾默认没有勾,意思是是否消息保证,当客户端或者服务端发送消息的时候保证百分之百到达,不会丢包 通常用来做一些严谨的事情 可以用Has Authority来判断当前执行的人是不是服务器 最后是第三人称和第一人称镜头切换 一般的是直接切换镜头,有一些想做到细节的就是有两个模型,一个是第一人称,一个是第三人称 但是我们做的话只想让第三人称给玩家看到,第一人称玩家看不见,这里就可以使用角色的可见属性 点一个Mesh 右边属性搜索see Owner No See 意思是拥有者,也就是自己不可见 Only Owner See 只有自己可见 那么通常情况下,我们肯定要设置第一人称为自己可见其他人不可见 第三人称是其他人可见自己不可见,就可以实现很多细节!
  10. 多人游戏的房间建立与房间搜索加入的实现 首先建立一个游戏实例 创建一个MPGameinstance 游戏实例类 打开蓝图,在事件蓝图里创建一个自定义事件 命名为HostGame 然后创建一个会话节点(Create Session) 其节点有三个参数和2种返回值 第一个参数是谁创建的,当然是玩家,所以获取玩家控制器 第二个参数是公开的一个会话的最大连接数 第三个参数是LAN 如果你勾上了,那就表示在LAN,也就是局域网房间 图中CreateMessage 是一个函数,只是一个打印控制台函数,可以无视 关键在Open KLevel ByName ,这个节点是打开关卡的意思,值得注意的是打开关卡在多人中有参数 第一个是关卡名称,也就是地图名例如第三人称模板默认的地图ThirdPersonExampleMap 第二个默认勾上 第三个关键参数就是 listen 主机模式必须有listen,监听否则就找不到房间 然后创建搜索房间事件 CreateLoading是我写的加载UI,无视 关键函数,Find Sessions 参数1 玩家 参数2 最大搜索会话返回结果 参数3 是不是局域网搜索 找得到房间后你可以直接加入,或者储存起来,我这里是存起来让玩家自己选要进的房间 最后就是加入房间了! 加入房间关键函数也是 Join Session 把上面储存的会话结果放到第二个参数 ,然后就可以加入房间了 值得注意的是设置 默认生成的pawn 必须是你搞好的角色 一般的游戏不是世界生成,而是服务器创建一个pawn 然后给那个玩家possess,这都是在服务器上运行的,所以当你要这么搞,必须判断是否是服务器,可以用Has Authority网络鉴权 来判断 然后是大家最关注的地图里有玩家,主机怎么携带玩家跳转到其他关卡,其实很简单 关键参数: ServerTravel 例如 ServerTravel 地图名称?listen 使用方法:
  11. Version 1.2.5.1

    10,443 downloads

    直播伴侣中不能有任何场景哦,要用OBS
  12. 当时密码加密我还是停留在盐,第一次用Spring Security的时候,接触到了 BCryptPasswordEncoder 这个加密,当时非常好奇这个算法,因为它每次加密的密码都是不一样的,动态的 拆开发现,两个关键方法 BCrypt.hashpw和BCrypt.gensalt 仔细看能发现这个加密算法是用的强哈希+盐+密钥,默认情况是的密码,因为使用到了哈希,所以是密码是不可逆的,这样就算你密码泄露了别人也不知道密码明文是什么 看gensalt 有没有很熟悉 这个盐和生成的密码前缀是不是有点像,生成了一个带盐的哈希 再看hashpw 里面一大堆代码都是在校验密文 注意看,这里已经开始截取上面生成的哈希盐了 String real_salt = salt.substring(off + 3, off + 25); 最后工序就是解码base64和编码64 然后生成的结果就是我们现在看见的,同一个密码,每一次都不一样的密文 这个是一个非常强大的好用的加密方法,反正我是比较喜欢的!因为都不需要在数据库里把盐给存起来
  13. 那么我们可以简化,使用自己写的一个注解注入到服务层,让其自己完成必须参数的校验功能 maven项目引入 <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> 建一个实体类对象 public class Parameter { @NotEmpty(message="姓名不能为空") private String name; @Min(value = 18, message = "年龄必须大于18岁") private int age; @NotEmpty(message="hobbies不能为空") private List<String> hobbies; } 新建一个自定义注解接口 /**检查方法实体类是否符合规则 * @author qilong */ //在自定义注解的时候可以使用@Documented来进行标注,如果使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。 @Documented //@Retention可以用来修饰注解,是注解的注解,称为元注解。RetentionPolicy.RUNTIME运行时注解,注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在; @Retention(RetentionPolicy.RUNTIME) //注解的作用目标METHOD——方法,TYPE——接口、类、枚举、注解 @Target({ElementType.METHOD, ElementType.TYPE}) public @interface EntityCheck { /** * 是否打印输出拦截日志 * @return */ boolean debug() default false; } 新建一个类用以实现AOP /**实体类检查切面 * @author qilong */ @Aspect @Component @Slf4j public class EntityCheckAspect { //之前注解接口的路径 @Pointcut("@annotation(zx.cloud.commons.utils.entityCheck.EntityCheck)") public void pointEntityCheck() { } /** * 环绕切入点 * 匹配指定包包下所有使用@Service注解的类 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around(value = "pointEntityCheck()") private Object entityCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 获取切入的方法对象 // 这个m是代理对象的,没有包含注解 Method m = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod(); // this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解 Method methodWithAnnotations = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod( proceedingJoinPoint.getSignature().getName(), m.getParameterTypes()); EntityCheck annotation = methodWithAnnotations.getAnnotation(EntityCheck.class); if (annotation.debug()) { log.info("实体类验证:拦截到包:{} 方法:{}", proceedingJoinPoint.getTarget().getClass().getName(), proceedingJoinPoint.getSignature().getName()); } //获取所有参数 Object[] args = proceedingJoinPoint.getArgs(); for (Object o: args) { if (o instanceof BindingResult){ //校验不通过如果有错误 不执行方法直接返回出去 String re = getError((BindingResult)o); if (null != re) { //NormalResponse 是我自己的返回的格式,你可以自定义,例如返回一个map,String 都可以 return new NormalResponse(Code.FAIL, re); } } } //参数校验没有错误继续执行方法 return proceedingJoinPoint.proceed(args); } //自定义错误返回, 当实体类校验不通过,这里会直接返回给前端 public static String getError(BindingResult result) { StringBuffer errorInfo = new StringBuffer(); if (result.hasErrors()) { List<FieldError> fieldErrors = result.getFieldErrors(); for (FieldError error : fieldErrors) { errorInfo.append(String.format("参数 %s错误: %s ", error.getField(), error.getDefaultMessage())); } return errorInfo.toString(); } else { return null; } } } 服务层 @Slf4j @Service public class TestServiceImpl implements TestService { //加上我们自定义的注解 @EntityCheck @Override public NormalResponse createAutoProvisioningGroup(Parameter entity, BindingResult result) { log.info("没有遇到参数错误我已被运行!"); } } 访问你的接口,如果有参数不对,就不会执行服务层,被@EntityCheck注解检查后拦截返回,至此自定义参数校验注解完成!
  14. 在application.yml文件中配置web以及rabbitmq的配置信息 spring: rabbitmq: host: 127.0.0.1 port: 5672 username: root password: 9YoIu # 发送者开启 return 确认机制 publisher-returns: true # 发送者开启 confirm 确认机制 publisher-confirm-type: correlated listener.simple: # 设置消费端手动 ack acknowledge-mode: manual # 是否支持重试 retry: enabled: true 在maven中引入依赖: <!--MQ消息队列--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> 使用@EnableRabbit注解在入口类上启用MQ @SpringBootApplication @EnableRabbit public class SmsEmailApplication { public static void main(String[] args) { SpringApplication.run(SmsEmailApplication.class, args); } } 新建rabbitmq配置类 @Configuration public class RabbitmqConfig { @Bean public MessageConverter messageConverter() { return new Jackson2JsonMessageConverter(); } //配置正常业务 例如邮件发送 的队列交换机与通道 public static String Queue1 = "queue_1"; public static String Exchange1 = "exchange_1"; public static String Routing1 = "routing_key_1"; /** * 定义死信队列相关信息 */ public final static String deadQueueName = "dead_queue"; public final static String deadRoutingKey = "dead_routing_key"; public final static String deadExchangeName = "dead_exchange"; /** * 死信队列 交换机标识符 */ public static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange"; /** * 死信队列交换机绑定键标识符 */ public static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key"; /** * 创建死信交换机 */ @Bean public DirectExchange deadExchange() { return new DirectExchange(deadExchangeName); } /** * 创建配置死信队列 * * @return */ @Bean public Queue deadQueue() { Queue queue = new Queue(deadQueueName, true); return queue; } /** * 死信队列与死信交换机绑定 */ @Bean public Binding bindingDeadExchange() { return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(deadRoutingKey); } /** * 队列绑定到死信 * 第一个参数是创建的queue的名字,第二个参数是是否支持持久化 * @return */ @Bean public Queue EmailQueue() { // 将普通队列绑定到死信队列交换机上 Map<String, Object> args = new HashMap<>(2); args.put(DEAD_LETTER_QUEUE_KEY, deadExchangeName); args.put(DEAD_LETTER_ROUTING_KEY, deadRoutingKey); Queue queue = new Queue(Queue1, true, false, false, args); return queue; } /** * 创建交换机 * 一共有三种构造方法,可以只传exchange的名字, 第二种,可以传exchange名字,是否支持持久化,是否可以自动删除, * 第三种在第二种参数上可以增加Map,Map中可以存放自定义exchange中的参数 * @return */ @Bean public DirectExchange EmailExchange() { return new DirectExchange(Exchange1, true, false); } /** * 绑定 * @param Queue * @param Exchange * @return */ @Bean public Binding bindingFinanceExchange(Queue Queue1, DirectExchange Exchange) { return BindingBuilder.bind(EmailQueue()).to(Exchange1).with(Routing); } } 新建消费者类RabbitReceiver /** 消息队列监听器 * * @param message */ @RabbitListener(queues = "queue_1") @RabbitHandler public void process(JSONObject data, Channel channel, Message message) throws IOException { //消息手动确认 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //消息重新投递 channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true); //消息是否重复 if(message.getMessageProperties().getRedelivered()){ //拒绝消息 channel.basicReject(message.getMessageProperties().getDeliveryTag(),false); //拒绝消费消息(丢失消息) 重新投递给死信队列 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); } } /** * 死信 消息队列消费者 * 当无法消费且被投递至死信队列则再次被死信消费 * @param message * @param headers * @param channel * @throws Exception */ @RabbitListener(queues = "dead_queue") @RabbitHandler public void deadProcess(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception { // 获取消息Id String messageId = message.getMessageProperties().getMessageId(); String msg = new String(message.getBody(), "UTF-8"); // // 手动ack Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG); // 手动签收 channel.basicAck(deliveryTag, false); log.error("日志已记录...."); } 投递一条消息 @Resource RabbitTemplate rabbitTemplate; //生成一个随机消息ID public static Message getId (JSONObject data){ return MessageBuilder.withBody(JSON.toJSONString(data).getBytes()) .setContentType(MessageProperties.CONTENT_TYPE_JSON) .setContentEncoding("utf-8") .setMessageId(UUID.randomUUID()+"") .build(); } /** * 投递消息 * 通过自定义设置消息体给每一个消息设置一个唯一ID,方便失败时候重试 * @param sendData 要发送的数据 * @param async 如果开启此项,交换机会马上把所有的信息都交给所有的消费者,消费者再自行处理,不会因为消费者处理慢而阻塞线程。 返回一个null * 如果禁用此项,交换机会同步消费者。使用此方法,当确认了所有的消费者都接收成功之后,才接收另外一个 会造成阻塞 返回一个object * rabbitTemplate.convertAndSend 里接受3个参数 第一个是之前配置的交换机,第二个是配置好的routing_key,第三个是消息体 */ public Object sendSmsEmail(JSONObject sendData,boolean async){ //自定义消息体 必须给消息指定一个UUID,用于失败重试 Message message = getId(sendData); if (async){ rabbitTemplate.convertAndSend("exchange_1","routing_key_1",message); return null; }else{ return rabbitTemplate.convertSendAndReceive("exchange_1","routing_key_1",message); } } 现在我们在浏览器中输入:http://localhost:15672 可以看到一个登录界面 查看队列,features行下的普通交换机有一个 DLX 的标志,就说明已绑定了死信交换机
  15. using System.Text; using System.Runtime.InteropServices; using RGiesecke.DllExport; using System; using System.Threading.Tasks; using System.Collections; using System.Web.SessionState; using System.Web; using System.Collections.Generic; /** * by MarkCode 七龙 * url https://o.ls * REDIS 扩展 * */ namespace ArmaMapsExt { public class ArmaMapsExt { public static ExtensionCallback callback; public delegate int ExtensionCallback([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPStr)] string data); public static ServiceStack.Redis.RedisClient client = null; #if WIN64 [DllExport("RVExtensionRegisterCallback", CallingConvention = CallingConvention.Winapi)] #else [DllExport("_RVExtensionRegisterCallback@4", CallingConvention = CallingConvention.Winapi)] #endif public static void RVExtensionRegisterCallback([MarshalAs(UnmanagedType.FunctionPtr)] ExtensionCallback func) { callback = func; } /// <summary> ///当arma启动并加载所有扩展时被调用。 ///最好在单独的线程中加载静态对象,以使扩展不需要任何单独的初始化 /// </ summary> /// <param name =“ output”>包含以下内容的字符串生成器对象:函数的结果</ param> /// <param name =“ outputSize”>可以返回的最大字节数</ param> #if WIN64 [DllExport("RVExtensionVersion", CallingConvention = CallingConvention.Winapi)] #else [DllExport("_RVExtensionVersion@8", CallingConvention = CallingConvention.Winapi)] #endif public static void RvExtensionVersion(StringBuilder output, int outputSize) { output.Append("MapsExt by Qilong v1.0"+ outputSize); } /// <summary> ///默认callExtension命令的入口点。 /// </ summary> /// <param name =“ output”>包含函数结果的字符串生成器对象</ param> /// <param name =“ outputSize”>可以返回</ param> /// <param name =“ function”>与callExtension一起使用的字符串参数</ param> #if WIN64 [DllExport("RVExtension", CallingConvention = CallingConvention.Winapi)] #else [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)] #endif public static void RvExtension(StringBuilder output, int outputSize, [MarshalAs(UnmanagedType.LPStr)] string function) { output.Append(function); } /// <summary> /// callExtensionArgs命令的入口点。 /// </ summary> /// <param name =“ output”>包含函数结果的字符串生成器对象</ param> /// <param name =“ outputSize”>可以返回</ param> /// <param name =“ function”>与callExtension一起使用的字符串参数</ param> /// <param name =“ args”>作为字符串传递给callExtension的args array </ param> /// <param name =“ argsCount”>字符串数组args的大小</ param> /// <returns>结果代码</ returns> #if WIN64 [DllExport("RVExtensionArgs", CallingConvention = CallingConvention.Winapi)] #else [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)] #endif public static int RvExtensionArgs(StringBuilder output, int outputSize, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, int argCount) { if (args.Length > 10240) { output.Append("ERROR"); return 0; } for (int i = 0; i < args.Length; i++) { args[i] = args[i].Trim().Replace("\"", ""); } //请求结果 if (function.Equals("connectRedis")) { try { if (args.Length > 2) { client = new ServiceStack.Redis.RedisClient(args[0], int.Parse(args[1]), args[2]); } else { client = new ServiceStack.Redis.RedisClient(args[0], int.Parse(args[1])); } output.Append("success"); return 0; } catch (Exception e) { output.Append("fail"); return 0; } } //储存请求 if (function.Equals("sendMsg")) { try { if (args.Length > 2) { var timeOut = new TimeSpan(0, 0, 0, int.Parse(args[2])); if (client.Set<string>(args[0], args[1], timeOut)) { output.Append("success"); } else { output.Append("fail"); } } else { if (client.Set<string>(args[0], args[1])) { output.Append("success"); } else { output.Append("fail"); } } } catch (Exception e) { output.Append("fail"); return 0; } } if (function.Equals("getMsg")) { try { string value = client.Get<string>(args[0]); output.Append(value); return 0; } catch(Exception e) { output.Append("fail"); return 0; } } return 0; } } } 这是个简单的实现ARMA3连接Reids缓存服务器的源代码,使用C#实现 自行编译为dll,放置服务的目录下即可。 食用方法: //在服务端初始化后首先连接redis服务器,只需要连接一次不能重复执行如要重复请修改源代码。 //参数: [ip,端口,密码],redis有密码: "ArmaMapsExt_x64" callExtension ["connectRedis",["127.0.0.1","6379","123456"]] //redis无密码: "ArmaMapsExt_x64" callExtension ["connectRedis",["127.0.0.1","6379"]] //储存一个字符串至redis缓存服务器,参数:[key,value] //缓存无限期: "ArmaMapsExt" callExtension ["sendMsg",["usename","小明"]] //缓存有限期(单位:秒): "ArmaMapsExt" callExtension ["sendMsg",["usename","小明","60"]] //从key中获取一个缓存字符串,参数:[key] _data = "ArmaMapsExt" callExtension ["getMsg",["username"]] //如果命中缓存则返回值,否则返回空字符串 注意:全程只能在服务器上执行! 模组也只能服务器挂载 如果你要在客户端测试,必须关闭be,否则会被阻止! MOD: 编译好的MOD
×
×
  • Create New...