这几天,看了些源码,梳理了下源码中,客户端和服务器端交互的部分
1、首先看看定义的几个相关类
CHttpRpc类,此类用于保存要连接的服务器信息
class CHttpRpc { public: char m_uri[128]; //服务器资源定位 // host:port char m_hostport[64]; //host char m_host[32]; int m_port; short m_ssl; short m_reserve; public: int LoadConf(const char* conffile,int dev); //从本地配置文件中,读取远程URI等信息 };
static CHttpRpc g_httprpc,g_httprpc_dev;
CUICallbackItem 类,保存窗口句柄,用以确定哪个窗口响应服务器返回的信息
class CUICallbackItem { public: HWND hWnd; // 窗口句柄,给此窗口发送自定义的服务器响应消息 WM_LAVA, 在此窗口中处理此消息 UINT uMsg; //消息类型 CUICallbackItem(HWND h, UINT u) { hWnd = h; uMsg = u; } };
CFormData 表单数据类,用于存储提交给要提交给服务器的数据
其中的Format是将表单数据格式化转换为字符串形式。
class HTTPD_API CFormData { MAPSTRSTR m_map; public: void AddPair(const char* key,const char* v) { m_map[key]=v; } //应该是将表单数据格式化为标准字符串形式 const char* Format(CBinBuf& form); };
CHttpRequest 请求类
class HTTPD_API CHttpRequest { char m_host[64]; char m_method[8]; char m_uri[1023]; char m_ssl; MAPSTRSTR* m_map; MAPSTRSTR* m_mParam; public: CHttpRequest() { m_ssl=0; m_map=m_mParam=0; } // host:port, GET|POST, /aaa/aaa.html?query_string CHttpRequest(const char* host,const char* method,const char* uri) { m_ssl=0; m_map=m_mParam=0; Set(host,method,uri); } ~CHttpRequest() { if(m_map) { delete m_map; m_map=0; } if(m_mParam) { delete m_mParam; m_mParam=0; } } // http[s]://host:port/abc.html?query_string, GET|POST void Set(const char* url,const char* method); // host:port, GET|POST, /aaa/aaa.html?query_string void Set(const char* host,const char* method,const char* uri) { strcpyN(m_host,host,sizeof(m_host)); strcpyN(m_method,method,sizeof(m_method)); strcpyN(m_uri,uri,sizeof(m_uri)); } int IsSsl() const { return m_ssl; } void SetSsl(int ssl) { m_ssl = ssl; } int GetHostPort(char* szHost, int size) const; // header void AddPair(const char* key,const char* v) { if(!m_map) m_map=new MAPSTRSTR; (*m_map)[key]=v; } // query_string void AddParam(const char* key,const char* v) { if(!m_mParam) m_mParam=new MAPSTRSTR; (*m_mParam)[key]=v; } const char* Format(CBinBuf& req,const char* ctype,const char* data,int l); };
CHttpResponse响应类
该类的主要作用 是格式化数据
// when act as http client or http server. class HTTPD_API CHttpResponse { MAPSTRSTR m_map; public: CBinBuf m_bf; public: CHttpResponse(){} void AddPair(const char* key,const char* v) { m_map[key]=v; } const char* Format(int code,const char* reason,int keepalive,const char* data=0,int len=0,int bHead=0,uint64_t ctlen=0); const char* Format(int code,const char* reason,int keepalive,MAPSTRSTR& mheader,const char* data=0,int len=0,int bHead=0,uint64_t ctlen=0); };
2、向服务器提交数据
向服务器提交数据,也就是客户端发送request请求
项目中,最终调用的是:
typedef int(*CALLBACK_HTTP_REQUEST_RESPONSE)(int trid,int code,CHttpResponseParse* resp); HTTPD_API int httpd_request(const char* host, int port, const char* data, int len, CALLBACK_HTTP_REQUEST_RESPONSE cb, int ssl);
这里面cb,是回调函数,服务器响应后,用此函数处理服务器返回的响应信息
对其进一步封装,一般调用这个就可以了。
int xnw_api_request(const char* uri, CFormData& form, HWND cb, int msgId) { CBinBuf bf; form.AddPair("ver", "2000"); char sz[32]; form.AddPair("gid",CGid(xnw_GetMyGid()).tostring(sz)); form.AddPair("passport", xnw_GetPsp()); form.Format(bf); //表单数据转换为字符串形式 CHttpRequest* req = httprpc_newreq("POST", uri); CUICallbackItem* cbitem = new CUICallbackItem(cb, msgId); int err=httprpc_request(req, "application/x-www-form-urlencoded;charset=utf-8", bf.c_str(), bf.GetLength(), (void*)cbitem); if (err != 0) delete cbitem; return err; }
// param 为窗口项信息:HWND MSG int httprpc_request(CHttpRequest* http,const char* ct,const char* data,int len,void* param,int dev) { CHttpRpc* ucapi=&g_httprpc; if(dev) ucapi=&g_httprpc_dev; http->AddPair("Connection","close"); CBinBuf bf; http->Format(bf,ct,data,len); delete http; int trid=httpd_request(ucapi->m_host,ucapi->m_port,bf,bf.GetLength(),cb_httpc_httprpc,ucapi->m_ssl); if(!trid) { writelog3(LOG_LEVEL_CRITICAL,"httprpc: httpc_request failed! (%s)",bf.c_str()); return LAVA_ERR_CONNECT; } //记录哪个窗口发来的信息 if(param) g_mRequestUC.Add2(trid,param); writelog3(LOG_LEVEL_PACKET,"->httprpc(%u): %s",trid,bf.c_str()); return LAVA_ERR_OK; }
3 处理服务器返回的响应信息
客户端向服务器发送信息,最终调用的是
httpd_request(ucapi->m_host,ucapi->m_port,bf,bf.GetLength(),cb_httpc_httprpc,ucapi->m_ssl);
其中,cb_httpc_httprpc便是处理服务器返回信息的回调函数
static int cb_httpc_httprpc(int trid,int code,CHttpResponseParse* resp) { if(void* param=g_mRequestUC.Remove(trid)) { if(resp) writelog3(LOG_LEVEL_PACKET,"<-httprpc(%d): code(%d) header: %s body: %s", trid,code,resp->m_header.GetPtr(),resp->m_body.c_str()); g_cb(param,code,resp); } return 0; }
这个g_cb 是cb_api_resp
static void cb_api_resp(void* param, int code, CHttpResponseParse* resp) { CUICallbackItem* cbitem = (CUICallbackItem*)param; const char* p = NULL; CBinBuf bf; if (resp) { if (resp->m_header.m_code == 302) { CBinBuf bfV; p = resp->m_header.GetHeaderValue("location",bfV); bf.Format("{"); int bComma = 0; CJsonObj::append_json_item(bf, "location", p, bComma); bf.AppendFormat("}"); p = bf.c_str(); } else { int len; p = resp->GetRawContent(bf, len); } } g_ui.callback(cbitem->hWnd, cbitem->uMsg, p); delete cbitem; }
这里面用到了一个隐藏窗口g_ui
staticCThreadToUI g_ui;
初始化时:
g_ui.Create(0, 0, L"UI", 0, rc, 0, 0);
服务器返回信息后,通过g_ui的callback处理,此函数调用callui,而callui是给相应的UI窗口发送WM_LAVAAGNT_MSG消息,对应UI窗口收到这个消息后,就可以根据服务器传来的数据,进行界面的调整。
callUI向对应的 int callui(HWND hWnd, WPARAM wParam, LPARAM lParam=NULL) { for (int n = 3; n > 0; --n) { if (PostMessage(hWnd, WM_LAVAAGNT_MSG, wParam, lParam)) return 1; } return 0; }
对应窗口的处理方式 LRESULT CourseListForm::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_LAVAAGNT_MSG) { m_json = (CJsonObj*)lParam; CJsonObj* json = (CJsonObj*)lParam;
4、示例
客户端请求封装如下:
// uri 服务器上资源接口 // form 提交的表单数据 // 处理服务器响应返回信息的窗口 // 消息类型 int xnw_api_request(const char* uri, CFormData& form, HWND cb, int msgId)
示例1
1、 void ClassListForm::request(std::string& qid, std::string& course_id) { m_qid = qid; m_course_id = course_id; if (m_qid == "-1") { add_test_items(); return; } CFormData form; form.AddPair("course_id", course_id.c_str()); form.AddPair("qid", qid.c_str()); form.AddPair("page", "1"); form.AddPair("limit", "400"); xnw_api_request("/demo/demo/get_list", form, m_hWnd);
示例2
void ClassListForm::req_enter_room(std::string& qid, std::string& course_id, std::string& chapter_id) { CFormData form; form.AddPair("course_id", course_id.c_str()); form.AddPair("qid", qid.c_str()); form.AddPair("chapter_id", chapter_id.c_str()); form.AddPair("page", "1"); form.AddPair("limit", "400"); if(xnw_api_request("/demo/demo/enter_class", form, m_hWnd, 2)<0) nim_comp::Toast::ShowToast(L"API请求失败!", 600, m_hWnd); }
示例3
3、 void CourseListForm::request() { m_course_list->RemoveAll(); add_test_item(); // Add Loading ... { ui::ListContainerElement* black_item = dynamic_cast<ui::ListContainerElement*>(ui::GlobalManager::CreateBoxWithCache(L"courselist/loading_item.xml")); ui::Label* label = (ui::Label*)black_item->FindSubControl(L"contact"); if (label) label->SetText(L"课程加载中..."); black_item->SetUTF8Name("0"); m_course_list->Add(black_item); } CFormData form; form.AddPair("limit", "9999"); xnw_api_request("/demo/demo/list", form, m_hWnd); }
消息处理
LRESULT CourseListForm::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_LAVAAGNT_MSG) { m_json = (CJsonObj*)lParam; CJsonObj* json = (CJsonObj*)lParam;
示例4
4、登陆有点特殊,通过一个隐藏窗口处理回调信息 void LoginForm::StartLogin( std::string username, std::string password ) { user_name_edit_->SetEnabled(false); password_edit_->SetEnabled(false); login_error_tip_->SetVisible(false); login_ing_tip_->SetVisible(true); btn_login_->SetVisible(false); btn_cancel_->SetVisible(true); Login(username.c_str(), password.c_str()); }
int Login(const char* account,const char* pass) { CFormData form; form.AddPair("account", account); form.AddPair("password", pass); return xnw_api_request("/demo/demo/login", form, g_ui.GetHwnd()); }
//登陆信息服务器响应,是由g_ui这个窗口本身处理 class CThreadToUI : public CWndX { LRESULT DefWndProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_LAVAAGNT_MSG) { on_login_resp(wParam,(CJsonObj*)lParam); } return DefWindowProc(m_hWnd, uMsg, wParam, lParam); }
5、Push server 推送服务器
前文描述的是正常的客户端请求、服务器响应回调函数方式。
核心就是客户端发送请求,服务器响应,客户端处理服务器返回的响应信息,根据服务器返回的信息调整界面
但是这种情况下,服务器无法主动推送给客户端信息,只有等着客户端发来请求,才能对这请求进行响应。客户端要是不请求,即使服务器重要的信息改变了,客户端也没有变化。
为此,服务器添加了一个push server,它的作用在于主动的给客户端推送信息
实现机制,应该是客户端与服务器建立一个长链接,服务器端根据情况,将服务器中的信息主动推送给客户端。
在项目的代码中,客户端接受服务器端主动推送过来信息的回调函数是:
class CMyLavaAgnt : public ILavaAgnt { // call backs int OnRecvNotifyData(DWORD id,const char* data,int len);