hpsocket实现HTTP通信功能

前段时间朋友急需让我帮忙搞定个C++的http库????然后指定了hpsocket这个库。看了一下是国人写了,资料在doc还算比较全,整理了很多框架的部分也挺详细,正好在补基础,所以学习下http后端的一些概念,也学习学习别人的代码。时间比较匆忙,就按照从先从自身的需求出发再到后面可以研究库的源代码思路进行。

这个库是跨平台的,我选择的环境平台是Linux。我的电脑是ubuntu16的
测试demo git

填充

0.目标

作为工作首先要解决的工作:
实现一个HTTP服务,可以实现HTTP收发的功能。

1.编译安装

编译
./compile.sh 

需要把HTTP这选项打开
作者在说明文档中标注了demo/目录不参与这个命令的编译。
demo下的项目都是sln组织的,需要安装和配置Visual C++ for Linux Development插件。
还有部分是编译是通过ndk,这部分也先不管他。
另外这个好像要C++14编译。所以GCC的版本也需要要求5.X以上。

安装
./install.sh

这步我就不装了没啥必要,编译完了就OK了,前面那步会把边的库放在lib目录下

2.实现

实现主要分为两部分:客户端和服务器。服务器是是实现重点。

2.1.服务器

服务器的参考代码我是参考demo/testecho-http的代码。
参考代码中加了命令行控制的代码,看着代码就比较多。这边我把代码重新整理一下,写一个新的demo。新的demo里我就考出了http服务

首先除了库的头文件,包括依赖到一个文件,再demo/gloabl/下的helper.cpp及helper.h。helper.cpp和helper中间包括一些宏定义,配置参数,命令行类之类的,可能还涉及到其他demo中需要的一些宏和定义。

把中间和HTTP有关有用的抽出来放到新建的myhelper.cpp和myhelper.h里。

myhelper.h

首先是包括的头文件

#include 
#include 
#include 
#include 
#include 
#include 

//#include "include/hpsocket/SocketInterface.h"
//#include "include/hpsocket/HPTypeDef.h"
//#include "include/hpsocket/common/GlobalDef.h"
#include "src/common/StringT.h"      
#include "src/HttpServer.h"
#include "src/common/FuncHelper.h"

这里我一开始想用include的库的头文件,发现好多类没有(不过像一些类其实完全可以用自己实现)。这里为了拖原demo方便,就先使用到src源代码的头文件了。不过这里的接口也确实很迷???

然后再试一些服务器参数宏

#define IPV4_LOOPBACK_ADDRESS	_T("127.0.0.1")
#define IPV6_LOOPBACK_ADDRESS	_T("::1")
#define IPV4_ANY_ADDRESS		_T("0.0.0.0")
#define IPV6_ANY_ADDRESS		_T("::")
#define DEF_MULTI_CAST_ADDRESS	_T("233.0.0.1")
#define BROAD_CAST_ADDRESS		_T("255.255.255.255")
#define DEF_TCP_UDP_PORT		5555
#define DEF_HTTP_PORT			8080
#define DEF_HTTPS_PORT			8443

#define TCP_KEEPALIVE_TIME		(60 * 1000)
#define UDP_DETECT_ATTEMPTS		3

...

#define HTTP_NAME				_T("http")
#define HTTPS_NAME				_T("https")
#define STR_HTTP_SCHEMA			"http://"
#define STR_HTTPS_SCHEMA			"https://"
#define CRLF				"\r\n"
#define NV_SEPARATOR_CHAR			'='
#define HEADER_SEPARATOR			": "
#define COOKIE_TOKENIZE			"; "
#define STR_HTTP_1_0			"HTTP/1.0"
#define STR_HTTP_1_1			"HTTP/1.1"
#define HOST_HEADER			"Host"
#define COOKIE_HEADER			"Cookie"
#define SET_COOKIE_HEADER			"Set-Cookie"
#define CONTENT_TYPE_HEADER		"Content-Type"
#define CONTENT_LENGTH_HEADER		"Content-Length"
#define TRANSFER_ENCODING_HEADER	        "Transfer-Encoding"
#define UPGRADE_HEADER			"Upgrade"
#define WEB_SOCKET_HEADER_VALUE		"WebSocket"

