分类 C# 下的文章

C# 使用 HttpListener 開啟 Httpserver

this.listener = new HttpListener();
this.listener.Prefixes.Add(s);
this.listener.Start();
this.Accept();
但跑到 Accept 出現錯誤
http server error: 5 ---> System.Net.HttpListenerException:
26281-r1th1hsoif8.png

如果本地沒有使用root權限運行就會出現無法獲取監聽端口問題, 需要添加對應權限
netsh http add urlacl url=http://*:8081/ user=everyone

使用 nuget 安裝 StackExchange.Redis 套件
連線方式

ConfigurationOptions options = new ConfigurationOptions
{
    EndPoints = { { "127.0.0.1", 6379 } },
};
ConnectionMultiplexer muxer = ConnectionMultiplexer.Connect(options);
IDatabase conn = muxer.GetDatabase(1);

設定string數據

conn.StringSet("name", "chung  chen");
string name = conn.StringGet("name");

//get value async
var pending = conn.StringGetAsync("name");
string value2 = conn.Wait(pending);

HashSet

var list = new List<User>{
    new User { Index = 1, Name = "立白" },
    new User { Index = 2, Name = "妄为" },
    new User { Index = 3, Name = "毒妇" }
    };
foreach (var item in list){
    var hashEntries = new HashEntry[]{
        new HashEntry("ID", item.Index),
        new HashEntry("Name", item.Name)
    };
    conn.HashSet("User_" + item.Index.ToString(), hashEntries);
}

Subscribe

ISubscriber sub = muxer.GetSubscriber();
sub.Subscribe("messages", (channel, message) => {
    Log.Debug("Subscribe message1:" + (string)message);
});
sub.Publish("messages", "hello");

SortedSet

string rankTag = "rank_1001";
Dictionary<string, int> userScore = new Dictionary<string, int>(){
    { "1045", 70},
    { "1046", 23}
};
foreach (KeyValuePair<string, int> data in userScore){
    await conn.SortedSetAddAsync(rankTag, data.Key, data.Value);
}
//get 1045's score
double score = (double)await conn.SortedSetScoreAsync(rankTag, "1045");

//get 1045's rank number
int rankNumber = (int)await conn.SortedSetRankAsync(rankTag, "1046")

//get rank's member between 1 to 3
RedisValue[] rankList = await conn.SortedSetRangeByRankAsync(rankTag, 1, 3);

具體還有很多, 請參考下面對應表去使用
79041-rxzl0qz9ut9.png

服務端跑起來發現出現了很多警告, 警告內容是註冊消息 OpCode = 0, 警告如下

消息opcode为0: C2S_BomberGame_QueryFrameMessage

接著從錯誤的地方順著流程查看

