客户端与服务器交互方式

这几天,看了些源码,梳理了下源码中,客户端和服务器端交互的部分


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_uicallback处理,此函数调用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);






猜你喜欢

转载自blog.csdn.net/shuilan0066/article/details/79792521