查看: 2924|回复: 6

[通用发布] HTTP网络通信模块(外部EXE程序)

[复制链接]
梦石
0
星屑
1803
在线时间
133 小时
注册时间
2013-10-6
回帖
181
发表于 2015-2-22 17:07:38 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 不死鸟之翼 于 2015-5-17 12:38 编辑

    应朋友的要求随手写的一个小程序,封装了基于WinInet系列API实现的HTTP协议,可以较为简单地实现RM的异步网络通信功能。
    该模块主要解决了在RM中直接调用网络相关API的“10秒杀”问题。为了更易于使用,放弃了Win32中的IPC机制,改用命令行或者文件进行通信。

当前版本:Version 0.3 (201KB)
更新命令行直接文件下载功能。

https://azurefx.name/article/%e9 ... %e6%a8%a1%e5%9d%97/
使用方法如下(2楼和5楼直接给出了在RGSS环境下的具体代码实现,可以参考)

★用法1:命令行简易模式
直接用参数启动程序就可以完成将文件下载到指定路径。
"RMHttpDelegate.exe SourceURL DestiniationPath"
其中SourceURL为源URL地址,DestinationPath为保存文件路径
例如
"RMHttpDelegate.exe http://www.baidu.com PageSource.txt"

★用法2:后台服务模式 (RGSS端代码范例参见5楼)
工程启动时执行此模块,参数传入RM自身进程的PID。如"RMHttpDelegate.exe 1234"
推荐在RM工程启动时调用,将始终在后台保持运行状态,RM退出后自动清理资源并退出。内存占用在600KB左右,可以多开游戏而不会相互干扰。
RM需要进行网络通信时,首先获得Temp文件夹的目录,可用GetTempPath API或者%TEMP%环境变量。
在Temp目录创建一个文件RM_HTTP_REQUEST_XXXX,其中XXXX为RM的PID,编码为ANSI
文件总共含4行,分别为Server,Action,Method,Data。Server为服务器地址,Action为请求目标,Method为HTTP请求类型,Data为附加数据(这行可以省略)

示例1:HTTP GET

www.yourserver.com
game/submit.php?score=100
GET

示例2:HTTP POST

www.yourserver.com
game/submit.php
POST
score=100

示例3:获取百度搜索结果的网页源代码

www.baidu.com
s?wd=Hello,World
GET

RM_HTTP_REQUEST_XXXX文件会在请求收到后自动删除,并在原地生成一个RM_HTTP_RESULT_XXXX文件,其内容为Success或者Failure,分别代表成功或失败。如果成功,则还会生成另一个RM_HTTP_CONTENT_XXXX,其内容为获取到的数据。如遇到网络问题导致失败,请在游戏内提示用户并重试。RM_HTTP_REQUEST_XXXX推荐在读取内容后删除。
当游戏进程退出后此模块会一并退出,并自动删除相关的临时文件。
提示:使用GET方法时请不要填写第四行Data。

以上是使用方法,如果有疑问欢迎留言。

评分

参与人数 2星屑 +60 收起 理由
imsy + 30 又到了大角虫们出来收膝盖的季节.
ML4455739 + 30 精品文章

查看全部评分

←你看到一只经常潜水的萌新。
梦石
0
星屑
672
在线时间
2184 小时
注册时间
2009-12-6
回帖
571

开拓者

发表于 2015-2-22 18:08:58 | 显示全部楼层
本帖最后由 ML4455739 于 2015-2-23 13:47 编辑

楼下已经给出了一套完善的方案,所以敬请无视此楼……
[fold=请无视]

