Bootstrap

Qt中的网络客户端

目录

HttpClient

http报文相关

HttpClient发送报文格式

x-www-form-urlencoded:

multipart/form-data

raw

binary

QUrl

QNetworkAccessManager

Http-Get

Http-Post

http-post:form-data

ftp-up

ftp-down

QDesktopServices


HttpClient

http报文相关

URL是为了 统一的命名网络中的一个资源(URL不是单单为了HTTP协议而定义的,而是网络上的所有的协议都可以使用) ;因此URL有以下特点:

  • URL是可移植的。(所有的网络协议都可以使用URL)

  • URL的完整性。(不能丢失数据,比如URL中包含二进制数据时,如何处理)

  • URL的可阅读性。(希望人能阅读)

    因为一些历史的原因URL设计者使用US-ASCII字符集表示URL。(原因比如ASCII比较简单;所有的系统都支持ASCII) 。

    http报文

    http报文是面向文本的,报文中每一个字段都是一些ASCII码串,各个字段的长度是不确定的 。http有两类报文:请求报文 响应报文 。

使用GET方法时,请求参数和对应的值附加在URL后面,利用一个问号("?")代表URL的结尾与请求参数的开始,传递参数长度受限制,例如: /index.jsp?id=100&op=bind 。

1.请求行

          请求行由请求方法字段、URL字段和HTT协议版本字段组成;空格隔开eg:GET /index.html HTTP/1.1

        HTTP请求的方法:GET POST HEAD PUT DELETE OTIONS TRACE CONNECT

       GET:一般客户端从server读取信息;请求参数和值附加在URL后面,?隔开。传递参数长度受限。eg:/etccharging?id=erdfwsaxnjdcdcd&secret=qw2dccbhcv;

        POST:可以传递更多的信息给服务器,将参数封装在HTTP请求数据中,可以传递大量数据,可用来传文件。

2.消息头部

        请求头部以键值对组成,一行一对,分号隔开,主要包含通知Server关于Client的请求信息;如:   

        User-Agent:产生请求的浏览浏览器类型;

        Accet:Client端可识别的内容列表

        Host:请求的主机名。

3.空行

        最后一个请求头之后是一个空行、发送回车符和换行符通知服务器请求头结束,是完整的Http报文必须格式(据此可以设计解析报文)。

4.请求正文

        请求数据不在GET方法中使用,一般POST中主要使用,通常设置与此相关头Content-Type 和Content-Length

HttpClient发送报文格式

multipart/form-data、x-www-form-urlencoded、raw、binary

x-www-form-urlencoded:

1,它是post的默认格式,使用URLencode转码方法。包括将name、value中的空格替换为加号;将非ascii字符做百分号编码;将input的name、value用‘=’连接,不同的input之间用‘&’连接 。

2、百分号编码:比如汉字‘武’吧,他的utf8编码在十六进制下是0xE69CA6,占3个字节,把它转成字符串‘E69CA6’,变成了六个字节,每两个字节前加上百分号前缀,得到字符串“%E6%9C%A6”,变成九个ascii字符,占九个字节。把这九个字节拼接到数据包里,这样就可以传输“非ascii字符的 utf8编码的 十六进制表示的 字符串的 百分号形式”

3,同样使用URLencode转码,这种post格式跟get的区别在于,get把转换、拼接完的字符串用‘?’直接与表单的action连接作为URL使用,所以请求体里没有数据;而post把转换、拼接后的字符串放在了请求体里 不会在浏览器的地址栏显示,稍微安全一些。

multipart/form-data

1、对于一段utf8编码的字节,用application/x-www-form-urlencoded传输其中的ascii字符没有问题,但对于非ascii字符传输效率就很低了(汉字‘武’从三字节变成了九字节),因此在传很长的字节(如文件)时应用multipart/form-data格式。smtp等协议也使用或借鉴了此格式。

2、此格式表面上发送一段一句话和一个文件,请求体如下