List<Type> types = Game.EventSystem.GetTypes(typeof(MessageAttribute));
foreach (Type type in types)
{
    object[] attrs = type.GetCustomAttributes(typeof(MessageAttribute), false);
    if (attrs.Length == 0){
         continue;
    }
    MessageAttribute messageAttribute = attrs[0] as MessageAttribute;
    if (messageAttribute == null){
        continue;
    this.typeMessages.Add(messageAttribute.Opcode, Activator.CreateInstance(type));
    this.opcodeTypes.Add(messageAttribute.Opcode, type);
}

public ushort GetOpcode(Type type)
{
    return this.opcodeTypes.GetKeyByValue(type);
}

發現 opcodeTypes 跑到一半就crash了, 這個框架一直有個致命的問題就是流程上有exception部會跳出提示, 有時候就需要下來下斷點查看....有空真的必須好好看看這個問題怎麼解決

發現 Activator.CreateInstance(type) 執行到 MarketInfo 就掛了, 出現這個錯誤

'Activator.CreateInstance(type)' threw an exception of type 'System.MissingMethodException' object {System.MissingMethodException}

後來查看 MarketInfo.cs 發現了 constructor 沒有寫, 補上去就好了, 就這段

public MarketInfo() { }

56888-xind84hxker.png

在開發好友系統時, 做了個功能叫做推薦名單, 也就是將最近對戰的玩家記錄到數據表上, 再推給客戶端, 但在獲取數據時發現必須過濾掉已經添加的清單, 索性在表格上添加了一個字段來紀錄是否有效
45883-4ngtcpyj76m.png

後來發現Chloe處理這段很麻煩, 沒查到官方有 replace 的功能, 所以自己用了 procedure 來處理, 有點久沒用研究了下 Chole 怎麼調用

官方文檔說明
https://github.com/shuxinqin/Chloe/wiki/Oracle-procedure
42712-uac01obubl8.png

兩個寫法不一樣, dbContext.SqlQuery 是直接獲取 Procedure 返回數據的, 就譬如 procedure 最後面寫了 select * from user limit 1; 可以透過這個方式獲取返回的 user 資料, 而 ExecuteNonQuery("Proc_GetPersonName", CommandType.StoredProcedure, id, outputName) 則是透過 Procedure 的 out 獲取數據

我們在更新 recent 表格同時也要獲取 recent 數據, 作法如果

DbParam iUserId = new DbParam("@iUserId", userId);
DbParam iRUserId = new DbParam("@iRUserId", recentUserId);
DbParam iState = new DbParam("@iState", 1);
dbContext.SqlQuery<Tbl_Recent>("proc_update_recent", CommandType.StoredProcedure, iUserId, iRUserId, iState);

這是一個沒有 return 的 procedure 調用方式, Procedure 如下

CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `proc_update_recent`(
    IN `iUserId` int,
    IN `iRUserId` int, 
    IN `iState` int)
BEGIN
    DECLARE ts long;
    set ts = (UNIX_TIMESTAMP() * 1000) + (MICROSECOND(NOW(3)) / 1000);

    REPLACE into `recent`(`UserId` ,`RUserId`, `State`)
        value
    (iUserId, iRUserId, iState);
END

另外得益於使用 Procedure , 在發送邀請功能上需要判斷很多情況, 如果在 c# 處理邏輯就會很繁瑣, 後來使用 Procedure 解決, 如下

CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `proc_check_send_friendApply`(
    IN `iApplyUserId` BIGINT,
    IN `iApplyUserName` VARCHAR(64),
    IN `iApplyUserHead` VARCHAR(128),
    IN `iToUserId` BIGINT, OUT `oData` INT)
MAINLOOP:BEGIN

    # declare timestamp
    DECLARE ts BIGINT(20) default 0;
    DECLARE isMyFriend TINYINT(4) default 0;
    DECLARE findRecent TINYINT(4) default 0;
    DECLARE findFriend TINYINT(4) default 0;
    DECLARE findApply TINYINT(4) default 0;


    set ts = (UNIX_TIMESTAMP() * 1000) + (MICROSECOND(NOW(3)) / 1000);

    #已經是我的好友, 退出流程
    select count(*) into isMyFriend from `friend` where `UserId`=iApplyUserId and `FUserId`=iToUserId;
    if isMyFriend != 0 then
        LEAVE MAINLOOP;
    end if;

    #if 如果Recent裡面有數據, 就改掉 state = 1
    select count(*) into findRecent from `recent` where `UserId`=iApplyUserId and `RUserId`=iToUserId;
    if findRecent != 0 then
        REPLACE into `recent`(`UserId` ,`RUserId`,`State`) value (iApplyUserId, iToUserId, 1);
    end IF;

    #if 對方已經是好友就直接加回去, 
    select count(*) into findFriend from `friend` where `UserId`=iToUserId and `FUserId`=iApplyUserId;
    if findFriend != 0 then
        REPLACE into `friend`(`UserId` ,`FUserId`,`CreateTime`, `UpdateTime`) value (iApplyUserId, iToUserId, ts, ts);
        set oData = 2;
        LEAVE MAINLOOP;
    end if;

    #如果沒有邀請過就發邀請
    select count(*) into findApply from `friendapply` where `ApplyUserId`=iApplyUserId and `ToUserId`=iToUserId;
    if findApply = 0 then
        REPLACE into `friendapply`(`ApplyUserId` ,`ApplyUserName`, `ApplyUserHead`, `ToUserId`, `State`, `CreateTime`, `UpdateTime`)
        value
        (iApplyUserId, iApplyUserName, iApplyUserHead, iToUserId, 0, ts, 0);

        select * from `friendapply` where `ApplyUserId`=iApplyUserId and `ToUserId`=iToUserId limit 1;
        set oData = 1;
    end if;
END

很久以前在做第一版的麻將時發現那位服務端非常喜歡用存儲過程, 可能是因為服務端 nodejs 且沒有做數據持久化的緣故, 很多數據無法獲取必須重新拉數據, 這樣就會造成數據庫壓力很大, 最早接手開發RPG遊戲時必然是必須做數據持久化的, 設計到服務端乘載人數跟架構, 不做數據持久化管理壓力會壓在Mysql上, 後來做博奕遊戲就很少做這塊了, 輕便為主

先確定已經安裝 Chloe 組件後將 MySqlContext 加入組件
69059-p5rx6kxlnf.png

接著在 System 中完成讀取配置跟連接
22270-amvn97ot2re.png

mysql 配置如下
92468-dxzfrkvkz2.png

獲取數據

List<Tbl_UserStageInfo> userStageInfos = mySqlComponent.dbContext.Query<Tbl_UserStageInfo>().Where(item => item.UserId == UserId).ToList();

更新數據

mySqlComponent.dbContext.Update<Tbl_UserStageInfo>(userStageInfo);