TeamTalk客户端源码分析五

版权声明:本文为博主原创文章,如需转载请注明出处 https://blog.csdn.net/bajianxiaofendui/article/details/85238520

     在上一章中我们介绍了线程池的概念和基本使用,在最后的例子中,DownloadAvatarHttpOperation::processOpertion()函数中使用到了Http::HttpRequest来实现http请求,使用起来非常方便,在TeamTalk中,这些http操作都是用原生的Socket在httpclient工程中封装实现的。虽然目前已经有很多常见的第三方网络库供我们使用,但这也不妨碍我们学习下http协议的封装,发包与回报的具体实现。

一,socket封装

     在httpclient工程的socket过滤器中包含了对原生socket操作封装的一些类文件,比如StreamingSocket封装了原生的socket,ProxySocket封装了支持代理的socket。socket_helper中就是一些函数对socket,connect,recv这些操作的封装。供StreamingSocket调用。

1,StreamingSocket

     socket通信的基本步骤:初始化socket,绑定ip和端口,发送请求,循环监听,Streamingsocket中是对这些操作进行了二次封装。
在这里插入图片描述
     其中_handle成员变量就是SOCKET对象(SOCKET就是一个int,typedef int SOCKET),在Streamingsocket中主要就做两件事:1)保存传进来的ip和短裤;2)调用socket_helper.h文件中的函数来操作_handle。
     以connect为例,在Streamingsocket::connect函数中保存ip和port,然后调用了SocketHelper::connectSocket。
在这里插入图片描述
     在SocketHelper::connectSocket中,在真正调用了API函数::socket和::connect来建立网络连接。
在这里插入图片描述
     Streamingsocket中的其他函数也都是一样的原理,最终都是调用socket_helper.h中的接口实现的。

2,ProxySocket

     这是一个支持代理的socket封装类,内部的原理和Streamingsocket是一样的,只不过因为是使用代理,进行网络连接的时候与Streamingsocket不一样,需要使用代理的ip和端口去发送网络请求(代理又可以分为HTTP代理,SOCKET4代理,SOCKET5代理,具体区别可自行查找资料),根据不同的代理类型,在connect的时候做不同的处理。
在这里插入图片描述
     HTTP代理和SOCKET5代理是需要进行身份认证的,所以在网络连接上后,需要发一次身份认证请求。关于代理的具体实现可以自己看下代码,很好理解。

二,HttpRequest

     HttpRequest类主要存储http请求的相关参数,包括请求方式(GET/POST),请求头,请求的url等信息。HttpRequest最重要的接口就是setRequest,将http请求方式和url传递进来,并对url解析得到主机名,域名,以及端口号,具体的解析方式封装在URLParser类中,主要是字符串的解析操作,在此不做过多描述,大家可以直接看代码。
在这里插入图片描述
     在HttpRequest中有很多接口,比如
void addField(const std::string& name,const std::string& value)添加HTTP体参数
void saveToFile(const std::wstring& filepath)设置文件的下载路径
void setBody(const std::string& body)设置HTTP包体内容
void addHeaderField(const std::string& name,const std::string& value);设置HTTP头字段
     总之,HttpRequest只是一个存储类,存储与HTTP请求相关的任何参数。

三,HttpResponse

     HttpResponse类也是一个数据存储类,主要对http请求的数据进行一个保存操作,并提供get接口给外部调用获取数据。
在这里插入图片描述

四,HttpClient

     HttpClient是具体的操作类,它最主要也最常用的接口就是bool execute(HttpRequest* request,HttpResponse* respone);对HttpRequest对象进行请求发送,将结果存储在HttpResponse中。
在这里插入图片描述
     在execute函数中,首先调用ProxySocket类来进行网络连接的建立(ProxySocket默认是不使用代理的,就相当于StreamingSocket),然后根据http是get还是post来调用httpGet或httpPost来发送请求。GET和POST的最大的区别就是GET请求只用发送一个HTTP头,POST请求除了发送HTTP头(头部也会添加fileds),还需要发送Body。
在这里插入图片描述
     在sendHeader中,通过HttpRequest对象来获取http头部信息

int		HttpRequest::generateHeader(std::string& header)
{
	header.clear();

	//请求方法和实体
	header.append(_http_method+" ");
	header.append(_object);
	header.append(" HTTP/1.1\r\n");

	//主机
	header.append("Host:");
	header.append(_http_host);
	//add by kuaidao host增加端口
	header.append(":");
	header.append(Util::num_to_string(_port_number));
	header.append("\r\n");

	//计算Body的长度,赋值给ContentLength
	if (_stricmp(_http_method.c_str(),kpost) == 0)
	{
		calcBody();
	}

	//添加Header Field
	size_t	field_size	=	_header_fields.size();
	for (size_t i = 0; i < field_size; i ++)
	{
		std::string&	name	=	_header_fields[i]._field_name;
		std::string&	value	=	_header_fields[i]._field_value;
		header.append(name	+	": " +	value	+	"\r\n");
	}
	
	//添加最后一个\r\n
	header.append("\r\n");

	///返回结果
	return (int)header.size(); 
}

     然后调用ProxySocket::writeAll(const char *source_buffer, int max_write_length)发送头部信息。
     如果是POST请求,还需要发送HTTP包体,也是首先HttpRequest对象来获取http包体信息,然后再通过ProxySocket::writeAll(const char *source_buffer, int max_write_length)发送包体信息。包体的填充是在HttpRequest::generateHeader(std::string& header)中调用的。具体实现在HttpRequest::calcBody()函数中。

五,具体功能业务举例

     主要的封装类和实现函数就介绍到这里,具体细节肯定是需要去研读代码的,在最后,还是以用户头像下载这个功能业务为例,将整个流程串起来过一遍。
     在DownloadAvatarHttpOperation::processOpertion()中创建了三个HTTP操作类对象(注意:不要用指针重复使用,因为一个HttpClient对应一个HttpRequest,一个HttpRequest对应一个socket)。在HttpRequest的构造函数中传入http请求类型"GET"和请求url(在构造函数中完成了url的解析,获取了ip,端口和域名相关信息),同时调用saveToFile设置文件下载路径。最后执行HttpClient::execute。
在这里插入图片描述
     在HttpClient::execute函数中,调用socket封装类ProxySocket来完成与远端ip的网络连接,并将HTTP包头和包体信息发送过去。然后直接在HttpClient::getResponse()中通过ProxySocket::read来读取回包信息,我们取回来的数据是包头和包体的混合数据,所以还需要对它们进行解析。最终解析结果填充在HttpResponse中。
在这里插入图片描述
     当然中间还涉及到了文件下载操作,使用到了HttpResponseReceiver类,在里面讲读取到的数据通过Util::FileWriter写到文件中。这些操作都是同步的,因为是在非UI线程的工作线程中处理的,不会影响界面程序。

     在自己的项目中,如果不想使用第三方库,可以直接把这个工程移植过去,接口都很简单,使用起来非常方便。

猜你喜欢

转载自blog.csdn.net/bajianxiaofendui/article/details/85238520