同时请求头里规定了Content-Type: multipart/form-data;

boundary=----WebKitFormBoundarymNhhHqUh0p0gfFa8

可见请求体里不同的input之间用一段叫boundary的字符串分割,每个input都有了自己一个小header,其后空行接着是数据。

multipart/form-data将表单中的每个input转为了一个由boundary分割的小格式,没有转码,直接将utf8字节拼接到请求体中(然后逐字节转码ASCII),在本地有多少字节实际就发送多少字节,极大提高了效率,适合传输长字节

raw

可以上传任意格式的文本,可以上传text、json、xml、html等。

binary

相当于Content-Type:application/octet-stream,从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件。

QUrl

对字符串进行 URL 格式化编码 ;

[static] QByteArray QUrl::toPercentEncoding(const QString &input, const QByteArray &exclude = QByteArray(), const QByteArray &include = QByteArray())

Returns an encoded copy of input. input is first converted to UTF-8, and all ASCII-characters that are not in the unreserved group are percent encoded. To prevent characters from being percent encoded pass them to exclude. To force characters to be percent encoded pass them to include.

Unreserved is defined as: ALPHA / DIGIT / "-" / "." / "_" / "~"

  QByteArray ba = QUrl::toPercentEncoding("{a fishy string?}", "{}", "s");
  qDebug(ba.constData());
  // prints "{a fi%73hy %73tring%3F}"
遵循这些原则,以避免在处理 URL 和字符串时,出现错误的字符转换:当从一个 QByteArray 或一个char * 创建包含 URL 的 QString 时,记得要用 QString::fromUtf8()。

URL格式

scheme

scheme 指定使用的传输协议,它由 URL 起始部分的一个或多个 ASCII 字符表示。scheme 只能包含 ASCII 字符,对输入不做转换或解码,必须以 ASCII 字母开始。

scheme 严格兼容 RFC 3986:scheme = ALPHA *( ALPHA / DIGIT / “+” / “-” / “.” )

协议描述
file资源是本地计算机上的文件。格式:file:///,注意后边应是三个斜杠。
ftp通过 FTP 访问资源。格式:FTP://
gopher通过 Gopher 协议访问该资源。
http通过 HTTP 访问该资源。格式:HTTP://
https通过安全的 HTTPS 访问该资源。格式:HTTPS://
mailto资源为电子邮件地址,通过 SMTP 访问。格式:mailto:
MMS通过支持MMS(流媒体)协议的播放该资源(代表软件:Windows Media Player)。格式:MMS://
ed2k通过 支持ed2k(专用下载链接)协议的P2P软件访问该资源(代表软件:电驴)。格式:ed2k://
Flashget通过 支持Flashget:(专用下载链接)协议的P2P软件访问该资源(代表软件:快车)。格式: Flashget://
thunder通过 支持thunder(专用下载链接)协议的 P2P 软件访问该资源(代表软件:迅雷)。格式: thunder://
news通过 NNTP 访问该资源。

下图显示了一个 URL,其 scheme 是 ftp

这里写图片描述

要设置 scheme,使用以下方式:

QUrl url;url.setScheme("ftp");12

Authority

URL 的 authority 由用户信息、主机名和端口组成。所有这些元素都是可选的,即使 authority 为空,也是有效的。

格式:username:password@hostname:port

用户信息和主机由'@' 分割,主机和端口由 ':'分割 。如果用户信息为空,'@' 必须被省略。尽管端口为空时,允许使用 ':'

这里写图片描述

host:指存放资源的服务器的域名系统(DNS)主机名或 IP 地址。

port:整数,可选,省略时使用方案的默认端口,各种传输协议都有默认的端口号,如 HTTP 的默认端口为80。如果输入时省略,则使用默认端口号。有时候出于安全或其他考虑,可以在服务器上对端口进行重定义,即采用非标准端口号,此时,URL 中就不能省略端口号这一项。

user info