发一下可能有用的东西,个人整理,不代表楼主的观点。
相信不少人都看过这个脚本,wininet。
[pre lang="ruby"]# てつ
class Get_Version_Info
  AGENT = "RGSS PLAYER"
  INTERNET_OPEN_TYPE_PRECONFIG = 0
  INTERNET_OPEN_TYPE_DIRECT    = 1
  def Get_Version_Info.InternetOpen()
    io = Win32API.new('wininet', 'InternetOpen', %(p, l, p, p, l), 'l')
    hInternet = io.call(AGENT, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0)
    return hInternet
  end

  def Get_Version_Info.InternetOpenUrl(hSession, url)
    iou = Win32API.new('wininet', 'InternetOpenUrl', %(l, p, p, l, l, l), 'l')
    hService = iou.call(hSession, url, nil, 0, 0, 0)
    return hService
  end

  def Get_Version_Info.InternetReadFile(hService)
    content = ""
    read_size = 1024
    irf = Win32API.new('wininet', 'InternetReadFile', %(l, p, i, p), 'i')
    while(true)
      buf = "\0" * read_size
      red = [0].pack('L')
      irf.call( hService, buf, read_size, red )
      if(red.unpack('L') == [0])
        break
      end
      buf.delete!("\0")
      content += buf
    end

    return content
  end

  HTTP_QUERY_STATUS_CODE = 19
  def Get_Version_Info.HttpQueryInfo(hService)
    read_size = 1024
    buf = "\0" * read_size
    red = [read_size-1].pack('L')
    hqi = Win32API.new('wininet', 'HttpQueryInfo', %(l, l, p, p, p), 'i')
    isOk = hqi.call( hService, HTTP_QUERY_STATUS_CODE, buf, red, nil)
    buf.delete!("\0")
    return buf
  end

  def Get_Version_Info.InternetCloseHandle(hInternet)
    ich = Win32API.new('winInet', 'InternetCloseHandle', %(l), 'i')
    isOk = ich.call(hInternet)
    return isOk
  end

  HTTP_CODE_OK = "200"
   
  def Get_Version_Info.get_version(url)
    version = nil
    begin
      hInternet = InternetOpen()
      if(hInternet != 0)
        hService = InternetOpenUrl(hInternet, url)
        if(hService != 0)
          status_code = HttpQueryInfo(hService)
          if(status_code == HTTP_CODE_OK)
            version = InternetReadFile(hService)
          end
          InternetCloseHandle(hService)
        end
        InternetCloseHandle(hInternet)
      end
    rescue
      return nil
    end
    return version
  end
end[/pre]
虽然来路不太明确还是贴一下,不过用不上就是啦。
上面这个脚本能够实现RM获取一个指定地址的文本内容,严格上说是html代码,
如Get_Version_Info.get_version("http://www.baidu.com/")
这个脚本的问题就是,它会导致游戏进程阻塞,尤其容易在网络环境不佳时发生,
默认情况下,如果游戏超过10s没有调用一次Graphics.update方法,
就会弹出“脚本已备份”然后退出游戏,即使利用脚本根除10s问题,
长时间画面卡顿的等待,也是非常糟糕的体验。

楼主此帖的意义就在于,把访问网络的工作交给一个外部可执行程序去办,
而且楼主已经把这个程序提供给我们了,真的非常方便。
好了,废话到此为止。

1.  如何启动楼主提供的外部exe程序
[pre lang="ruby"]a = Win32API.new("shell32","ShellExecute","lppppl","l")
a.call(0,0,"RMHttpDelegate.exe","#{$$}",0,0)[/pre]
"RMHttpDelegate.exe"为楼主所提供的外部可执行程序的路径和文件名
$$是获取Game.exe的进程PID的,在任务管理器中可以找到,此外,
因为要把PID这个参数传给楼主的外部程序,这个需要把变量转换成字符串
用$$.to_s也行吧,不再赘述,总之这两行脚本就是启动楼主的程序了,
就如楼主所说,占用内存很小很小,所以游戏一开始就打开也没什么。

2. 如何获取临时文件夹路径,摘自这里,直接使用ENV['temp']应该也没问题
楼主的外部程序是通过Temp文件夹中的一个文件实现与RM游戏的通讯的
[pre lang="ruby"]get_temp_path = Win32API.new('kernel32', 'GetTempPath', 'lp', 'l')
temp_path = ' ' * 256
get_temp_path.call(256, temp_path)
temper = temp_path.strip
temper = temper.split("Temp")
temper = temper[0].gsub!("\\"){"/"}
temper += "Temp/"
# p temper
[/pre]
temper就是绝对路径了,我这台电脑测试时返回的是
"C:/Users/ADMINI~1/AppData/Local/Temp/"
直接使用ENV['temp']的话,返回的是这样
"C:\\Users\\ADMINI~1\\AppData\\Local\\Temp"


3. 如何通过文件实现游戏对外部程序的通讯
[pre lang="ruby"]f = open(temper + "RM_HTTP_REQUEST_#{$$}","w")
f.write("www.server.com\nlist.php\nPOST\nname=player&result=100&rand=#{rand(1000)}")
f.close
[/pre]
蛮普通的写法,总之文件的内容至少三行,POST方法传参数自然是四行
  1. www.server.com
  2. list.php
  3. POST
  4. name=player&result=100
