cocos2dx-lua_XMLHttpRequestTest分析

以下是win7、cocos2dx-3.10 lua开发


1.概述
需求一个能上传音频文件到指定服务器,且能从指定服务器下载音频文件的功能。
在cocos2dx的测试用例中发现了XMLHttpRequestTest。
经过分析,发现cocos2dx中的XMLHttpRequest完全是仿照AJAX的XMLHttpRequest写的。
cocos2dx中的XMLHttpRequest其底层实现还是HttpRequest,只不过在lua中被重新封装了。
目前,cocos2dx的XMLHttpRequestTest的演示还很粗糙,其没有关于发送数据的具体操作演示。


cocos2dx的XMLHttpRequest的基本思路:
(1)少量的不敏感的数据通过get来传递,但url的长度有限是225个字符,所以如果数据多的话就用post来传递。
(2)post是把form中的数据放入html流的头(head)中传递。


2.分析
2.1.cocos2dx中与XMLHttpRequest有关的lua脚本
(1)
NetworkConstants.lua------------
cc.XMLHTTPREQUEST_RESPONSE_STRING       = 0   //字符串
cc.XMLHTTPREQUEST_RESPONSE_ARRAY_BUFFER = 1   //二进制
cc.XMLHTTPREQUEST_RESPONSE_BLOB         = 2
cc.XMLHTTPREQUEST_RESPONSE_DOCUMENT     = 3
cc.XMLHTTPREQUEST_RESPONSE_JSON         = 4   //json格式


(2)
DeprecatedCocos2dClass.lua--------------


--XMLHttpRequest class will be Deprecated,begin
function DeprecatedClass.XMLHttpRequest()
    deprecatedTip("XMLHttpRequest","cc.XMLHttpRequest")
    return cc.XMLHttpRequest
end
_G["XMLHttpRequest"] = DeprecatedClass.XMLHttpRequest()
--XMLHttpRequest class will be Deprecated,end


2.2.查找对应的cpp
lua_xml_http_request.h-------------
enum class ResponseType{
STRING,
ARRAY_BUFFER,
BLOB,
DOCUMENT,
JSON
};


static const unsigned short UNSENT = 0;            //初始化状态。XMLHttpRequest 对象已创建或已被 abort() 方法重置。
static const unsigned short OPENED = 1;            //open() 方法已调用,但是 send() 方法未调用。请求还没有被发送。
static const unsigned short HEADERS_RECEIVED = 2;  //send方法已经调用,尚未开始接受数据;
static const unsigned short LOADING = 3;           //正在接受数据。Http响应头信息已经接收,但尚未接收完成;
static const unsigned short DONE = 4;              //响应数据接收完成。


lua_xml_http_request.cpp-------------
...
TOLUA_API int register_xml_http_request(lua_State* L)
{
    tolua_open(L);
    lua_reg_xml_http_request(L);
    tolua_module(L,"cc",0);
    tolua_beginmodule(L,"cc");
      tolua_cclass(L,"XMLHttpRequest","cc.XMLHttpRequest","cc.Ref",lua_collect_xml_http_request);
      tolua_beginmodule(L,"XMLHttpRequest");
        tolua_variable(L, "responseType", lua_get_XMLHttpRequest_responseType, lua_set_XMLHttpRequest_responseType);
        tolua_variable(L, "withCredentials", lua_get_XMLHttpRequest_withCredentials, lua_set_XMLHttpRequest_withCredentials);
        tolua_variable(L, "timeout", lua_get_XMLHttpRequest_timeout, lua_set_XMLHttpRequest_timeout);
        tolua_variable(L, "readyState", lua_get_XMLHttpRequest_readyState, nullptr);
        tolua_variable(L, "status",lua_get_XMLHttpRequest_status,nullptr);
        tolua_variable(L, "statusText", lua_get_XMLHttpRequest_statusText, nullptr);
        tolua_variable(L, "responseText", lua_get_XMLHttpRequest_responseText, nullptr);
        tolua_variable(L, "response", lua_get_XMLHttpRequest_response, nullptr);
        tolua_function(L, "new", lua_cocos2dx_XMLHttpRequest_constructor);
        tolua_function(L, "open", lua_cocos2dx_XMLHttpRequest_open);
        tolua_function(L, "send", lua_cocos2dx_XMLHttpRequest_send);
        tolua_function(L, "abort", lua_cocos2dx_XMLHttpRequest_abort);
        tolua_function(L, "setRequestHeader", lua_cocos2dx_XMLHttpRequest_setRequestHeader);
        tolua_function(L, "getAllResponseHeaders", lua_cocos2dx_XMLHttpRequest_getAllResponseHeaders);
        tolua_function(L, "getResponseHeader", lua_cocos2dx_XMLHttpRequest_getResponseHeader);
        tolua_function(L, "registerScriptHandler", lua_cocos2dx_XMLHttpRequest_registerScriptHandler);
        tolua_function(L, "unregisterScriptHandler", lua_cocos2dx_XMLHttpRequest_unregisterScriptHandler);
      tolua_endmodule(L);
    tolua_endmodule(L);
    return 1;
}