#define HTTP_METHOD_POST			"POST"
#define HTTP_METHOD_PUT			"PUT"
#define HTTP_METHOD_PATCH			"PATCH"
#define HTTP_METHOD_GET			"GET"
#define HTTP_METHOD_DELETE			"DELETE"
#define HTTP_METHOD_HEAD			"HEAD"
#define HTTP_METHOD_TRACE			"TRACE"
#define HTTP_METHOD_OPTIONS		"OPTIONS"
#define HTTP_METHOD_CONNECT		"CONNECT"

#define HTTP_WEB_SOCKET_SEC_SALT	        "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

这些不一定全部用到。

参数类:

struct app_arg
{
    static char OPTIONS[];
 CString remote_addr;
 USHORT port;
 CString bind_addr;
 USHORT local_port;
 CString reject_addr;
    ...
 }


这个参数类在原来的demo里其实就是用来存放可执行文件输入动态的参数的。我们这里用不到,用默认值固定它就好了。所以把原程序的几个public方法的定义和实现都去了。然后保留那个全局变量。。。后面就用这个参数

app_arg g_app_arg;

后面会讲到如何使用这个参数,不过这个参数本质就是个容器,用来存放一系列参数。

HTTP相关的类

原demo中对于http的使用顺序是这样的
首先定义一个接口类

class CHttpServerListenerImpl : public CHttpServerListener
{
private:
	virtual EnHandleResult OnPrepareListen(ITcpServer* pSender, SOCKET soListen);
	...
	virtual EnHandleResult OnWSMessageComplete(IHttpServer* pSender, CONNID dwConnID);
public:
	CHttpServerListenerImpl(LPCTSTR lpszName): m_strName	(lpszName){}
public:
	CString m_strName;
};

可以看出这个接口类继承于CHttpServerListener。定义再SocketInterface.h中它的描述如下

名称:IHttpServerListener 监听器抽象基类
描述:定义某些事件的默认处理方法(忽略事件)

这个底层其实继承了IHttpListenerT和 ITcpServerListener的接口,也就是http和tcp的接口。这个等后面分析源代码的时候再细讲。

对CHttpServerListenerImpl 类进行实现,这里其实就是TCP和HTTP的几种事件的回调的实现,简单的说就是,发生上面的事件之后会调用对应的函数进行执行。对于每个事件的含义在SocketInterface.h中都有详细的接口说明。这部分的在后面再说。

然后实例化HTTP接口

CHttpServerListenerImpl s_listener_1(HTTP_NAME);
CHttpServerListenerImpl s_listener_2(HTTPS_NAME);

接着创建一个CHttpServer对象,同时绑定到对应的接口

CHttpServer s_http_server(&s_listener_1);
CHttpsServer s_https_server(&s_listener_2);

这里关注下CHttpServer 这个类。整个类会在后面解释,其实这是个库的内部类,不能被调用

typedef CHttpServerT<CTcpServer, HTTP_DEFAULT_PORT> CHttpServer;

是个CHttpServerT的模板类,默认端口是80,另一个参数是CTcpServer的类

template class CHttpServerT : public IComplexHttpResponder, public T
{
	using __super = T;
 public:
     .... //一堆操作接口
 public:
	CHttpServerT(IHttpServerListener* pListener)
	: T					(pListener)
	, m_pListener		(pListener)
	, m_bHttpAutoStart	(TRUE)
	, m_enLocalVersion	(DEFAULT_HTTP_VERSION)
	, m_dwReleaseDelay	(DEFAULT_HTTP_RELEASE_DELAY)
	{}
 ...
}

他的定义可以看出来,继承HTTP的接口类,简单理解为借助CTcpServer功能的实现,绑定前面的接口。所以这个类的对象是对HTTP进行控制管理的关键。

重新回到程序代码中,因为在原有的代码中,程序为了使用的灵活性增加了CHttpCommandParser类,但提取主要功能这部分就不要了。这个类其实就是解析终端输入命令,然后执行不同动作。

