<C/C++> Socket编程Http下载的简单实现
2013-01-08 00:08
435 查看
下载原理: 网上介绍很多,就是按照Http协议,使用Socket连接并发送请求头给Http服务器,若服务器正确响应,返回请求文件数据,接收并写文件保存.至于Http协议的请求头及响应头的格式,这里不再赘述,请Google之.
实现: 为此,我封装了一个HttpDownload类.先上代码...(基于WinSock,移植时请注意部分函数)
(HttpDownload.h)
(HttpDownload.cpp)
其中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编译),上图
View Code
[b]附件: 源码下载[/b]
实现: 为此,我封装了一个HttpDownload类.先上代码...(基于WinSock,移植时请注意部分函数)
(HttpDownload.h)
#ifndef HTTP_DOWNLOAD_H #define HTTP_DOWNLOAD_H #include <cstdio> #include <string> #include <winsock2.h> class HttpDownload { public: HttpDownload(const char* hostAddr, const int port, const char* getPath, const char* saveFileName); ~HttpDownload(); bool start(); void cancel(); void getPos(ULONGLONG& totalSize, ULONGLONG& downloadSize); protected: bool initSocket(); //初始化Socket bool sendRequest(); //发送请求头 bool receiveData(); //接收数据 bool closeTransfer(); //关闭传输 private: std::string m_hostAddr; //目标主机IP int m_port; //HTTP端口号 std::string m_getPath; //目标文件相对路径 std::string m_saveFileName; //保存文件路径 SOCKET m_sock; //Socket FILE* m_fp; //保存文件指针 ULONGLONG m_fileTotalSize; //目标文件总大小 ULONGLONG m_receivedDataSize; //已接收数据大小 bool m_cancelFlag; //取消下载标记 }; #endif //HTTP_DOWNLOAD_H
(HttpDownload.cpp)
#include "HttpDownload.h" #define BUFFER_SIZE 1024 HttpDownload::HttpDownload(const char* hostAddr, const int port, const char* getPath, const char* saveFileName) { m_hostAddr = hostAddr; m_port = port; m_getPath = getPath; m_saveFileName = saveFileName; m_sock = 0; m_fp = NULL; m_fileTotalSize = 1; //没给0,因为分母 m_receivedDataSize = 0; m_cancelFlag = false; } HttpDownload::~HttpDownload() { } bool HttpDownload::initSocket() { m_sock = socket(AF_INET, SOCK_STREAM, 0); if (m_sock < 0) return false; //设置Socket为非阻塞模式 unsigned long mode = 1; if (ioctlsocket(m_sock, FIONBIO, &mode) < 0) return false; if (m_hostAddr.empty()) return false; struct sockaddr_in destaddr; destaddr.sin_family = AF_INET; destaddr.sin_port = htons(m_port); destaddr.sin_addr.s_addr = inet_addr(m_hostAddr.c_str()); int nRet = connect(m_sock, (struct sockaddr*)&destaddr, sizeof(destaddr)); if (nRet == 0) //如果立即连接成功 return true; //虽直接返回,但未立即成功,用select等待看socket是否可写来判断connect是否成功 if (WSAGetLastError() != WSAEWOULDBLOCK) return false; int retryCount = 0; while(1) { fd_set writeSet, exceptSet; FD_ZERO(&writeSet); FD_SET(m_sock, &writeSet); exceptSet = writeSet; struct timeval timeout; timeout.tv_sec = 3; timeout.tv_usec = 0; int err = select((int)m_sock+1, NULL, &writeSet, &exceptSet, &timeout); if (err < 0) //出错 break; if (err == 0) //超时 { if (++retryCount > 10) //重试10次 return false; continue; } if (FD_ISSET(m_sock, &writeSet)) return true; if (FD_ISSET(m_sock, &exceptSet)) break; } return false; } bool HttpDownload::sendRequest() { if (m_getPath.empty()) return false; char requestHeader[256]; //格式化请求头 int len = sprintf(requestHeader, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Range: bytes=%I64d-\r\n" //从m_receivedDataSize位置开始 "Connection: close\r\n" "\r\n", m_getPath.c_str(), m_hostAddr.c_str(), m_receivedDataSize); int nSendBytes = 0; //已发送字节数 while(1) { fd_set writeSet; FD_ZERO(&writeSet); FD_SET(m_sock, &writeSet); struct timeval timeout; timeout.tv_sec = 3; timeout.tv_usec = 0; int err = select((int)m_sock+1, NULL, &writeSet, NULL, &timeout); if (err < 0) break; if (err == 0) continue; int nBytes = send(m_sock, requestHeader+nSendBytes, len, 0); if (nBytes < 0) { if (WSAGetLastError() != WSAEWOULDBLOCK) break; nBytes = 0; } nSendBytes += nBytes; //若一次未发完,累计,循环send len -= nBytes; if (len == 0) return true; } return false; } bool HttpDownload::receiveData() { char responseHeader[BUFFER_SIZE] = {0}; struct timeval timeout; timeout.tv_sec = 3; timeout.tv_usec = 0; //接收响应头 int retryCount = 0; int nRecvBytes = 0; //已接收字节数 while (1) { fd_set readSet; FD_ZERO(&readSet); FD_SET(m_sock, &readSet); int nRet = select(m_sock+1, &readSet, NULL, NULL, &timeout); if (nRet < 0) //出错 return false; if (nRet == 0) //超时 { if (++retryCount > 10) return false; continue; } retryCount = 0; if (recv(m_sock, responseHeader+nRecvBytes, 1, 0) <= 0) return false; nRecvBytes++; if (nRecvBytes >= BUFFER_SIZE) return false; if (nRecvBytes >= 4 && responseHeader[nRecvBytes-4]=='\r' && responseHeader[nRecvBytes-3]=='\n' && responseHeader[nRecvBytes-2]=='\r' && responseHeader[nRecvBytes-1]=='\n') break; } responseHeader[nRecvBytes] = '\0'; if (strncmp(responseHeader, "HTTP/", 5)) return false; int status = 0; float version = 0.0; ULONGLONG startPos, endPos, totalLength; startPos = endPos = totalLength = 0; if (sscanf(responseHeader, "HTTP/%f %d ", &version, &status) != 2) return false; char* findStr = strstr(responseHeader, "Content-Range: bytes "); if (findStr == NULL) return false; if (sscanf(findStr, "Content-Range: bytes %I64d-%I64d/%I64d", &startPos, &endPos, &totalLength) != 3) return false; if (status != 200 && status != 206 || totalLength == 0) return false; if (m_fileTotalSize == 1) //第一次获取HTTP响应头,保存目标文件总大小 m_fileTotalSize = totalLength; if (m_receivedDataSize != startPos) return false; //接收目标文件数据 retryCount = 0; while (1) { char buf[BUFFER_SIZE] = {0}; fd_set readSet; FD_ZERO(&readSet); FD_SET(m_sock, &readSet); int nRet = select((int)m_sock+1, &readSet, NULL, NULL, &timeout); if (nRet < 0) break; if (nRet == 0) { if (++retryCount > 10) break; continue; } int length = recv(m_sock, buf, BUFFER_SIZE, 0); if(length < 0) //出错 return false; if (length == 0) //socket被优雅关闭 return true; size_t written = fwrite(buf, sizeof(char), length, m_fp); if(written < length) return false; m_receivedDataSize += length; if (m_receivedDataSize == m_fileTotalSize) //文件接收完毕 { return true; } } return false; } bool HttpDownload::closeTransfer() { if (m_sock > 0) { if (closesocket(m_sock) < 0) return false; m_sock = 0; } else m_sock = 0; return true; } bool HttpDownload::start() { m_fp = fopen(m_saveFileName.c_str(), "wb"); //创建文件 if (m_fp == NULL) return false; bool errFlag = false; while(1) { if (!initSocket() || !sendRequest() || !receiveData()) { if (m_cancelFlag) { errFlag = true; break; } if (!closeTransfer()) { errFlag = true; break; } Sleep(1000); continue; } break; } if(m_fp != NULL) { fclose(m_fp); m_fp = NULL; } if (errFlag) return false; return true; } void HttpDownload::cancel() { m_cancelFlag = true; closeTransfer(); } void HttpDownload::getPos(ULONGLONG& totalSize, ULONGLONG& downloadSize) { totalSize = m_fileTotalSize; downloadSize = m_receivedDataSize; }
其中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编译),上图
View Code
#include <cstdio> #include "pthread.h" #include "HttpDownload.h" #include "InitWinSocket.h" InitWinSocket init; const char* g_progName = NULL; const char* g_saveFileName = "savefilename"; void usage() { printf("Usage: %s http://www.xxx.com/filename %s\n", g_progName, g_saveFileName); } void progressBar(float percent) { const int numTotal = 50; int numShow = (int)(numTotal * percent); if (numShow == 0) numShow = 1; if (numShow > numTotal) numShow = numTotal; char sign[numTotal+1] = {0}; memset(sign, '=', numTotal); printf("\r%.2f%%\t[%-*.*s]", percent*100, numTotal, numShow, sign); fflush(stdout); if (numShow == numTotal) printf("\n"); } void parseURL(const char* url, char* hostAddr, int& port, char* getPath) { if (url == NULL || hostAddr == NULL || getPath == NULL) return; const char* temp = strstr(url, "http://"); if (temp == NULL) return; const char* hostStart = temp + strlen("http://"); const char* colon = strchr(hostStart, ':'); if (colon != NULL) //表示存在冒号,有端口号 sscanf(hostStart, "%[^:]:%d%s", hostAddr, &port, getPath); else sscanf(hostStart, "%[^/]%s", hostAddr, getPath); //通过主机名转IP地址 struct hostent* hostEntry; hostEntry = gethostbyname(hostAddr); if (hostEntry == NULL) { printf("Hostname not available!\n"); return; } struct in_addr inAddr = {0}; memcpy(&inAddr.s_addr, hostEntry->h_addr, sizeof(inAddr.s_addr)); strcpy(hostAddr, inet_ntoa(inAddr)); } void* task(void* arg) { HttpDownload* pObj = (HttpDownload*)arg; if (pObj->start()) return ((void*)1); else return ((void*)0); } int main(int argc, char** argv) { g_progName = strrchr(argv[0], '\\'); if (g_progName != NULL) g_progName += 1; else g_progName = argv[0]; if (argc != 3 || strncmp(argv[1], "http://", strlen("http://")) != 0) { usage(); return -1; } g_saveFileName = argv[2]; char hostAddr[256] = {0}; int port = 80; char getPath[256] = {0}; parseURL(argv[1], hostAddr, port, getPath); HttpDownload obj(hostAddr, port, getPath, g_saveFileName); //创建下载类对象 pthread_t tid; int err = pthread_create(&tid, NULL, task, &obj); if (err != 0) printf("Start Download Failed!\n"); else printf("Start Downloading...\n"); ULONGLONG totalSize = 1; ULONGLONG downloadSize = 0; float percent = 0; while (1) { obj.getPos(totalSize, downloadSize); percent = downloadSize/(float)totalSize; progressBar(percent); if (downloadSize == totalSize) break; Sleep(500); } void* ret = NULL; pthread_join(tid, &ret); if (ret) printf("Download Finished.\n"); else printf("Download Failed!\n"); return 0; }
[b]附件: 源码下载[/b]
相关文章推荐
- Socket编程Http下载的简单实现
- http 与 c++ 网络编程(用于简单的网络下载)
- Java Socket编程 - 基于Socket实现HTTP下载客户端
- <网络编程培训之三> 实现TCP/UDP的简单Echo服务器
- Java Socket编程 - 基于Socket实现HTTP下载客户端
- Java Socket编程 - 基于Socket实现HTTP下载客户端
- socket编程实现HTTP下载 思路 ,未实现的 可以直接用 微软的 wininet 库 (转载)
- socket http编程(用socket编程实现http网页下载)
- <HTTP>ASI实现的注册方法:利用http的get和post两种方式
- STL 简单 <stl_numeric.h> 算法的实现
- socket编程中客户端常用函数 以及简单实现
- 网络编程----------SOCKET编程实现简单的TCP协议
- Socket 简单实现下载功能
- Cocos2d-x简单游戏<植物大战僵尸>代码实现|第二部分:菜单场景<后续会提供源码下载链接>
- <<决定一生的99个简单法则>>下载
- Python3 socket编程,并与多线程实现最简单的聊天工具之一
- C++模板编程->嵌套实现元组
- TCP Socket编程 C/C++实现 (Windows Platform SDK)
- <JAVA学习笔记9>——网络之Socket的简单介绍
- C++ 简单的 Tcp 实现[socket] 客户端与客户端通信