new()                     //新建一个XMLHttpRequest对象
responseType              //服务器反馈的内容的类型,对应lua_xml_http_request.h中的枚举ResponseType,同时也对应上面2中的(1)
open(methode, url, async) //向url指向的服务器发送一个类型为methode的请求,async为true表示异步发送(默认)
                          //methode的类型:POST、PUT
registerScriptHandler(callFunction) //添加监听,当服务器反馈到客户端时会调用函数callFunction
unregisterScriptHandler() //解除监听,一般在callFunction中调用,以实现一个请求、只回应一次
readyState                //准备状态,请查看lua_xml_http_request.h
status                    //服务器返回的 HTTP 状态代码,200 表示成功,而 404 表示 "Not Found" 错误。具体请查看4
                          //当 readyState 小于 3 的时候读取这一属性会导致一个异常。
send(header)              //向服务器发出请求,如果采用异步方式,该方法会立即返回。
 //content可以指定为null表示不发送数据,其内容可以是DOM对象,输入流或字符串。
                          //username="zwcwu"&password="123456"
response                  //服务器返回的数据,按responseType类型不同,数据格式也不同
                          //cc.XMLHTTPREQUEST_RESPONSE_ARRAY_BUFFER  对应二进制数据
 //若二进制数据是一个文件,在lua中要按如下读取
 //local response   = xhr.response
 //local size     = table.getn(response)
 //local file = io.open("res/girl.wav","wb")
 //for i = 1, size do
 // file:write(string.char(response[i]))
 //end
 //file:close()
 //若二进制数据只是字符串信息,则按照XMLHttpRequestTest中演示的获取数据即可。
setRequestHeader(field,value) //写http头,如下
                              //xhr:setRequestHeader("Content-Type","audio/x-wav")    
 


2.3.服务器返回的 HTTP 状态代码
1xx: 信息
100 Continue 服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求。
101 Switching Protocols 服务器转换协议:服务器将遵从客户的请求转换到另外一种协议。


2xx: 成功
200 OK 请求成功(其后是对GET和POST请求的应答文档。)
201 Created 请求被创建完成,同时新的资源被创建。
202 Accepted 供处理的请求已被接受,但是处理未完成。
203 Non-authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝。
204 No Content 没有新文档。浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
205 Reset Content 没有新文档。但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容。
206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它。


3xx: 重定向
300 Multiple Choices 多重选择。链接列表。用户可以选择某链接到达目的地。最多允许五个地址。
301 Moved Permanently 所请求的页面已经转移至新的url。
302 Found 所请求的页面已经临时转移至新的url。
303 See Other 所请求的页面可在别的url下被找到。
304 Not Modified 未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取。
306 Unused 此代码被用于前一版本。目前已不再使用,但是代码依然被保留。
307 Temporary Redirect 被请求的页面已经临时移至新的url。