user info 指用户信息,是 URL 中 authority 可选的一部分。

用户信息包括:用户名和一个可选的密码,由 ':' 分割。 如果密码为空,':'必须被省略。

这里写图片描述

path

由零或多个 / 隔开的字符串,一般用来表示主机上的一个目录或文件地址。在 authority 之后,query 之前。

这里写图片描述

对于没有层级的 schemes,路径将是 scheme 后的所有部分,像下面这样:

这里写图片描述

query

query 指查询字符串,可选,用于给动态网页(例如:使用 CGI、ISAPI、PHP/JSP/ASP/ASP、.NET 等技术制作的网页)传递参数,可有多个参数,用 & 隔开,每个参数的名和值用 = 隔开。

这里写图片描述

fragment

fragment 指定网络资源中的片断。是 URL 的最后一部分,由'#' 后面跟的字符串表示。通常指的是用于 HTTP 页面上的某个链接或点。

例如:一个网页中有多个名词解释,可使用 fragment 直接定位到某一名词解释。

这里写图片描述

fragment 有时也被称为 URL“引用”。

传递一个 QString()(null 字符串)将取消 fragment 的设置。传递一个参数 QString(“”)(空而非 null 字符串)将 fragment 设置为一个空字符串(和原始 URL 一样,只有一个 "#")。

QNetworkAccessManager

Http-Get


QString QHttpClient::Get(const QString&url, const QMap<QString, QString>&queryItemMap, const QMap<QString, QString>&headerMap)
{
	QUrl GetUrl(url);
	QNetworkRequest requestInfo;

	if (queryItemMap.isEmpty())
	{
		QUrlQuery query;//添加问好后面的key = value参数

		QMap<QString, QString>::const_iterator iter = queryItemMap.constBegin();
		while (iter != queryItemMap.constEnd()) {
			query.addQueryItem(iter.key(), iter.value());
			++iter;
		}
		GetUrl.setQuery(query);
	}

	requestInfo.setUrl(GetUrl);
	if (!headerMap.isEmpty())
	{
		QMap<QString, QString>::const_iterator iter = headerMap.constBegin();
		while (iter != headerMap.constEnd()) {
			requestInfo.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8());
			++iter;
		}
	}
    
	QNetworkAccessManager NetWorkManager;
	QTimer timer;
	timer.setInterval(3000);
	timer.setSingleShot(true);
	QEventLoop eventLoop;
	QNetworkReply *reply = NetWorkManager.get(requestInfo);
	connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
	eventLoop.exec();
	//错误处理
	QString replyStr;
	if (reply->error() == QNetworkReply::NoError)
	{
		replyStr = reply->readAll();
	}
	else
	{
		reply->deleteLater();
		qDebug() << reply->errorString();
	
	}
	return replyStr;
}

Http-Post

QString QHttpClient::Post(const QString&url, const QString&Content, const QMap<QString, QString>&headerMap)
{
	QUrl PostUrl(url);
	if (!PostUrl.isValid())
	{
		return "";
	}
	QNetworkRequest requestInfo(PostUrl);
	if (!headerMap.isEmpty())
	{
		QMap<QString, QString>::const_iterator iter = headerMap.constBegin();
		while (iter != headerMap.constEnd()) {
			requestInfo.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8());
			++iter;
		}
	}
	requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

	QNetworkAccessManager NetWorkManager;
	QTimer timer;
	timer.setInterval(3000);
	timer.setSingleShot(true);
	QEventLoop eventLoop;
	QNetworkReply *reply = NetWorkManager.post(requestInfo, Content.toUtf8());
	connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
	eventLoop.exec();

	QString replyStr;
	if (reply->error() == QNetworkReply::NoError)
	{
		replyStr = reply->readAll();
	}
	else
	{
		reply->deleteLater();

	}
	qDebug() << reply;

	return replyStr;

}

http-post:form-data

