使用HTTP POST方法录入战绩信息–骑砍战绩查询实现2

本文将分为以下两个部分介绍暮色服战绩查询系统录入数据库的方法

  • 使用c#异步发送HTTP POST请求到网页服务器
  • 网页服务器使用php接受请求并写入数据库

理论上来讲,是可以使用c#直接访问服务器上数据库进行数据的录入的。但是考虑到我们今后可能将这一功能应用于更多的服务器,或对数据进行更进一步的预处理,使用web api来进行这一操作是合适的。

我不打算将具体的技术细节放到这样一篇文章中来介绍,因此,我已写了几篇文章用于介绍本文中应用到的编程技巧。同时,这也将是一篇十分简短的文章,只是为有意建设类似系统的玩家提供思路。

public async static Task AddEvent(Entry entry)
{  
    HttpClient sendLogClient = new HttpClient();
    var values = new Dictionary<string, string>
    {
        { "token", "xxxxx" },
        { "type", entry.type.ToString() },
        { "subj", entry.subj },
        { "obj", entry.obj},
        { "time", entry.time.ToString("yyyy-MM-dd HH:mm:ss") },
    };
    if (entry.type == EntryType.kill)
    {
        values.Add("teamkill", (entry.is_team_kill ? "1" : "0"));
        values.Add("weapon", entry.weapon);
    }
            
    var content = new FormUrlEncodedContent(values);

    try
    {
        HttpResponseMessage response = await sendLogClient.PostAsync("https://api.knightdusk.cn/set_entry.php", content);
        AddLog(response.ToString());
    }
    catch(TaskCanceledException)//超时重试
    {
        Task.Delay(10000).Wait();
        AddEvent(entry).Wait();
    }
    catch(Exception exp)
    {
        AddLog(exp.Message);
    }
}

使用HTTP POST协议向游戏服务器发送数据

首先,Entry结构体包含了骑砍服务器一条日志中的所有数据,由程序的日志分析部分构造并传递给本函数。其定义是:

struct Entry
{
    public EntryType type;
    public DateTime time;
    public string subj; //动作的发出者
    public string obj;  //事件为击杀时,被击杀的玩家
                        //事件为聊天时,聊天的内容
                        //事件为进入时,玩家的uid
    public bool is_team_kill; //仅当事件为击杀时才有效
    public string weapon; //击杀武器,同上
}

程序首先使用Dictionary以键值对的形式保存这些数据,然后利用FormUrlEncodedContent方法形成HTTP POST方法所需要的数据。PostAsync方法即向链接发送数据。c#中没有此函数的同步版本,如果不想研究异步执行的相关知识则可以直接使用Wait方法,如下:

HttpResponseMessage response = sendLogClient.PostAsync(url,content).Wait();

对于异步方法而言,超时时抛出的异常并不是TimeOutException,而是TaskCanceledException。这里用递归调用自己实现了一个超时重试的方法,但有一致命缺陷:没有限制重试次数。为方法增加一个参数用于记录递归调用层数即可解决此问题。

在读取日志主循环中使用该异步方法:

public void ReadFile(string filepath,DateTime date)
{   
    //这里打开文件
    Task task;//声明变量
    while (streamReader.Peek() > 0)//读取完毕之前,循环
    {
         /*这里是读取日志数据的字符串处理过程
         详情请见我的骑砍战绩查询系列文章的第一篇
         下面是异步逻辑*/
        if (task != null)//等待上次的异步http post结束
            task.Wait();
        task = PhpApiCaller.AddEvent(entry);
        //省略一些后续工作
    }
    if (task != null)//等待最后一次http post结束
    task.Wait();
}

由于访问网络资源是比较缓慢的,日志处理部分用到大量字符串处理函数,其速度也较慢。因此,这里使用异步方法以期提高效率。其思路是,在发送上一条日志对应的HTTP请求的同时开始处理下一条日志。从暮色骑士团骑砍服务器每天最多处理2万余条日志的情况来看,这种处理可以有效提高性能。但如上文所讲,如不会也不想研究异步编程,可使用下面语句替换上述循环体内的代码,并删除外面的两行:

 PhpApiCaller.AddEvent(entry).Wait(); 

设置服务器状态

我们还希望网站服务器能够了解到游戏服务器正在向其发送数据,并将此状态反馈给网页用户。因此我们制作了设置状态的api接口,并在读取日志的程序中如下进行使用:

enum ServerStatusToSet { ServerSendDataStart, ServerSendDataEnd };
//这个枚举类型包含了可以设置的状态
public async static Task SetStatus(ServerStatusToSet status_to_set)
{
    HttpClient setStatusClient = new HttpClient();
    var values = new Dictionary<string, string>
    {
        { "token", "XXX" },
        {"status",status_to_set.ToString() }
        //枚举类型ToString之后得到的就是它的名字,例如"ServerStatusToSet"
    };
    var content = new FormUrlEncodedContent(values);

    try
    {
        var response = await setStatusClient.PostAsync("https://api.knightdusk.cn/SetStatus.php", content);
        AddLog(response.ToString());
    }
    catch (Exception exp)
    {
        AddLog(exp.Message);
    }
}

其使用方法是类似的,在开始录入之前和结束录入之后调用它设置状态即可

在服务器端接受请求并写入数据库(使用php)

header("Access-Control-Allow-Origin: *");
$token = $_REQUEST["token"];
$type = $_REQUEST["type"];
$time = $_REQUEST["time"];
$subj = $_REQUEST["subj"];
$obj = $_REQUEST["obj"];
if(isset($_REQUEST["teamkill"]))
     $is_team_kill = $_REQUEST["teamkill"]; 
if(isset($_REQUEST["weapon"])) 
    $weapon = $_REQUEST["weapon"];
if($token!="xxxxxx") 
    exit();//检查token是否正确 
$con = mysqli_connect("127.0.0.1","SomeUser","SomePassword","SomeDatabase"); //连接到数据库 
mysqli_query($con,"set names 'utf8'"); 
if (!$con) 
{ 
    echo mysqli_error(); 
    exit(); 
} 
switch($type) 
{
case 'join': 
    mysqli_query($con,"INSERT INTO join_info (playername,uid,join_time) VALUES ('$subj','$obj','$time')"); 
    break; 
case 'kill': 
    mysqli_query($con,"INSERT INTO kill_record (killer,killed,weapon,is_team_kill,time) VALUES ('$subj','$obj','$weapon','$is_team_kill','$time')"); 
    break; 
case 'chat': 
    mysqli_query($con,"INSERT INTO chat_record (time,player,content) VALUES ('$time','$subj','$obj')"); 
    break; 
} 
mysqli_close($con); 

很简单的录入数据库操作而已,并无太多可讲之处。但有以下几点需要做出说明:

  • 此程序不移除所有与bot有关的数据。这项工作交给了数据库内的存储过程来完成。
  • 此程序不查重复记录。

设置服务器状态的程序则更为简单,直接更新某张存储状态的表内的值即可。

发表评论

电子邮件地址不会被公开。 必填项已用*标注