4xx: 客户端错误
400 Bad Request 服务器未能理解请求。
401 Unauthorized 被请求的页面需要用户名和密码。
402 Payment Required 此代码尚无法使用。
403 Forbidden 对被请求页面的访问被禁止。
404 Not Found 服务器无法找到被请求的页面。
405 Method Not Allowed 请求中指定的方法不被允许。
406 Not Acceptable 服务器生成的响应无法被客户端所接受。
407 Proxy Authentication Required 用户必须首先使用代理服务器进行验证,这样请求才会被处理。
408 Request Timeout 请求超出了服务器的等待时间。
409 Conflict 由于冲突,请求无法被完成。
410 Gone 被请求的页面不可用。
411 Length Required "Content-Length" 未被定义。如果无此内容,服务器不会接受请求。
412 Precondition Failed 请求中的前提条件被服务器评估为失败。
413 Request Entity Too Large 由于所请求的实体的太大,服务器不会接受请求。
414 Request-url Too Long 由于url太长,服务器不会接受请求。当post请求被转换为带有很长的查询信息的get请求时,就会发生这种情况。
415 Unsupported Media Type 由于媒介类型不被支持,服务器不会接受请求。
416 服务器不能满足客户在请求中指定的Range头。
417 Expectation Failed


5xx: 服务器错误
500 Internal Server Error 请求未完成。服务器遇到不可预知的情况。
501 Not Implemented 请求未完成。服务器不支持所请求的功能。
502 Bad Gateway 请求未完成。服务器从上游服务器收到一个无效的响应。
503 Service Unavailable 请求未完成。服务器临时过载或当机。
504 Gateway Timeout 网关超时。
505 HTTP Version Not Supported 服务器不支持请求中指明的HTTP协议版本。


2.4.结论
XMLHttpRequest只能实现简单的post请求,其没有实现上传文件的功能,只能自己添加相关类了。
在不修改cocos2dx源代码的前提下,最简单的方法就是实现c++的基于curl的上传文件的类,再在lua中调用。


3.创建上传文件项目
3.1.搭建php服务器
我在win7下安装了Visual AMP (x64),创建了一个网站:myWeb
设置其ip为本机,即127.0.0.1,端口为30
这里有可能会遇到php文件夹权限的问题,比如,你想把客户端上传的文件保存到Visual-AMP-x64/www/myWeb/sound,
请自己百度设置权限的解决方法。


在其默认网页文件夹Visual-AMP-x64/www/myWeb下,创建如下:
myWeb--
postTest.php    
uploadTest.php


postTest.php----------------------
echo $_POST["username"];  
if($_POST["username"]=="zwcwu" && $_POST["password"]=="123456")    
echo "Login Success"; //return to client    
else      
echo "Login Failed"; //return to client         
?> 


uploadTest.php----------------------
//打开名字为"file"的文件
$myFile = $_FILES["file"]["tmp_name"];  
$content = '';  
$fh = fopen($myFile, 'r') or die("can't open file");  
while (!feof($fh)) {  
    $content .= fgets($fh); 
}  
fclose($fh);  


//文件存储路径 
$file_path="sound/";
if(is_dir($file_path)!=TRUE) 
mkdir($file_path,0664) ; 


//文件名字
$myFile = $file_path.$_REQUEST['name'];  


//创建文件
$fh = fopen($myFile, 'w') or die("can't open file");  
$stringData = $content;
fwrite($fh, $stringData);  
fclose($fh);  
?>  


3.2.创建cocos2dx lua项目
testHttpDownload--
--res
  --testSound.wav
--src
  --main.lua
  --app
    --MyApp.lua
--views
 --MainScene.lua
--frameworks
  --runtime-src
    --Classes
 --AppDelegate.cpp
 --AppDelegate.h
 --lua_module_register.h
 --HttpUpload.cpp
 --HttpUpload.h
 --BindLuaAPI.cpp
 --BindLuaAPI.h
 