QFile file(picpath);
	if (!file.open(QIODevice::ReadOnly))
	{
		
		cLogger(LOG_MODULE_NAME)->error("file open {} failed", picpath);
		return;
	}
	QByteArray imageData = file.readAll();
	QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

	QHttpPart imagePart;
	imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
	imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\";filename=\"" + file.fileName() + "\""));
	imagePart.setBody(imageData);
	multiPart->append(imagePart);
	

	QUrl qurl(url);
	QNetworkRequest request(qurl);
	
	QNetworkAccessManager NetManager;
	QTimer timer;
	timer.setInterval(m_sendTimeOut);
	timer.setSingleShot(true);
	QEventLoop eventLoop;
	QNetworkReply *reply = NetManager.post(request, multiPart);
	multiPart->setParent(reply);
	connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
	eventLoop.exec();
	
	if (reply->error() == QNetworkReply::NoError)
	{
		strReply = reply->readAll();
		cLogger(LOG_MODULE_NAME)->info("reply: {}", strReply);
	}
	else
	{
		cLogger(LOG_MODULE_NAME)->error("Request connection failed! {}", reply->errorString());
	}

	reply->deleteLater();

ftp-up

bool QHttpClient::UploadFtp(const QString&host, const int port, const QString&username, const QString&password, const QByteArray&Content, const QString&uploadFilepath, bool bVertrify)
{
	QUrl ftpUrl;
	ftpUrl.setScheme("ftp");
	ftpUrl.setHost(host);
	ftpUrl.setPort(port);
	if (bVertrify)
	{
		ftpUrl.setUserName(username);//匿名则不需要
		ftpUrl.setPassword(password);//匿名则不需要
	}
	ftpUrl.setPath(uploadFilepath);

	QNetworkRequest requestInfo;
	requestInfo.setUrl(ftpUrl);

	QNetworkAccessManager ftpWorkManager;
	QTimer timer;
	timer.setInterval(3000);
	timer.setSingleShot(true);
	QEventLoop eventLoop;
	QNetworkReply *reply = ftpWorkManager.put(requestInfo, Content);
	connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
	eventLoop.exec();
	if (reply->error() == QNetworkReply::NoError)
	{
		return true;
	}
	else
	{
		reply->deleteLater();
		return false;
	}
}

ftp-down

bool QHttpClient::DownLoadFtp(const QString&host, const int port, const QString&username, const QString&password, QString&getContentStr, const QString&uploadFilepath, bool bVertrify)
{
	QUrl ftpUrl;
	ftpUrl.setScheme("ftp");
	ftpUrl.setHost(host);
	ftpUrl.setPort(port);
	if (bVertrify)
	{
		ftpUrl.setUserName(username);//匿名则不需要
		ftpUrl.setPassword(password);//匿名则不需要
	}
	ftpUrl.setPath(uploadFilepath);
	

	QNetworkRequest requestInfo;
	requestInfo.setUrl(ftpUrl);

	QNetworkAccessManager ftpWorkManager;
	QTimer timer;
	timer.setInterval(3000);
	timer.setSingleShot(true);
	QEventLoop eventLoop;
	QNetworkReply *reply = ftpWorkManager.get(requestInfo);
	connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
	eventLoop.exec();
	if (reply->error() == QNetworkReply::NoError)
	{
		getContentStr = reply->readAll();
		return true;
	}
	else
	{
		reply->deleteLater();
		return false;
	}

}

QDesktopServices

        主要用于与桌面环境交互,启用外部程序打开任意URL地址。Qt会根据传进来的url决定具体的操作。可以应用在软件打开本地的帮助文档、生成的excel数据表快捷打开等等,也可以打开在线的帮助引导网页等。

ConstantValueDescription
QUrl::TolerantMode0QUrl试图纠正一些常见的url错误,便于解析不完全符合标准的url。
QUrl::StrictMode1只接受有效的url,可以验证url的格式。

自定义openUrl行为

外部程序运行效果

;