fnCmds[CCommandParser::CT_START]	= (CCommandParser::CMD_FUNC)OnCmdStart;
fnCmds[CCommandParser::CT_STOP]		= (CCommandParser::CMD_FUNC)OnCmdStop;
fnCmds[CCommandParser::CT_STATUS]	= (CCommandParser::CMD_FUNC)OnCmdStatus;
fnCmds[CCommandParser::CT_SEND]		= (CCommandParser::CMD_FUNC)OnCmdSend;
fnCmds[CCommandParser::CT_PAUSE]	= (CCommandParser::CMD_FUNC)OnCmdPause;
fnCmds[CCommandParser::CT_KICK]		= (CCommandParser::CMD_FUNC)OnCmdKick;
fnCmds[CCommandParser::CT_KICK_L]	= (CCommandParser::CMD_FUNC)OnCmdKickLong;
fnCmds[CCommandParser::CT_KICK_S]	= (CCommandParser::CMD_FUNC)OnCmdKickSilence;	

动作就是上述几个函数的回调。

整理下我们需要啥:

  1. 启动OnCmdStart
  2. 查看状态OnCmdStatus();
  3. 停止OnCmdStop

好像主要就3步。不过这个是过程非阻塞的,所以我们退出手动控制退出getchar()

int main(int argc, char* const argv[])
{
	s_http_server.SetKeepAliveTime(g_app_arg.keep_alive ? TCP_KEEPALIVE_TIME : 0);
	OnCmdStart();
	OnCmdStatus();
	getchar();
	OnCmdStop();
	return 0;
}

另外可能要先设置下一时间连接时间。原demo里这个先设置了。

这样整个过程就完成了。

接口回调

接着就是接收数据怎么处理了。全部在那几个接口实现的函数里。如果想了解整个过程在每个回调里都打印信息就可以更加直观了解了。

注意:这里需要了解HTTP报文的格式。

然后说一些重点的。(这里之前编译的问题搞来搞去,偷懒我就把都放在myhelper.cpp里了)

/*
* 名称:请求行解析完成通知(仅用于 HTTP 服务端)
* 描述:请求行解析完成后,向监听器发送该通知
*	
* 参数:		pSender		-- 事件源对象
*			dwConnID	-- 连接 ID
*			lpszMethod	-- 请求方法名
*			lpszUrl		-- 请求行中的 URL 域
* 返回值:	HPR_OK		-- 继续执行
*		HPR_ERROR	-- 引发 OnParserError() 和 OnClose() 事件并关闭连接
*/
EnHttpParseResult CHttpServerListenerImpl::OnRequestLine(IHttpServer* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl)
{
	printf("OnRequestLine: method:%s    url:%s \n", lpszMethod, lpszUrl);
	return HPR_OK;
}

这个函数里面可以得到url然后还有请求参数,请求方法之类的。可以进行解析参数,数据封装之后发给业务处理。

/*
* 名称:请求头通知
* 描述:每当解析完成一个请求头后,向监听器发送该通知
*		
* 参数:		pSender		-- 事件源对象
*			dwConnID	-- 连接 ID
*			lpszName	-- 请求头名称
*			lpszValue	-- 请求头值
* 返回值:	HPR_OK		-- 继续执行
*		HPR_ERROR	-- 引发 OnParserError() 和 OnClose() 事件并关闭连接
*/
EnHttpParseResult CHttpServerListenerImpl::OnHeader(IHttpServer* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue)
{
	printf("OnHeader: 请求头名称:%s 请求头值:%s\n", lpszName, lpszValue);
	return HPR_OK;
}

得到头部信息字段和值,同样按需要获取

/*
* 名称:BODY 报文通知
* 描述:每当接收到 HTTP BODY 报文,向监听器发送该通知
*		
* 参数:		pSender		-- 事件源对象
*			dwConnID	-- 连接 ID
*			pData		-- 数据缓冲区
*			iLength		-- 数据长度
* 返回值:	HPR_OK		-- 继续执行
*			HPR_ERROR	-- 引发 OnParserError() 和 OnClose() 事件并关闭连接
*/
EnHttpParseResult CHttpServerListenerImpl::OnBody(IHttpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength)
{
	printf("OnBody: size:%d 主体消息: %s\n", iLength, pData);
	return HPR_OK;
}

同样的获得报文主体。

其他还有很多事件的回调处理,具体参考说明

——————————————————————————

发送应答

demo中发送应答在消息完成后(这个其实不一定)