(1)主场景(这里是在其自动生成的MainScene.lua基础上修改的)  
MainScene.lua---------------------
local MainScene = class("MainScene", cc.load("mvc").ViewBase)


MainScene.RESOURCE_FILENAME = "MainScene.csb"


function MainScene:onCreate()
    release_print("MainScene")

--上传声音
cpp_uploadVoice("http://127.0.0.1:30/uploadTest.php","testSound.wav",cc.FileUtils:getInstance():getWritablePath())

--下载声音
--self:onPostBinary_Download()
end


--post测试
function MainScene:onMyPost()
    local xhr = cc.XMLHttpRequest:new()-- 新建一个XMLHttpRequest对象
    xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_STRING --返回数据为字符串
    xhr:open("POST", "http://127.0.0.1:30/postTest.php")
    
    local data = string.format("username=%s&password=%s", "zwcwu", "123456")
    local function onReadyStateChanged()
        if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then
            release_print("Http Status Code:" .. xhr.statusText)
            release_print(xhr.response)
        else
            release_print("xhr.readyState is:" .. xhr.readyState .. ",xhr.status is: " .. xhr.status)
        end
        xhr:unregisterScriptHandler()
    end
    xhr:registerScriptHandler(onReadyStateChanged)
    xhr:send(data)
end


--下载声音(这里连接到本机php服务器的时候一切正常,但连接到外网php服务器时要将"POST"改为"GET"才行,原因未知)
function MainScene:onPostBinary_Download()
    local xhr = cc.XMLHttpRequest:new()
    xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_ARRAY_BUFFER
    xhr:open("POST", "http://127.0.0.1:30/testSound.wav")
    local function onReadyStateChanged()
        if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then
            local response   = xhr.response
            local size     = table.getn(response)
            release_print("Http Status Code:" .. xhr.statusText .. ",size=" .. size)
            local file = io.open("res/testSound.wav","wb")
            for i = 1, size do
                file:write(string.char(response[i]))
            end
            file:close()
        else
            release_print("xhr.readyState is:" .. xhr.readyState .. ",xhr.status is: " .. xhr.status)
        end
        xhr:unregisterScriptHandler()
    end
    xhr:registerScriptHandler(onReadyStateChanged)
    xhr:send()
end


return MainScene


(2)添加绑定c++到lua的类
BindLuaAPI.h------------------------
#ifndef BindLuaAPI_h
#define BindLuaAPI_h


#include
#include "CCLuaEngine.h"


class BindLuaAPI{
public:
    static BindLuaAPI* getInstance();
    ~BindLuaAPI();
    void bind(lua_State* ls);
static int upload(lua_State* ls);
private:
    lua_State* m_ls;
};


#endif /* BindLuaAPI_hpp */


BindLuaAPI.cpp----------------------


#include "BindLuaAPI.h"
#include "HttpUpload.h"
#include "cocos2d.h"


USING_NS_CC;


static BindLuaAPI* _instance = nullptr;


BindLuaAPI* BindLuaAPI::getInstance(){
    if(_instance == nullptr){
        _instance = new BindLuaAPI();
    }
    return _instance;
}


BindLuaAPI::~BindLuaAPI(){
    if (_instance) {
        free(_instance);
        _instance = nullptr;
    }
}


void BindLuaAPI::bind(lua_State *ls){
    this->m_ls = ls;
    lua_register(ls, "cpp_uploadVoice", cpp_uploadVoice);
}


int BindLuaAPI::upload(lua_State* ls){
    const char* url = lua_tostring(ls, 1);
const char* fileName = lua_tostring(ls, 2);
const char* filePath = lua_tostring(ls, 3);
HttpUpload::getInstance()->upload(url, fileName, filePath);
    return 0;
}


