Socket编程Http下载的简单实现 - Mr.DejaVu - 博客园

<C/C++> Socket编程Http下载的简单实现

下载原理: 网上介绍很多,就是按照Http协议,使用Socket连接并发送请求头给Http服务器,若服务器正确响应,返回请求文件数据,接收并写文件保存.至于Http协议的请求头及响应头的格式,这里不再赘述,请Google之.

实现: 为此,我封装了一个HttpDownload类.先上代码...(基于WinSock,移植时请注意部分函数)

(HttpDownload.h)

 1 #ifndef HTTP_DOWNLOAD_H
 2 #define HTTP_DOWNLOAD_H
 3
 4 #include <cstdio>
 5 #include <string>
 6 #include <winsock2.h>
 7
 8 class HttpDownload {
 9 public:
10     HttpDownload(const char* hostAddr, const int port,
11                  const char* getPath, const char* saveFileName);
12     ~HttpDownload();
13     bool start();
14     void cancel();
15     void getPos(ULONGLONG& totalSize, ULONGLONG& downloadSize);
16 protected:
17     bool initSocket();                        //初始化Socket
18     bool sendRequest();                        //发送请求头
19     bool receiveData();                        //接收数据
20     bool closeTransfer();                    //关闭传输
21 private:
22     std::string m_hostAddr;                    //目标主机IP
23     int m_port;                                //HTTP端口号
24     std::string m_getPath;                    //目标文件相对路径
25     std::string m_saveFileName;                //保存文件路径
26     SOCKET m_sock;                            //Socket
27     FILE* m_fp;                                //保存文件指针
28     ULONGLONG m_fileTotalSize;                //目标文件总大小
29     ULONGLONG m_receivedDataSize;            //已接收数据大小
30     bool m_cancelFlag;                        //取消下载标记
31 };
32
33 #endif    //HTTP_DOWNLOAD_H

