通常我们传输数据会有以下几种情况。
1.网络环境良好,A->B从开始到结束连接不断开
2.网络环境良好,A->B从开始到结束,连接可能因为某一方掉线而断开,收到不完整的数据。
3.网络环境很差,A->B传输过程中,因为丢包,TCP会重传,而当重传次数达到系统次数,连接被RESET。这样我们的链接就会断开,收到的数据就不完整。
我们对比网络情况二,网络情况三的情况:
情况二因为网络良好,只要某一方 重新上线,且不丢包,且不传输大文件,那么我们只要保证双方不再掉线,即可传输完文件。
情况三就很特殊,这也是笔者遇到的实战问题。在3这样的环境中,传输一个50KB的小文件,都反复失败。失败的原因则是因为丢包率很高,TCP又是分包发送数据,我们发送的50KB会被分为很多很多个小的包,只要某一个小的包重传超过五次,那么该链接就会被断开,接受到的数据就不完整。
接下来也正是本文的重点,介绍一种无论在网络环境差和好都适用的传输方案。
网络协议模型介绍
由于网络通信是个复杂的系统,所以网络层次分为不同层次来开发,不同层次负责不同功能。而通常我们讨论的为七层协议和五层协议只是不同归纳方法而已。
OSI模型与TCP/IP模型
OSI(Open System Interconnect)开放系统互联参考模型,是ISO(国际标准组织)颁布的一个开放式体系结构 ,把网络分为七层。
TCP/IP早期的TCP/IP模型是四层结构,后来借鉴OSI的七层参考模型,形成了一个新的五层结构。
实战会遇到的问题
通常我们在实战中会遇到如下一种情况:
TCP重传五次会被RESET
大家也许都知道TCP是个可靠的传输,但是却会在实战中遇到这种情况,连接突然被断开。
解决方案设计
鉴于如上问题,我们就需要在协议进行如下设计:
1.分包
2.重传
3.断开重连,重连
HTTP下的重传设计
HTTP是应用层协议,已经通过http头的content-length解决了TCP会出现的粘包问题,之后我们通过http协议已支持的range头来完成我们的分包传输。通过使用Range: bytes=xxx-xxx完成分片的工作,告诉服务器需要传输多少数据。
实验开始
1.首先从官网https://curl.se/libcurl/ 下载libcurl库后解压缩,笔者是VS2010所以进入curl-7.75.0\projects\Windows\VC10\进行编译。
2.打开项目文件curl-all.sln,修改运行时库为Multi-threaded (/MT)。
3.编译选项选择LibRelease后进行编译。
1.新建个项目工程,将生成好的lib文件导入到项目中。
2.给工程添加依赖的库:项目->属性->链接器->输入->附加依赖项,把libcurl.lib ws2_32.lib winmm.lib wldap32.lib添加进去。
3.加入预编译选项:项目->属性->c/c++ ->预处理器->预处理器,把 ;BUILDING_LIBCURL;HTTP_ONLY复制进去(注意不要丢了”;”)。
将下述实现代码添加进入工程
#include <stdio.h>
#include<iostream>
#include<string>
#define CURL_STATICLIB //静态链接
#include "curl/curl.h"
#pragma comment(lib, "libcurl.lib")
using namespace std;
typedef unsigned int uint;
typedef PVOID pvoid;
size_t writeData(void *buffer, size_t size, size_t nmemb, void *lpVoid)
{
string *str = dynamic_cast<string *>((string *)lpVoid);
if (NULL == str || NULL == buffer)
{
return -1;
}
char *pData = (char*)buffer;
str->append(pData,size *nmemb);
return nmemb;
}
long f_get(char * strUrl,string & strRespose)
{
CURLcode res;
CURL * curl = curl_easy_init();
if (NULL == curl)
{
return false;
}
curl_easy_setopt(curl,CURLOPT_URL,strUrl);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writeData);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void*)&strRespose);
curl_easy_setopt(curl,CURLOPT_NOSIGNAL,1);
curl_easy_setopt(curl,CURLOPT_CONNECTTIMEOUT,10); //TCP连接超时
curl_easy_setopt(curl,CURLOPT_TIMEOUT,30); //接收数据时超时设置
res = curl_easy_perform(curl);
if (CURLE_OK != res)
{
}
long response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
double size = 0;
if(response_code == 200){
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
}
return size;
}
bool f_get2(char *strUrl,int beginIndex,int maxSize,string & strResponse)
{
CURLcode res;
CURL * curl = curl_easy_init();
if (NULL == curl)
{
return false;
}
curl_easy_setopt(curl,CURLOPT_URL,strUrl);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writeData);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void*)&strResponse);
curl_easy_setopt(curl,CURLOPT_NOSIGNAL,1);
curl_easy_setopt(curl,CURLOPT_CONNECTTIMEOUT,10);
curl_easy_setopt(curl,CURLOPT_TIMEOUT,30);
int endIndex = beginIndex + 10000;
if(endIndex > maxSize){
endIndex = maxSize;
}
char beginIndexStr[100];
itoa(beginIndex,beginIndexStr,10);
char endIndexStr[100];
itoa(endIndex,endIndexStr,10);
string beginIndexString = string(beginIndexStr);
string endIndexString = string(endIndexStr);
string rangeHead = "";
rangeHead += beginIndexString;
rangeHead += "-";
rangeHead += endIndexString;
curl_easy_setopt(curl, CURLOPT_RANGE, rangeHead.c_str());
res = curl_easy_perform(curl);
if (CURLE_OK != res)
{
}
long response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
double size = 0;
if(response_code == 200){
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
}
return true;
}
BOOL Write(wstring filePath,char *buffer, DWORD contentLen)
{
HANDLE pFile;
char *tmpBuf;
DWORD dwBytesWrite,dwBytesToWrite;
pFile = CreateFile(filePath.c_str(),GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if ( pFile == INVALID_HANDLE_VALUE)
{
printf("create file error!\n");
CloseHandle(pFile);
return FALSE;
}
dwBytesToWrite = contentLen;
dwBytesWrite = 0;
tmpBuf = buffer;
do{
int ret = WriteFile(pFile,tmpBuf,dwBytesToWrite,&dwBytesWrite,NULL);
dwBytesToWrite -= dwBytesWrite;
tmpBuf += dwBytesWrite;
} while (dwBytesToWrite > 0);
CloseHandle(pFile);
return TRUE;
}
int main()
{
char * url = "http://192.168.206.144/Messagebox.exe";
string payload;
string response;
long fileSize = f_get(url,response);
response.clear();
long payloadSize = payload.size();
while(payloadSize < fileSize){
//get data using http head range
response.clear();
f_get2(url,payloadSize,fileSize,response);
payload.append(response);
payloadSize = payload.size();
}
wstring path = L"C:\\Users\\Public\\1.exe";
Write(path,(char *)payload.c_str(),payload.size());
system("pause");
}
1.首先通过第一次get获取目标文件的大小,由于设置了curl_easy_setopt(curl,CURLOPT_TIMEOUT,30); //接收数据时超时设置
当在指定时间内无法接受到整块数据,会返回部分接受到的数据(包括能获取到大小的http头),这也是一次直连HTTP在网络状况差的情况下会导致的问题。
2.当得知了整个文件大小,通过range头来分块获取