EnHttpParseResult CHttpServerListenerImpl::OnMessageComplete(IHttpServer* pSender, CONNID dwConnID)
{
    ...//一堆组发送的内容
    THeader header[] = {{"Content-Type", "text/plain"}, {"Content-Length", strContentLength}, {"Set-Cookie", strSeqCookie1}, {"Set-Cookie", strSeqCookie2}};
	int iHeaderCount = sizeof(header) / sizeof(THeader);
    pSender->SendResponse(	dwConnID,
			HSC_OK,
			"HP Http Server OK",
			header, iHeaderCount,
			(const BYTE*)(LPCSTR)strBody,
			iBodyLength);
	if(!pSender->IsKeepAlive(dwConnID))
		pSender->Release(dwConnID);
	return HPR_OK;
}

前面用了一堆代码组strBody发送整体,这个我没时间整理了。随便改了下,就是发到报文的主体内容变化,不过我demo里是有问题的。
然后就是组织应答报文头部之类的。

2.2.客户端

客户端的实现比较简单甚至都可以不用实现,
大概方法有这么几个:

  • 用demo/Release/下的hp-testecho-http-client.exe可执行程序进行测试
  • curl命令进行测试
  • 浏览器url直接测试

3.编译过程中的问题总结

这个问题出现让我意识到了,真的只能说人丑就要多读书。然后多读书的机会来了。现在趁着有时间好好总结下问题:

代码结构和存放目录
HP-Socket-dev\Linux\mytest
mytest是我新建的目录,里面放着
在这里插入图片描述
关于编译:
我可能是对这个库有误解。一开始直接用g++的命令编译。发现各种未定义的到怀疑人生。没办法就写了makefile

CC = g++ -std=c++14  -w
TARGET = server_test
LDFLAG = -L../lib/hpsocket/x64/
LIBS =  -pthread  -lpthread  -lhpsocket -ldl
CFLAG = -I ../

OBJS = myserver_test.o myhelper.o #$(wildcard /home/awe/HP-Socket-dev/Linux/lib/hpsocket/x64/obj/Release/*.o)

SRC_FILE = myserver_test.cpp myhelper.cpp
 
$(TARGET) : $(OBJS)
	@echo $(CFLAG)
	$(CC) -g $(CFLAG)  $(OBJS) $(LDFLAG) $(LIBS) -o $(TARGET) 

myserver_test.o: myserver_test.cpp
	$(CC) -c $^ -o $@ $(CFLAG)  $(LDFLAG) $(LIBS)

myhelper.o: myhelper.cpp
	$(CC) -c $^ -o $@ $(CFLAG)  $(LDFLAG) $(LIBS)

但是写了makefile后依旧是一堆未引用的问题。

——————————————————————————————————————
其实这个的makefile主要的争议点在哪里呢?
在于链接so库时候的引用的定义。

这里先改回makefile到链接libhpsocket.so库
在这里插入图片描述
列举了一些找不到的引用,但在我的认识中,这些应该都在libhpsocket.so库里的。为什么编译不过。