(HttpDownload.cpp)

  1 #include "HttpDownload.h"
  2
  3 #define BUFFER_SIZE 1024
  4
  5 HttpDownload::HttpDownload(const char* hostAddr, const int port, const char* getPath, const char* saveFileName)
  6 {
  7     m_hostAddr = hostAddr;
  8     m_port = port;
  9     m_getPath = getPath;
 10     m_saveFileName = saveFileName;
 11     m_sock = 0;
 12     m_fp = NULL;
 13     m_fileTotalSize = 1;    //没给0,因为分母
 14     m_receivedDataSize = 0;
 15     m_cancelFlag = false;
 16 }
 17
 18 HttpDownload::~HttpDownload()
 19 {
 20
 21 }
 22
 23 bool HttpDownload::initSocket()
 24 {
 25     m_sock = socket(AF_INET, SOCK_STREAM, 0);
 26     if (m_sock < 0)
 27         return false;
 28
 29     //设置Socket为非阻塞模式
 30     unsigned long mode = 1;
 31     if (ioctlsocket(m_sock, FIONBIO, &mode) < 0)
 32         return false;
 33
 34     if (m_hostAddr.empty())
 35         return false;
 36
 37     struct sockaddr_in destaddr;
 38     destaddr.sin_family = AF_INET;
 39     destaddr.sin_port = htons(m_port);
 40     destaddr.sin_addr.s_addr = inet_addr(m_hostAddr.c_str());
 41
 42     int nRet = connect(m_sock, (struct sockaddr*)&destaddr, sizeof(destaddr));
 43     if (nRet == 0)    //如果立即连接成功
 44         return true;
 45     //虽直接返回,但未立即成功,用select等待看socket是否可写来判断connect是否成功
 46     if (WSAGetLastError() != WSAEWOULDBLOCK)
 47         return false;
 48     int retryCount = 0;
 49     while(1)
 50     {
 51         fd_set writeSet, exceptSet;
 52         FD_ZERO(&writeSet);
 53         FD_SET(m_sock, &writeSet);
 54         exceptSet = writeSet;
 55
 56         struct timeval timeout;
 57         timeout.tv_sec = 3;
 58         timeout.tv_usec = 0;
 59
 60         int err = select((int)m_sock+1, NULL, &writeSet, &exceptSet, &timeout);
 61         if (err < 0)    //出错
 62             break;
 63         if (err == 0)    //超时
 64         {
 65             if (++retryCount > 10)    //重试10次
 66                 return false;
 67             continue;
 68         }
 69         if (FD_ISSET(m_sock, &writeSet))
 70             return true;
 71         if (FD_ISSET(m_sock, &exceptSet))
 72             break;
 73     }
 74     return false;
 75 }
 76
 77 bool HttpDownload::sendRequest()
 78 {
 79     if (m_getPath.empty())
 80         return false;
 81
 82     char requestHeader[256];
 83     //格式化请求头
 84     int len = sprintf(requestHeader,
 85         "GET %s HTTP/1.1\r\n"
 86         "Host: %s\r\n"
 87         "Range: bytes=%I64d-\r\n"    //从m_receivedDataSize位置开始
 88         "Connection: close\r\n"
 89         "\r\n",
 90         m_getPath.c_str(), m_hostAddr.c_str(), m_receivedDataSize);
 91
 92     int nSendBytes = 0;    //已发送字节数
 93     while(1)
 94     {
 95         fd_set writeSet;
 96         FD_ZERO(&writeSet);
 97         FD_SET(m_sock, &writeSet);
 98
 99         struct timeval timeout;
100         timeout.tv_sec = 3;
101         timeout.tv_usec = 0;
102
103         int err = select((int)m_sock+1, NULL, &writeSet, NULL, &timeout);
104         if (err < 0)
105             break;
106         if (err == 0)
107             continue;
108         int nBytes = send(m_sock, requestHeader+nSendBytes, len, 0);
109         if (nBytes < 0)
110         {
111             if (WSAGetLastError() != WSAEWOULDBLOCK)
112                 break;
113             nBytes = 0;
114         }
115         nSendBytes += nBytes;    //若一次未发完,累计,循环send
116         len -= nBytes;
117         if (len == 0)
118             return true;
119     }
120     return false;
121 }
122
123 bool HttpDownload::receiveData()
124 {
125     char responseHeader[BUFFER_SIZE] = {0};
126
127     struct timeval timeout;
128     timeout.tv_sec = 3;
129     timeout.tv_usec = 0;
130
131     //接收响应头
132     int retryCount = 0;
133     int nRecvBytes = 0;    //已接收字节数
134     while (1)
135     {
136         fd_set readSet;
137         FD_ZERO(&readSet);
138         FD_SET(m_sock, &readSet);
139         int nRet = select(m_sock+1, &readSet, NULL, NULL, &timeout);
140         if (nRet < 0)    //出错
141             return false;
142         if (nRet == 0)    //超时
143         {
144             if (++retryCount > 10)
145                 return false;
146             continue;
147         }
148         retryCount = 0;
149         if (recv(m_sock, responseHeader+nRecvBytes, 1, 0) <= 0)
150             return false;
151         nRecvBytes++;
152         if (nRecvBytes >= BUFFER_SIZE)
153             return false;
154         if (nRecvBytes >= 4 &&
155             responseHeader[nRecvBytes-4]=='\r' && responseHeader[nRecvBytes-3]=='\n' &&
156             responseHeader[nRecvBytes-2]=='\r' && responseHeader[nRecvBytes-1]=='\n')
157             break;
158     }
159     responseHeader[nRecvBytes] = '\0';
160
161     if (strncmp(responseHeader, "HTTP/", 5))
162         return false;
163     int status = 0;
164     float version = 0.0;
165     ULONGLONG startPos, endPos, totalLength;
166     startPos = endPos = totalLength = 0;
167     if (sscanf(responseHeader, "HTTP/%f %d ", &version, &status) != 2)
168         return false;
169     char* findStr = strstr(responseHeader, "Content-Range: bytes ");
170     if (findStr == NULL)
171         return false;
172     if (sscanf(findStr, "Content-Range: bytes %I64d-%I64d/%I64d",
173         &startPos, &endPos, &totalLength) != 3)
174         return false;
175     if (status != 200 && status != 206 || totalLength == 0)
176         return false;
177     if (m_fileTotalSize == 1)    //第一次获取HTTP响应头,保存目标文件总大小
178         m_fileTotalSize = totalLength;
179     if (m_receivedDataSize != startPos)
180         return false;
181
182     //接收目标文件数据
183     retryCount = 0;
184     while (1)
185     {
186         char buf[BUFFER_SIZE] = {0};
187         fd_set readSet;
188         FD_ZERO(&readSet);
189         FD_SET(m_sock, &readSet);
190
191         int nRet = select((int)m_sock+1, &readSet, NULL, NULL, &timeout);
192         if (nRet < 0)
193             break;
194         if (nRet == 0) {
195             if (++retryCount > 10)
196                 break;
197             continue;
198         }
199         int length = recv(m_sock, buf, BUFFER_SIZE, 0);
200         if(length < 0)    //出错
201             return false;
202         if (length == 0)    //socket被优雅关闭
203             return true;
204         size_t written = fwrite(buf, sizeof(char), length, m_fp);
205         if(written < length)
206             return false;
207         m_receivedDataSize += length;
208         if (m_receivedDataSize == m_fileTotalSize)    //文件接收完毕
209         {
210             return true;
211         }
212     }
213     return false;
214 }
215
216 bool HttpDownload::closeTransfer()
217 {
218     if (m_sock > 0) {
219         if (closesocket(m_sock) < 0)
220             return false;
221         m_sock = 0;
222     }
223     else
224         m_sock = 0;
225     return true;
226 }
227
228 bool HttpDownload::start()
229 {
230     m_fp = fopen(m_saveFileName.c_str(), "wb");    //创建文件
231     if (m_fp == NULL)
232         return false;
233     bool errFlag = false;
234     while(1)
235     {
236         if (!initSocket() || !sendRequest() || !receiveData())
237         {
238             if (m_cancelFlag)
239             {
240                 errFlag = true;
241                 break;
242             }
243             if (!closeTransfer())
244             {
245                 errFlag = true;
246                 break;
247             }
248             Sleep(1000);
249             continue;
250         }
251         break;
252     }
253     if(m_fp != NULL)
254     {
255         fclose(m_fp);
256         m_fp = NULL;
257     }
258     if (errFlag)
259         return false;
260     return true;
261 }
262
263 void HttpDownload::cancel()
264 {
265     m_cancelFlag = true;
266     closeTransfer();
267 }
268
269 void HttpDownload::getPos(ULONGLONG& totalSize, ULONGLONG& downloadSize)
270 {
271     totalSize = m_fileTotalSize;
272     downloadSize = m_receivedDataSize;
273 }

