相关文章推荐
曾深爱过的乒乓球
·
分享:docker部署nginx-proxy ...
·
9 月前
·
讲道义的鞭炮
·
VC调试篇:ASSERT(FALSE)时怎么 ...
·
1 年前
·
没有腹肌的自行车
·
JsPlumb在react的使用方法及介绍 ...
·
1 年前
·
小眼睛的仙人球
·
PostgreSQL记录中的null值是如何 ...
·
1 年前
·
痴情的领结
·
Business ...
·
1 年前
·
Code
›
网络通信3:HTTP实现文本传输_ITPUB博客
http://blog.itpub.net/69975060/viewspace-2693823/
善良的刺猬
1 年前
HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,HTTP协议是建立在TCP协议之上的一种应用。由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种短连接。HTTP遵循请求(Request)/应答(Response)模型。所以一定需要实现请求(Gk8HttpRequest)。同时需要实现返回(Gk8HttpResponse)。在游戏研发编程中HTTP请求类型常用的就是GET和POST。框架设计中就需要实现完成HTTP请求和返回的Gk8HttpClient类。HTTP不管是文本传输还是二进制传输Gk8HttpClient类都能兼容完成响应的功能。HTTP文本传输方便简洁。实现轻量级通信的最佳方式。 libCurl作为是一个多协议的便于客户端使用的URL传输库,基于C语言,提供C语言的API接口。底层通信都是使用libCurl库来完成。具体可找相关资料和参阅相关代码。C语言封装开发的优势就是支持跨平台。Windows平台,安卓平台,IOS平台。跨平台迁移代码无需再次开发。一劳永逸的解决通信问题。为了方便开发时HTTP并发和回调。又实现了Gk8HttpServlet类(httpnet目录中)。高度封装的代码目的就是易于理解便于使用。 C++实现网络HTTP请求类:Gk8HttpRequest.h ```c++ #ifndef __GK8HTTPREQUEST_H__ #define __GK8HTTPREQUEST_H__ #pragma once #include "Gk8BaseObj.h" #include "Gk8ByteMaker.h" #include "Gk8Str.h" typedef GK8_VOID (Gk8BaseObj::*HTTPREQUEST_CALLBACK_FUN)(GK8_LPVOID pHttpResponse); class Gk8HttpRequest public: //[定义HTTP请求类型] typedef enum kHttpGet, kHttpPost, kHttpUnkown, }HttpRequestType; protected: HttpRequestType m_eRequestType; //[HTTP请求类型] Gk8Str m_sHttpUrl; //[HTTP请求地址] Gk8ByteMaker m_iRequestData; //[HTTP请求数据] Gk8BaseObj* m_pTarget; //[HTTP请求回调目标对象] HTTPREQUEST_CALLBACK_FUN m_pSelector; //[HTTP请求回调函数] public: Gk8HttpRequest() m_eRequestType=kHttpUnkown; m_sHttpUrl.Empty(); m_iRequestData.Destroy(); m_pTarget=NULL; m_pSelector=NULL; virtual ~Gk8HttpRequest() ClearRequestData(); inline GK8_VOID ClearRequestData() m_iRequestData.Destroy(); inline GK8_VOID SetRequestType(HttpRequestType eRequestType) m_eRequestType=eRequestType; inline HttpRequestType GetRequestType() return m_eRequestType; inline GK8_VOID SetHttpUrl(GK8_LPCSTR lpHttpUrl) m_sHttpUrl=lpHttpUrl; inline GK8_LPCSTR GetHttpUrl() return m_sHttpUrl; inline GK8_VOID SetRequestData(Gk8ByteMaker* pRequestData) pRequestData->ShiftTo(m_iRequestData); inline Gk8ByteMaker* GetRequestData() return &m_iRequestData; inline GK8_INT GetRequestDataSize() return m_iRequestData.GetStreamSize(); inline GK8_VOID SetResponseCallBack(Gk8BaseObj* pTarget,HTTPREQUEST_CALLBACK_FUN pSelector) m_pTarget=pTarget; m_pSelector=pSelector; inline Gk8BaseObj* GetTarget() return m_pTarget; inline HTTPREQUEST_CALLBACK_FUN GetSelector() return m_pSelector; #endif C++实现网络HTTP返回类:Gk8HttpResponse.h ```c++ #ifndef __GK8HTTPRESPONSE_H__ #define __GK8HTTPRESPONSE_H__ #pragma once #include "Gk8HttpRequest.h" class Gk8HttpResponse protected: Gk8HttpRequest* m_pHttpRequest; //[HTTP请求类] GK8_BOOL m_bSucceed; //[HTTP请求反馈成功] Gk8ByteMaker m_iResponseData; //[HTTP请求反馈数据] GK8_INT m_nResponseCode; //[HTTP请求反馈码] Gk8Str m_sErrorBuffer; //[HTTP请求错误信息] protected: GK8_BOOL InitWithRequest(Gk8HttpRequest* pHttpRequest); public: Gk8HttpResponse(Gk8HttpRequest* pHttpRequest) m_pHttpRequest=pHttpRequest; m_bSucceed=false; m_iResponseData.Destroy(); m_sErrorBuffer.Empty(); virtual ~Gk8HttpResponse() if(m_pHttpRequest) delete m_pHttpRequest; ClearResponseData(); inline GK8_VOID ClearResponseData() m_iResponseData.Destroy(); inline Gk8HttpRequest* GetHttpRequest() return m_pHttpRequest; inline GK8_VOID SetSucceed(GK8_BOOL bSucceed) m_bSucceed=bSucceed; inline GK8_BOOL IsSucceed() return m_bSucceed; inline GK8_VOID SetResponseData(Gk8ByteMaker* pResponseData) pResponseData->ShiftTo(m_iResponseData); inline Gk8ByteMaker* GetResponseData() return &m_iResponseData; inline GK8_VOID SetResponseCode(GK8_INT nResponseCode) m_nResponseCode=nResponseCode; inline GK8_INT GetResponseCode() return m_nResponseCode; inline GK8_VOID SetErrorBuffer(GK8_LPCSTR lpErrorBuffer) m_sErrorBuffer=lpErrorBuffer; inline GK8_LPCSTR GetErrorBuffer() return m_sErrorBuffer; #endif C++实现HTTP通信类:Gk8HttpClient.h ```c++ #ifndef __GK8HTTPCLIENT_H__ #define __GK8HTTPCLIENT_H__ #pragma once #include "Gk8HttpRequest.h" #include "Gk8HttpResponse.h" class Gk8HttpClient private: GK8_INT m_nTimeOutForConnect; //[连接超时时间] GK8_INT m_nTimeOutForRead; //[读取超时时间] private: Gk8HttpClient(); virtual ~Gk8HttpClient(); GK8_BOOL InitThreadSemphore(); public: inline GK8_VOID SetTimeoutForConnect(GK8_INT nTimeOutForConnect){m_nTimeOutForConnect=nTimeOutForConnect;}; inline GK8_INT GetTimeoutForConnect(){return m_nTimeOutForConnect;} inline GK8_VOID SetTimeoutForRead(GK8_INT nTimeOutForRead){m_nTimeOutForRead=nTimeOutForRead;}; inline GK8_INT GetTimeoutForRead(){return m_nTimeOutForRead;}; GK8_VOID Send(Gk8HttpRequest* pHttpRequest); GK8_VOID HttpClientTick(); static Gk8HttpClient* GetInstance(); #endif C++实现HTTP通信类:Gk8HttpClient.cpp ```c++ #include "Gk8HttpClient.h" #include "curl/curl.h" #include "Gk8OperSys.h" #include "Gk8SetMb.cpp" #include <queue> #include <pthread.h> #include <semaphore.h> #include <errno.h> #include <fcntl.h> static Gk8HttpClient* sg_pHttpClient=NULL; //[静态HTTP客户端] static GK8_BOOL sg_bHttpClientQuit=false; //[退出HTTP] static sem_t* sg_pSem=NULL; //[信号量的数据类型] static GK8_INT sg_AsyncRequestCount=0; //[同步请求数] //[线程及互斥定义] static pthread_t sg_NetWorkThread; //[HTTP线程] static pthread_mutex_t sg_RequestQueueMutex; //[HTTP请求队列互斥体] static pthread_mutex_t sg_ResponseQueueMutex; //[HTTP响应队列互斥体] static Gk8SetMb<gk8httprequest> sg_iRequestQueue; //[HTTP请求队列] static Gk8SetMb<gk8httpresponse> sg_iResponseQueue; //[HTTP响应队列] static GK8_CHAR sg_szErrorBuf[CURL_ERROR_SIZE]; //[错误信息] static Gk8Str sg_iErrorStr; typedef size_t (*HTTPWRITE_CALLBACK)(GK8_LPVOID lpData,size_t nSize,size_t nMemBlock,GK8_LPVOID lpResponseData); #if (GK8_TARGET_PLATFORM==GK8_PLATFORM_IOS) #define GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE 1 #else #define GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE 0 #endif #if GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE #define GK8_ASYNC_HTTPREQUEST_SEMAPHORE "Gk8HttpAsync" #else static sem_t sg_iSem; #endif GK8_BOOL ConfigureCURL(CURL* pCurl); GK8_INT ProcessGetTask(Gk8HttpRequest* pHttpRequest,HTTPWRITE_CALLBACK lpCallBack,GK8_LPVOID lpStream,GK8_LPINT lpResponseCode); GK8_INT ProcessPostTask(Gk8HttpRequest* pHttpRequest,HTTPWRITE_CALLBACK lpCallBack,GK8_LPVOID lpStream,GK8_LPINT lpResponseCode); //[接收HTTP数据] size_t WriteHttpData(GK8_LPVOID lpData,size_t nSize,size_t nMemBlock,GK8_LPVOID lpResponseData) Gk8ByteMaker* pResponseData=(Gk8ByteMaker*)lpResponseData; size_t nLength=nSize*nMemBlock; pResponseData->WriteBuf(lpData,(GK8_INT)nLength); return nLength; //[设置CURL超时属性设置] GK8_BOOL ConfigureCURL(CURL* pCurl) if(!pCurl) return false; CURLcode nCurlCode=curl_easy_setopt(pCurl,CURLOPT_ERRORBUFFER,sg_szErrorBuf); if(nCurlCode!=CURLE_OK) return false; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_TIMEOUT,Gk8HttpClient::GetInstance()->GetTimeoutForRead()); if(nCurlCode!=CURLE_OK) return false; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_CONNECTTIMEOUT,Gk8HttpClient::GetInstance()->GetTimeoutForConnect()); if(nCurlCode!=CURLE_OK) return false; curl_easy_setopt(pCurl,CURLOPT_SSL_VERIFYPEER,0L); curl_easy_setopt(pCurl,CURLOPT_SSL_VERIFYHOST,0L); curl_easy_setopt(pCurl,CURLOPT_NOSIGNAL,1L); return true; //[处理Get请求] GK8_INT ProcessGetTask(Gk8HttpRequest* pHttpRequest,HTTPWRITE_CALLBACK lpCallBack,GK8_LPVOID lpStream,GK8_LPINT lpResponseCode) CURLcode nCurlCode=CURL_LAST; CURL* pCurl=curl_easy_init(); if(!ConfigureCURL(pCurl)) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_URL,pHttpRequest->GetHttpUrl()); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_WRITEFUNCTION,lpCallBack); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_WRITEDATA,lpStream); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_FOLLOWLOCATION,true); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_USERAGENT,"libcurl-agent/1.0"); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_perform(pCurl); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_getinfo(pCurl,CURLINFO_RESPONSE_CODE,lpResponseCode); //[200:指示客服端的请求已经成功收到,解析,接受] if(nCurlCode!=CURLE_OK||*lpResponseCode!=200) nCurlCode=CURLE_HTTP_RETURNED_ERROR; }while(0); if(pCurl) curl_easy_cleanup(pCurl); return (nCurlCode==CURLE_OK?0:1); //[处理POST请求:流的行事发出] GK8_INT ProcessPostTask(Gk8HttpRequest* pHttpRequest,HTTPWRITE_CALLBACK lpCallBack,GK8_LPVOID lpStream,GK8_LPINT lpResponseCode) CURLcode nCurlCode=CURL_LAST; CURL* pCurl=curl_easy_init(); if(!ConfigureCURL(pCurl)) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_URL,pHttpRequest->GetHttpUrl()); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_WRITEFUNCTION,lpCallBack); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_WRITEDATA,lpStream); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_POST,1); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_POSTFIELDS,pHttpRequest->GetRequestData()->GetBuf()); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_setopt(pCurl,CURLOPT_POSTFIELDSIZE,pHttpRequest->GetRequestDataSize()); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_perform(pCurl); if(nCurlCode!=CURLE_OK) break; nCurlCode=curl_easy_getinfo(pCurl,CURLINFO_RESPONSE_CODE,lpResponseCode); if(nCurlCode!=CURLE_OK||*lpResponseCode!=200) nCurlCode=CURLE_HTTP_RETURNED_ERROR; }while(0); if(pCurl) curl_easy_cleanup(pCurl); return (nCurlCode==CURLE_OK?0:1); //[创建网络线程] static GK8_LPVOID NetWorkThread(GK8_LPVOID lpData) Gk8HttpRequest* pHttpRequest=NULL; Gk8HttpResponse* pHttpResponse=NULL; while(true) //Wait for http request tasks from main thread GK8_INT nSemWaitRet=sem_wait(sg_pSem); if(nSemWaitRet<0) _GK8ERR<GetRequestType()) case Gk8HttpRequest::kHttpGet: nRetValue=ProcessGetTask(pHttpRequest,WriteHttpData,pHttpResponse->GetResponseData(),&nResponseCode); break; case Gk8HttpRequest::kHttpPost: nRetValue=ProcessPostTask(pHttpRequest,WriteHttpData,pHttpResponse->GetResponseData(),&nResponseCode); break; default: _GK8ERR<SetResponseCode(nResponseCode); //[清除请求中的二进制数据] pHttpRequest->ClearRequestData(); if(nRetValue!=0) pHttpResponse->SetSucceed(false); sg_iErrorStr=sg_szErrorBuf; pHttpResponse->SetErrorBuffer(sg_iErrorStr); }else pHttpResponse->SetSucceed(true); pthread_mutex_lock(&sg_ResponseQueueMutex); sg_iResponseQueue.AddItem(pHttpResponse); pthread_mutex_unlock(&sg_ResponseQueueMutex); sg_AsyncRequestCount-=sg_iRequestQueue.GetSize(); pthread_mutex_lock(&sg_RequestQueueMutex); sg_iRequestQueue.Clear(); pthread_mutex_unlock(&sg_RequestQueueMutex); if(sg_pSem!=NULL) #if GK8_ASYNC_HTTPREQUEST_USE_NAMED_SEMAPHORE sem_unlink(GK8_ASYNC_HTTPREQUEST_SEMAPHORE); sem_close(sg_pSem); #else sem_destroy(sg_pSem); #endif sg_pSem=NULL; pthread_mutex_destroy(&sg_RequestQueueMutex); pthread_mutex_destroy(&sg_ResponseQueueMutex); //[依次删除数据] GK8_INT nIndex; for(nIndex=0;nIndex<sg_irequestqueue.getsize phttprequest="sg_iRequestQueue.GetItemAt(nIndex);" delete="" for="" phttpresponse="sg_iResponseQueue.GetItemAt(nIndex);" sg_irequestqueue.destroy="" sg_iresponsequeue.destroy="" pthread_exit="" return="" gk8httpclient::gk8httpclient="" :m_ntimeoutforread="" gk8httpclient::="" sg_bhttpclientquit="true;" if="" sem_post="" gk8_bool="" gk8httpclient::initthreadsemphore="" true="" gk8_async_httprequest_use_named_semaphore="" sg_psem="sem_open(GK8_ASYNC_HTTPREQUEST_SEMAPHORE,O_CREAT,0644,0);" _gk8err="" httprequest="" semaphore="" failed="" false="" gk8_int="" nsemret="sem_init(&sg_iSem,0,0);" pthread_mutex_init="" pthread_create="" pthread_detach="" gk8_void="" gk8httpclient::send="" pthread_mutex_lock="" sg_irequestqueue.additem="" pthread_mutex_unlock="" gk8httpclient="" gk8httpclient::getinstance="" sg_phttpclient="new" gk8httpclient::httpclienttick="" gk8httpresponse="">0) pHttpResponse=sg_iResponseQueue.GetItemAt(0); sg_iResponseQueue.RemoveItemAt(0); pthread_mutex_unlock(&sg_ResponseQueueMutex); if(pHttpResponse) --sg_AsyncRequestCount; Gk8HttpRequest* pHttpRequest=pHttpResponse->GetHttpRequest(); Gk8BaseObj* pTarget=pHttpRequest->GetTarget(); HTTPREQUEST_CALLBACK_FUN pSelector=pHttpRequest->GetSelector(); //[回调处理:记录数据] if(pTarget&&pSelector) (pTarget->*pSelector)(pHttpResponse); //[销毁请求数据] delete pHttpResponse; C++实现Servlet类:Gk8HttpServlet.h ```c++ #ifndef __GK8HTTPSERVICE_H__ #define __GK8HTTPSERVICE_H__ #pragma once #include "curl/curl.h" #include "Gk8HttpClient.h" #include "Gk8HttpRequest.h" #include "Gk8HttpResponse.h" class Gk8HttpServlet:public Gk8BaseObj DECLARE_TOSPP_MAP; private: Gk8Str m_sHttpUrl; //[HTTP请求地址] Gk8ByteMaker m_iRequestData; //[HTTP请求数据] Gk8Var m_iHttpCallVar; //[HTTP回调信息] GK8_VOID ServletRequestCompleted(GK8_LPVOID lpHttpResponse); public: Gk8HttpServlet(); ~Gk8HttpServlet(); GK8_VOID TOSPPFUNC SendHttpMessage(GK8_LPCSTR lpHttpData,Gk8Var& iHttpCallVar); #endif C++实现Servlet类:Gk8HttpServlet.cpp ```c++ #include "Gk8HttpServlet.h" #include "Gk8OperSys.h" #include "Gk8Helper.h" static Gk8Str sg_iReceiveMessageEvent("OnReceiveMessage");//[接收网络信息] #define HTTPSERVLET_FAIL 0 //[HTTP请求失败] #define HTTPSERVLET_SUCC 1 //[HTTP请求成功] /////////////////////////////////////////////CLASS-TOLUA//////////////////////////////////////////////////// TOLUA_CLASS_COLLECT_FUNC(Gk8HttpServlet) BEGIN_TOLUA_CLASS_FUNC(Gk8HttpServlet,NewObj) if(!CheckToLuaFunParam(L,"NewObj",Gk8Var()<<isusertable return="" gk8httpservlet="" phttpservlet="(Gk8HttpServlet*)Mtolua_new((Gk8HttpServlet)());" tolua_pushusertype="" if="" tolua_register_gc="" end_tolua_class_func="" begin_tolua_class_func="">SendHttpMessage(TOLUAGETSTRING(2),TOLUAGETFUNEX(L,3,0)); END_TOLUA_CLASS_FUNC //[启动注册类的全部TOLUA函数] BEGIN_TOLUA_FUN_MAP(Gk8HttpServlet) TOLUA_CLASS(Gk8HttpServlet,Gk8BaseObj,"[网络类]","[HTTP请求类]") TOLUA_CLASS_FUNC(Gk8HttpServlet,NewObj,"[Gk8HttpServlet* NewObj(GK8_BOOL ifLocal=false)?创建Gk8HttpServlet对象]") TOLUA_CLASS_FUNC(Gk8HttpServlet,SendHttpMessage,"[GK8_VOID SendHttpMessage(GK8_LPCSTR lpHttpData,iHttpCallVar)?发送HTTP请求,字符串模式]") END_TOLUA_FUN_MAP /////////////////////////////////////////////CLASS-TOSPP//////////////////////////////////////////////////// BEGIN_TOSPP_MAP(Gk8HttpServlet,Gk8BaseObj) TOSPP_FUNC(Gk8HttpServlet,SendHttpMessage,' ',"s&v","SendHttpMessage(lpHttpData,iHttpCallVar)") END_TOSPP_MAP() ///////////////////////////////////////////////////////////////////////////////////////////////// Gk8HttpServlet::Gk8HttpServlet() Gk8HttpServlet::~Gk8HttpServlet() //[发送HTTP请求注意编码问题] GK8_VOID Gk8HttpServlet::SendHttpMessage(GK8_LPCSTR lpHttpData,Gk8Var& iHttpCallVar) m_sHttpUrl=lpHttpData; //[注册脚本回调消息] m_iHttpCallVar=iHttpCallVar; Gk8Var iEmptyVar; if(m_iHttpCallVar.GetSize()==1 && m_iHttpCallVar[0].IfInt()) SetEvent(sg_iReceiveMessageEvent,m_iHttpCallVar[0],iEmptyVar); Gk8HttpRequest* pHttpRequest=new Gk8HttpRequest(); pHttpRequest->SetRequestType(Gk8HttpRequest::kHttpGet); pHttpRequest->SetHttpUrl(m_sHttpUrl); pHttpRequest->SetResponseCallBack(this,(HTTPREQUEST_CALLBACK_FUN)&Gk8HttpServlet::ServletRequestCompleted); pHttpRequest->SetRequestData(&m_iRequestData); Gk8HttpClient::GetInstance()->Send(pHttpRequest); //[HTTP请求信息回调] GK8_VOID Gk8HttpServlet::ServletRequestCompleted(GK8_LPVOID lpHttpResponse) Gk8HttpResponse* pHttpResponse=(Gk8HttpResponse*)lpHttpResponse; if(!pHttpResponse) return; //[把服务器数据派遣到脚本中] GK8_INT nFailCode=HTTPSERVLET_SUCC; Gk8Str iResponstStr; if(!pHttpResponse->IsSucceed()) _GK8ERR<GetErrorBuffer()<<cr iresponststr="UnKown Error" nfailcode="HTTPSERVLET_FAIL;" gk8bytemaker="" presponsedata="pHttpResponse-">GetResponseData(); iResponstStr.BinToStr(pResponseData->GetBuf(),pResponseData->GetStreamSize()); if(m_iHttpCallVar.GetSize()==1) RunEventWithArgs(sg_iReceiveMessageEvent,iResponstStr); }else if(!m_iHttpCallVar[0].IfPtr()||!IfObjPtr(m_iHttpCallVar[0].GetPtr(),m_iHttpCallVar[0].GetPtrId())) return; Gk8Obj* pBindObj=m_iHttpCallVar[0].GetSafePtr(); Gk8Var iParamVar; iParamVar<<nfailcode pbindobj-="">OnCall(m_iHttpCallVar[1],iParamVar); //[直接删除本身] SafeDeleteObj(this); 来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69975060/viewspace-2693823/,如需转载,请注明出处,否则将追究法律责任。 <div class="author-name"><a href="http://blog.itpub.net/69975060/">WolfHawk_ITPUB</a></div> 从事游戏研发10年。经历端游,页游,手游的时代。精通游戏技术架构和丰富的游戏项目经验。自己写过服务器框架和客户端引擎。希望沉淀的经验和大家交流与分享。 <p class="register-time"><span class="color77">注册时间:</span>2020-05-19</p> <p>广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员</p>
推荐文章
曾深爱过的乒乓球
·
分享:docker部署nginx-proxy-manager,图形化管理nginx反向代理,实现不同子域名访问家庭搭建的各类服务 - 知乎
9 月前
讲道义的鞭炮
·
VC调试篇:ASSERT(FALSE)时怎么办?查看调用堆栈 - smallpi - 博客园
1 年前
没有腹肌的自行车
·
JsPlumb在react的使用方法及介绍 - 星涑 - 博客园
1 年前
小眼睛的仙人球
·
PostgreSQL记录中的null值是如何存储的_51CTO博客_postgresql存储过程
1 年前
痴情的领结
·
Business School,Beijing Normal University
1 年前