抱着这个怀疑。我让他直接链接.o文件。将OBJS变量改称如下(就是去掉#):

OBJS = myserver_test.o myhelper.o $(wildcard /home/awe/HP-Socket-dev/Linux/lib/hpsocket/x64/obj/Release/*.o)

然后编译就通过了。。。。也说明libhpsocket.so有问题,同时证明我确实编译了HTTP模块了,因为在编译的时候又让选择是否编译某些模块。

接着我把-lhpsocket去了,发现编译报错,但这次报的是ssl这里的引用错误。这个错误在依赖之内,毕竟我没把依赖库的其他库加进来。这样有说明了个问题,也就是libhpsocket.so确实在工作了。

我甚至都怀疑打包的时候是不是漏掉了某几个.o。然后就改了complish.sh脚本,找到编译生成libhpsocket.so的编译指令。。

g++ -o lib/hpsocket/x64/libhpsocket.so -Wl,--no-undefined -Wl,-Ldependent/x64/lib -Ldependent/x64/lib -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack 
 -shared -Wl,-Bsymbolic -Wl,-soname,libhpsocket.so.5 
 lib/hpsocket/x64/obj/Release/ArqHelper.o 
 lib/hpsocket/x64/obj/Release/BufferPool.o 
 lib/hpsocket/x64/obj/Release/Crypto.o 
 lib/hpsocket/x64/obj/Release/Event.o 
 lib/hpsocket/x64/obj/Release/FileHelper.o 
 lib/hpsocket/x64/obj/Release/FuncHelper.o 
 lib/hpsocket/x64/obj/Release/HPSocket.o 
 lib/hpsocket/x64/obj/Release/HPSocket-SSL.o 
 lib/hpsocket/x64/obj/Release/HPThreadPool.o 
 lib/hpsocket/x64/obj/Release/HttpAgent.o 
 lib/hpsocket/x64/obj/Release/HttpClient.o 
 lib/hpsocket/x64/obj/Release/HttpCookie.o 
 lib/hpsocket/x64/obj/Release/HttpHelper.o 
 lib/hpsocket/x64/obj/Release/http_parser.o 
 lib/hpsocket/x64/obj/Release/HttpServer.o 
 lib/hpsocket/x64/obj/Release/ikcp.o 
lib/hpsocket/x64/obj/Release/IODispatcher.o 
lib/hpsocket/x64/obj/Release/MiscHelper.o 
lib/hpsocket/x64/obj/Release/PollHelper.o 
lib/hpsocket/x64/obj/Release/RWLock.o 
lib/hpsocket/x64/obj/Release/SocketHelper.o 
lib/hpsocket/x64/obj/Release/SSLAgent.o 
lib/hpsocket/x64/obj/Release/SSLClient.o 
lib/hpsocket/x64/obj/Release/SSLHelper.o 
lib/hpsocket/x64/obj/Release/SSLServer.o 
lib/hpsocket/x64/obj/Release/SysHelper.o 
lib/hpsocket/x64/obj/Release/TcpAgent.o 
lib/hpsocket/x64/obj/Release/TcpClient.o 
lib/hpsocket/x64/obj/Release/TcpPackAgent.o 
lib/hpsocket/x64/obj/Release/TcpPackClient.o 
lib/hpsocket/x64/obj/Release/TcpPackServer.o 
lib/hpsocket/x64/obj/Release/TcpPullAgent.o 
lib/hpsocket/x64/obj/Release/TcpPullClient.o 
lib/hpsocket/x64/obj/Release/TcpPullServer.o 
lib/hpsocket/x64/obj/Release/TcpServer.o 
lib/hpsocket/x64/obj/Release/Thread.o 
lib/hpsocket/x64/obj/Release/UdpArqClient.o 
lib/hpsocket/x64/obj/Release/UdpArqServer.o 
lib/hpsocket/x64/obj/Release/UdpCast.o 
lib/hpsocket/x64/obj/Release/UdpClient.o 
lib/hpsocket/x64/obj/Release/UdpNode.o 
lib/hpsocket/x64/obj/Release/UdpServer.o 
-ljemalloc_pic -lz -lssl -lcrypto -lrt -ldl -pthread发现所有的该目录下的所有.o文件都被包括了???????那为啥还不行???????

看看报错的句子然后拿CHttpServerT类进行检测下:

nm libhpsocket.so | grep "CHttpServerT"

发现是有结果的,说明确实被链接进去了,但为什么引不到呢?

但是发现符号的说明基本都是t ,d ,r
查看man说明:t是text段, d是初始化数据段,r是只读数据段。
但在说明里这里有句话非常可疑:

if lowercase, the symbol is usually local; if uppercase, the symbol is global(external).

小写的也就是说这个是本地的符号,大写的是可以给外部用的??

一个是libhpsocket.a的库是可以直接链接的。

nm libhpsocket.a | grep "CHttpServerT"

看到符号基本是W和U.也就是大写的。所以可以调用了

结论:

这个问题现在基本推到libhpsocket.so中的这几个符号是本地符号的问题。

也就是说本质上是因为libhpsocket.so里那个类是个本地类。事实上真正使用的还是IHttpServer这个接口指针,然后获取个这个类用HP_CreateHttpServer的方法

但如何链接成本地符号还是全局符号这个问题还没研究,下次看到整理到动态和和静态库的内容的时候再统一整理下这块。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值