其中4个主要函数功能如下:

1) initSocket() : 初始化Socket->设置Socket为非阻塞模式->connect()

值得注意的是,这里我采用了非阻塞的select模型.相对来说非阻塞模式可以减少开销,增加错误控制能力,显得更灵活.因此,Line 42-73

connect之,因为非阻塞,立即返回,得到返回值判断,通常不会立即成功,然后

while(1) {

socket加入写描述符集

用select检测写写描述符集,延时3秒,可写就返回true

否则,重复10次(总共30秒),如果仍不成功,认为connect失败,返回false

}

2) sendRequest() : 格式化Http请求字符串->用send发送之.send依然使用非阻塞select模型判断,同上.

注意的是:这里Http请求字符串里的"Range: bytes="字段用m_receivedDataSize来格式化,此成员变量用于保存请求的目标文件开始位置.目的是为实现断点续传,若传输文件过程中网络异常后,可重新发送请求头,则只需从已下载位置之后继续传输.

3) receiveData() : recv接收Http响应头字符串->取出响应头中的信息(如文件大小)->若响应信息正确,开始recv接收目标文件数据->写文件

recv依然使用非阻塞select模型判断,同上.

4) closeTransfer() : 关闭套接字.(这里因在windows下,所以使用closesocket)

因为代码中多有注释,细节就不再多解释了.

另外,给出3个接口函数:

1) start() : 创建文件->分别依次调用initSocket(),sendRequest(),receiveData()->关闭文件,套接字

在一个循环,不断判断initSocket(),sendRequest(),receiveData()是否成功,若任一失败(网络异常),调用closeTransfer(),然后重新来,直到下载完毕或被cancel()中断

2) cancel() : 关闭套接字,并将m_cancelFlag置true

3) getPos() : 用于得到当前文件下载的进度

最后,附上源码,包含一个实现下载的控制台程序例子(MinGW编译),上图

(Main.cpp)