复制代码
就像这样。换行是通过\n来控制的。
我是在测试网络排行榜,所以这里是使用了POST方法向服务器上的list.php传递了
name和result两个参数,rand是用来防止缓存的,别在意啦
总之要实现对外部程序的控制,就是利用那三行脚本,向RM_HTTP_REQUEST_XXXX这个文件
写入所需要的四行文字

4. 如何通过文件实现外部程序对游戏的通讯
游戏需要知道外部程序把工作完成的怎么样了
[pre lang="ruby"]sleep(1)
# sleep是等待1秒,不要也行吧,楼主的程序每秒都会读取一次文件内容
g = File.open(temper + "RM_HTTP_RESULT_#{$$}","r").read rescue nil
g = g.to_s
#g.gsub!(/0/) {}
#g.gsub!(/\r\n/) {}
if g.include?("Success")
  print "成功"
elsif g.include?("Failure")
  print "失败"
else
  print "未知错误"
end[/pre]
由我们的游戏这边向RM_HTTP_REQUEST_XXXX这一文件中写入指令,
外部程序读取并执行后,会在Temp目录中生成一个RM_HTTP_RESULT_XXXX文件,
该文件的内容为Success或Failure,意味成功和失败,
我们的游戏通过对其的判断,知道外部程序的工作是否完成。

5. 获取目标网页的内容
这一点就和最上方的那个脚本一样了,因此完全可以用来替代那个脚本,
在获取成功的情况下,除RM_HTTP_RESULT_XXXX文件外,
还会生成另一个RM_HTTP_CONTENT_XXXX文件,内容为获取到的数据
  1. con = Get_Version_Info.get_version("http://www.baidu.com/")
  2. p con
复制代码
这个是最上面那个脚本的方法,而对于楼主提供的工程
  1. con = File.open(temper + "RM_HTTP_CONTENT_#{$}","r").read rescue nil
  2. p con
复制代码
这样就好了。

6. 附带一个用浏览器打开指定地址的API
[pre lang="ruby"]
v = "http://www.baidu.com/"
Win32API.new('shell32.dll','ShellExecuteA',%w(p p p p p i),'i').call(0, 'open', v ,0, 0, 1)
[/pre]
如果是排行榜的话,刚才的工作已经把玩家player得到100分的情况告知了服务器,
只要打开排行榜网页,把排行信息给玩家们看就好了。


关于排行榜网页本身,可以进一步学习PHP语言,如果有人实在需要的话还请楼主补充一下,
因为本人的水平拙劣,代码风格奇特,恐不符大众口味,还望见谅。
楼主所提供的这个功能并不只是适用于POST方法的传递成绩,总之就是获取某网页的文本内容,
至于有什么用的话,比如获取网络时间,还可使用GET方法实现从服务器上获取最新版本号,等等…
虽然本楼最开始的脚本就能做到,但楼主的方法最大的优势就在于,非阻塞,异步执行,不卡顿。

关于该外部可执行程序的安全性问题,我无权给予评价,
反正已经经过此人肉鸡实测和毫无生命特征的360安全卫士V10.0.0.2001的扫描了。
有强迫症的朋友也不必担心,外部程序会随着Game.exe的退出而退出,
退出时还会自动删除相关的文件,保证干净利落。
希望更多的人用到这个东西。




V0.3追加的直接文件下载功能:
[pre lang="ruby"]a = Win32API.new("shell32","ShellExecute","lppppl","l")
a.call(0,0,"RMHttpDelegate.exe","https://rpg.blue/template/co_ilite/image/logo.png","logo.png",0,0)[/pre]
V0.3的这个外部程序是可以运行多个的,所以通过开启新程序下载文件,
传递的参数中不需要PID,但必须文件的下载路径,以及保存到本地的文件名,注意需要后缀名。
上例为下载66RPG的Logo,并以logo.png的文件名下载到本地,注意路径中的斜杠。

[/fold]
楼下已经给出了一套完善的方案,所以敬请无视此楼……

点评

ML大触何必如此…   发表于 2015-2-23 15:42
……请不要这样  发表于 2015-2-22 18:23
ML大触何必如此…  发表于 2015-2-22 18:21

评分

参与人数 1星屑 +30 收起 理由
kuerlulu + 30 手滑

查看全部评分