(3)添加上传的类
HttpUpload.h----------------------
#ifndef HttpUpload_h
#define HttpUpload_h


#include
#include
#include
#include "cocos2d.h"
#include


USING_NS_CC;


//用来保存要发送的数据的结构体
struct MemoryStruct {
char *memory;
size_t size;
};


class HttpUpload{
public :
    static HttpUpload * getInstance();
size_t upload(const char* url, const char * fileName, const char * filePath);
size_t httpPost(const char* url, const char * fileName, const char * filePath);
static size_t respond_callback(uint8_t *dataPtr, size_t size, size_t nmemb, void *user_p);
static size_t progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
};


#endif /* HttpUpload_hpp */




HttpUpload.cpp----------------------
#include "HttpUpload.h"
#include "CCLuaEngine.h"
#include
#include


static HttpUpload * instance = nullptr ;


HttpUpload* HttpUpload::getInstance(){
    if (instance == nullptr) {
        instance = new HttpUpload();
    }
    return instance ;
}


size_t HttpUpload::upload(const char* url, const char * fileName, const char * filePath){
std::thread t1(&HttpUpload::httpPost, this, url, fileName, filePath);
t1.detach();
return 0;
}


size_t HttpUpload::httpPost(const char* url, const char * fileName, const char * filePath){
std::string str1 = filePath;
std::string str2 = fileName;
std::string file = str1 + "/" + str2;


CURL *_curl;
CURLcode res = CURLE_OK;
curl_httppost *m_formPost = 0;
curl_httppost *m_lastPost = 0;


_curl = curl_easy_init();
if (_curl){
curl_formadd(&m_formPost, &m_lastPost,
CURLFORM_COPYNAME, "name",
CURLFORM_COPYCONTENTS, fileName,
CURLFORM_END);
//注意这里的"file"对应uploadTest.php中的"file"
if (curl_formadd(&m_formPost, &m_lastPost,
CURLFORM_COPYNAME, "file",
CURLFORM_FILE, file.c_str(),
CURLFORM_END) != 0){
fprintf(stderr, "curl_formadd error.\n");
return -1;
}


curl_easy_setopt(_curl, CURLOPT_HEADER, 0);
curl_easy_setopt(_curl, CURLOPT_URL, url);
curl_easy_setopt(_curl, CURLOPT_TIMEOUT, 10);
curl_easy_setopt(_curl, CURLOPT_HTTPPOST, m_formPost);
curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, respond_callback);
curl_easy_setopt(_curl, CURLOPT_PROGRESSFUNCTION, progress_callback);


res = curl_easy_perform(_curl);
if (res != CURLE_OK){
fprintf(stderr, "%s\n", curl_easy_strerror(res));
curl_formfree(m_formPost);
return -1;
}
curl_easy_cleanup(_curl);


return (int)res;
}
return 0;
}


size_t HttpUpload::respond_callback(uint8_t *dataPtr, size_t size, size_t nmemb, void *user_p){
CCLOG("HttpUpload:send end!size=%ld,nmemb=%ld", size, nmemb);
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)user_p;
if(mem != null){
mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
if (mem->memory == NULL) {/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}


memcpy(&(mem->memory[mem->size]), dataPtr, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
printf("respond:%s", mem->memory);
}


return realsize;
}


size_t HttpUpload::progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow){
CCLOG("HttpUpload:progress_callback!ultotal=%f,ulnow=%f",ultotal,ulnow);
return 0;
}


(4)在AppDelegate.cpp中添加以下
AppDelegate.cpp----------------------
#include "BindLuaAPI.h"
bool AppDelegate::applicationDidFinishLaunching()
{
    // set default FPS
    Director::getInstance()->setAnimationInterval(1.0 / 60.0f);


    // register lua module
    auto engine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    lua_State* L = engine->getLuaStack()->getLuaState();


BindLuaAPI::getInstance()->bind(L);
...
}



你可能感兴趣的:(cocos2dx,cocos2d-x)