View Code

  1 #include <cstdio>
  2 #include "pthread.h"
  3 #include "HttpDownload.h"
  4 #include "InitWinSocket.h"
  5
  6 InitWinSocket init;
  7 const char* g_progName = NULL;
  8 const char* g_saveFileName = "savefilename";
  9
 10 void usage() {
 11     printf("Usage: %s http://www.xxx.com/filename %s\n", g_progName, g_saveFileName);
 12 }
 13
 14 void progressBar(float percent) {
 15     const int numTotal = 50;
 16     int numShow = (int)(numTotal * percent);
 17     if (numShow == 0)
 18         numShow = 1;
 19     if (numShow > numTotal)
 20         numShow = numTotal;
 21     char sign[numTotal+1] = {0};
 22     memset(sign, '=', numTotal);
 23     printf("\r%.2f%%\t[%-*.*s]", percent*100, numTotal, numShow, sign);
 24     fflush(stdout);
 25     if (numShow == numTotal)
 26         printf("\n");
 27 }
 28
 29 void parseURL(const char* url, char* hostAddr, int& port, char* getPath) {
 30     if (url == NULL || hostAddr == NULL || getPath == NULL)
 31         return;
 32     const char* temp = strstr(url, "http://");
 33     if (temp == NULL)
 34         return;
 35     const char* hostStart = temp + strlen("http://");
 36     const char* colon = strchr(hostStart, ':');
 37     if (colon != NULL)    //表示存在冒号,有端口号
 38         sscanf(hostStart, "%[^:]:%d%s", hostAddr, &port, getPath);
 39     else
 40         sscanf(hostStart, "%[^/]%s", hostAddr, getPath);
 41     //通过主机名转IP地址
 42     struct hostent* hostEntry;
 43     hostEntry = gethostbyname(hostAddr);
 44     if (hostEntry == NULL)
 45     {
 46         printf("Hostname not available!\n");
 47         return;
 48     }
 49     struct in_addr inAddr = {0};
 50     memcpy(&inAddr.s_addr, hostEntry->h_addr, sizeof(inAddr.s_addr));
 51     strcpy(hostAddr, inet_ntoa(inAddr));
 52 }
 53
 54 void* task(void* arg) {
 55     HttpDownload* pObj = (HttpDownload*)arg;
 56     if (pObj->start())
 57         return ((void*)1);
 58     else
 59         return ((void*)0);
 60 }
 61
 62 int main(int argc, char** argv) {
 63     g_progName = strrchr(argv[0], '\\');
 64     if (g_progName != NULL)
 65         g_progName += 1;
 66     else
 67         g_progName = argv[0];
 68     if (argc != 3 || strncmp(argv[1], "http://", strlen("http://")) != 0) {
 69         usage();
 70         return -1;
 71     }
 72     g_saveFileName = argv[2];
 73
 74     char hostAddr[256] = {0};
 75     int port = 80;
 76     char getPath[256] = {0};
 77     parseURL(argv[1], hostAddr, port, getPath);
 78
 79     HttpDownload obj(hostAddr, port, getPath, g_saveFileName);    //创建下载类对象
 80     pthread_t tid;
 81     int err = pthread_create(&tid, NULL, task, &obj);
 82     if (err != 0)
 83         printf("Start Download Failed!\n");
 84     else
 85         printf("Start Downloading...\n");
 86
 87     ULONGLONG totalSize = 1;
 88     ULONGLONG downloadSize = 0;
 89     float percent = 0;
 90     while (1) {
 91         obj.getPos(totalSize, downloadSize);
 92         percent = downloadSize/(float)totalSize;
 93         progressBar(percent);
 94         if (downloadSize == totalSize)
 95             break;
 96         Sleep(500);
 97     }
 98
 99     void* ret = NULL;
100     pthread_join(tid, &ret);
101     if (ret)
102         printf("Download Finished.\n");
103     else
104         printf("Download Failed!\n");
105     return 0;
106 }

附件: 源码下载