往事简直不堪回首,稍微做点想做的事吧
回复

使用道具 举报

梦石
0
星屑
167
在线时间
434 小时
注册时间
2009-1-1
回帖
557
发表于 2015-2-22 21:13:51 | 显示全部楼层
很好,这样就不用在连接的时候等待了
另外,能不能提供一下RMHttpD_0.2.exe的源码呢?
如果不方便公开的话可以私信我

评分

参与人数 1星屑 +30 收起 理由
kuerlulu + 30 手滑

查看全部评分

最近在研究XAS
回复

使用道具 举报

梦石
0
星屑
1803
在线时间
133 小时
注册时间
2013-10-6
回帖
181
 楼主| 发表于 2015-2-22 21:17:25 | 显示全部楼层
赛露休斯 发表于 2015-2-22 21:13
很好,这样就不用在连接的时候等待了
另外,能不能提供一下RMHttpD_0.2.exe的源码呢?
如果不方便公开的话 ...

目前更新很快,已经到0.3了。源码问题私下联系
←你看到一只经常潜水的萌新。
回复

使用道具 举报

梦石
0
星屑
76
在线时间
1379 小时
注册时间
2012-7-5
回帖
1643

开拓者

发表于 2015-2-22 21:44:13 | 显示全部楼层
给你们个我刚写的配套使用代码【本人的水平拙劣,代码风格奇特,恐不符大众口味,还望见谅。
[pre lang="ruby" file="RMHttp"]class Http
  Win32API.new("shell32","ShellExecute","lppppl","l").call(0,0,"RMHttpDelegate.exe","#{$$}",0,0)
  REQUEST = "RM_HTTP_REQUEST_#{$$}"
  RESULT  = "RM_HTTP_RESULT_#{$$}"
  CONTENT = "RM_HTTP_CONTENT_#{$$}"
  PATH = ENV['temp']
  def self.option(*strs)
    File.open("#{PATH}/#{REQUEST}", "w") {|f| f.write(strs.join("\n")) }
  end
  def self.check
    if File.exist?("#{PATH}/#{RESULT}")
      if File.read("#{PATH}/#{RESULT}") == "Success"
        return File.read("#{PATH}/#{CONTENT}")
      end
    end
    return nil
  end
  def self.dispose
    File.delete("#{PATH}/#{REQUEST}") if File.exist?("#{PATH}/#{REQUEST}")
    File.delete("#{PATH}/#{RESULT}")  if File.exist?("#{PATH}/#{RESULT}")
    File.delete("#{PATH}/#{CONTENT}") if File.exist?("#{PATH}/#{CONTENT}")
  end
end
END { Http.dispose }
[/pre]
食用姿势:
第一步 把主L的exe文件下载下来重命名为RMHttpDelegate.exe放到游戏根目录
第二步 仅供参考
  1. Http.option("www.baidu.com", "s?wd=Hello,World", "GET")
  2. switch = false
  3. loop do
  4.   Graphics.update
  5.    Input.update
  6.   if switch && Http.check
  7.     p Http.check
  8.     switch = false
  9.   end
  10. end
复制代码

点评

食用姿势  发表于 2015-2-23 14:17
嗯第一次的switch应该=true•﹏•  发表于 2015-2-23 11:15
就等着呢,造福R界  发表于 2015-2-22 21:48
赞!ww  发表于 2015-2-22 21:47

  -fk: -azogi:
回复

使用道具 举报

梦石
0
星屑
167
在线时间
434 小时
注册时间
2009-1-1
回帖
557
发表于 2015-2-23 22:07:24 | 显示全部楼层
感觉那个临时文件部分有安全隐患,通信的地址,数据都保存在里面,而且是明文,别人很容易获取,也有可能手动伪造个假的。最好能够不通过临时文件,直接与RM进行联系。我觉得RMHttpDelegate可以往这方面改进。
最近在研究XAS
回复

使用道具 举报

梦石
0
星屑
1803
在线时间
133 小时
注册时间
2013-10-6
回帖
181
 楼主| 发表于 2015-2-23 22:45:08 | 显示全部楼层
赛露休斯 发表于 2015-2-23 22:07
感觉那个临时文件部分有安全隐患,通信的地址,数据都保存在里面,而且是明文,别人很容易获取,也有可能手 ...

新的DLL模块已经写好,支持直接通信与多任务并行
←你看到一只经常潜水的萌新。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部