Socket编程Http下载的简单实现相关推荐

  1. C/C++ Socket编程Http下载的简单实现

    下载原理: 网上介绍很多,就是按照Http协议,使用Socket连接并发送请求头给Http服务器,若服务器正确响应,返回请求文件数据,接收并写文件保存.至于Http协议的请求头及响应头的格式,这里不再 ...

  2. 【Java高级】初探socket编程 ——JavaSocket连接与简单通信

    新学期生活开始一段时间了,要继续学习一些新的技术(这里指socket /doge),目标是尝试完成一个在线即时聊天的小程序(尽量不咕).会更新一系列socket编程的技术文章,欢迎关注交流~ 那么千里 ...

  3. Windows Socket编程笔记之最简单的小Demo

    Windows Socket编程的大致过程: 服务器端: ----过程-------------对应的API-------  0.初始化         |  WSAStartup()  1.创建So ...

  4. Java基于socket编程实现局域网内简单通信

    运行客户端程序将创建一个客户端套接字,并与指定的服务器建立连接,接收了服务端发来的消息后关闭连接.服务端启动后会循环接收客户端连接,在接收到连接后,向该客户端发送 "Hello World! ...

  5. tcp java实例_实现了基于TCP的Java Socket编程实例代码

    实现了基于TCP的Java Socket编程,功能很简单:客户端向服务器端输出一名话"connect",服务器端接收输出到控制台并向客户端输出一名话"Hello" ...

  6. Socket编程、协议理解

    Socket编程.协议理解 简单说明 Socket编程 Socket 常用接口 Socket服务端业务编码 代码说明 文件服务(fileServe) 消息服务(msgServe) 消息写会(write ...

  7. Linux Socket编程入门——浅显易懂

    文章目录 1. 概述 2. Socket 3. 网络字节序 4. sockaddr 数据结构 5. 网络套接字API函数  5.1 socket()  5.2 bind()  5.3 listen() ...

  8. 入门级C# Socket编程实现

    说明:本篇推文侧重讲解C#的Socket编程实现,里面有完整实现的GIF动图,大家可以先去看一下,Socket原理介绍的不多,可能有很多不足的地方,原理方面大家可以去找其他资料看一下. Socket编 ...

  9. socket编程简单Demo讲解及源码分享(C# Winform 内网)

    首先是什么是socket编程? 推荐大家看百度百科的解释:https://baike.baidu.com/item/socket/281150?fr=aladdin 百科对于定义讲的很详细,就不再叙述 ...

最新文章

  1. java.lang.ClasNotFoundException:Didnt findclass on path:DexPathList[[zip file
  2. My first Blog
  3. C#中方法的参数四种类型(值参数、ref、out、params)详解
  4. java集合:LinkedList
  5. 利用zabbix web scenario 监控Web站点的可用性
  6. Java:GB18030字节数组与UTF8互转
  7. 2020年领导最满意的可视化工具!分分钟做好数据报表,吊打python
  8. 测量地图,给shp文件赋参考坐标系,并给mxd文档中的数据框架设置投影类型
  9. ssm怎么自动排列序号6位数_小学数学1~6年级知识薄弱点应对方法汇总,建议收藏!...
  10. linux怎么重载mysql配置命令_【Linux命令】数据库mysql配置命令
  11. 合肥青少年信息学计算机竞赛试题,合肥市第三十一届青少年信息学(计算机)竞赛 小学组试题...
  12. 转载:EM算法的最精辟讲解
  13. 使用最小二乘法计算机器学习算法之线性回归(计算过程与python实现)
  14. 232接口针脚定义_工业RS232接口总线原理与应用方案
  15. 危害移动数据安全的风险有哪些?
  16. 罗永浩以为×××短信给了马化腾一记暴击,实际……
  17. 制作网站及论坛的过程
  18. axure sketch 对比_Sketch to Axure RP插件下载
  19. 手机定向root,指定APP获取root权限
  20. Java实现 蓝桥杯 算法训练 相邻数对(暴力)

热门文章

  1. matlab中a k,Python:相当于Matlab的大型数组的svds(A,k)?
  2. c 与mysql连接_c与mysql连接和一个简单查询的例子
  3. oracle job 及存储过程案例
  4. 1177:奇数单增序列
  5. 20.Azure备份Azure上的虚拟机(中)
  6. 几何画板要怎样度量直线方程
  7. 抓包工具 tcpdump tshark
  8. Linux 网桥设置固定MAC
  9. jsp当参数为空的时候默认显示值
  10. 使用 imitator 实现前后端分离开发中的数据模拟与静态资源映射