用socket实现http请求下载文件
起因
这两天需要给一个很早的java web项目加一个activex控件,实现批量下载图片的功能.其实原先项目是有这个功能的,只是前一段时间更换服务器之后,那个控件无法使用了,现在负责那个项目的人不会.所以才让我帮忙写的.几年前写过activex控件,直接拿来修改一下就可以使用了,当时就想c语言怎么使用这个功能呢?
http协议是基于tcp实现的.知道了,这个剩下就好办了.
代码(实现的比较匆忙)
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define HEAD "GET %s HTTP/1.1%s\
Host: %s%s%s"
#define FREE(p) \
if(p) \
{ \
free(p); \
}
#define BUF_SIZE 1024
//操作系统对换行符实现是不同的
#ifdef WIN32 //Windows系统
#define LF "\r\n"
#elif __APPLE //mac系统
#define LF "\r"
#else //Linux和unix
#define LF "\n"
#endif
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("argc < 2");
abort();
}
//1.根据url解析
char *url = argv[1];
int urllen = strlen(url);
int hoststart = 0;
int hostend = 0;
int filestart = 0;
//1.1计算host 开始的位置()
for (int i = 0; i < urllen; i++)
{
if (url[i] == '/'&& url[i + 1] == '/')
{
hoststart = i + 2;
break;
}
}
//1.2计算host 结束的位置
for (int i = hoststart; i < urllen; i++)
{
if (url[i] == '/')
{
hostend = i;
break;
}
}
//1.3计算请求的文件名称
for (int i = urllen; i > 0; i--)
{
if (url[i] == '/')
{
filestart = i + 1;
break;
}
}
//1.4 根据前面获取的位置,重新分配内存,创建宿主/文件路径(包含文件名)/文件名
int host_name_size = hostend - hoststart + 1;
int file_path_size = urllen - hostend + 1;
int file_name_size = urllen - filestart + 1;
char *hostname = malloc(host_name_size);
char *file_path = malloc(file_path_size);
char *file_name = malloc(file_name_size);
memset(hostname, 0, host_name_size);
memset(file_path, 0, file_path_size);
memset(file_name, 0, file_name_size);
memcpy(hostname, url + hoststart, host_name_size - 1);
memcpy(file_path, url + hostend, file_path_size - 1);
memcpy(file_name, url + filestart, file_name_size - 1);
//1.5 根据宿主,拆分ip和端口
int portstart = 0;
for (int i = host_name_size - 1; i > 0; i--)
{
if (hostname[i] == ':')
{
portstart = i;
break;
}
}
char *ip = malloc(portstart * sizeof(char) + 1);
memset(ip, 0, portstart * sizeof(char) + 1);
memcpy(ip, hostname, portstart);
char *portStr = malloc(host_name_size - portstart);
memset(portStr, 0, host_name_size - portstart - 1);
memcpy(portStr, hostname + portstart + 1, host_name_size - portstart - 1);
FREE(portStr);
u_short port = (u_short)atoi(portStr);
WSADATA wsadata;
WORD socket_verson = MAKEWORD(2, 2);
WSAStartup(socket_verson, &wsadata);
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr(ip);
server_addr.sin_port = htons(port);
if (connect(client_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) //连接
{
fprintf(stderr, "socket connection failed!\n");
abort();
}
char headBuf[1024];
memset(headBuf, 0, sizeof(headBuf));
sprintf(headBuf, HEAD, file_path, LF, hostname, LF, LF); //对请求头进行拼接
int contentLen = strlen(headBuf) + 1;
if (send(client_socket, headBuf, contentLen, 0) == -1) //发送请求信息
{
fprintf(stderr, "send failed!\n");
abort();
}
char buffer[BUF_SIZE];
memset(buffer, 0, BUF_SIZE);
strcpy(buffer, file_name);
FILE *pf = fopen(file_name, "wb");
if (pf == NULL)
{
fprintf(stderr, "open file failed!\n");
abort();
}
else
{
memset(buffer, 0, BUF_SIZE);
int flag = 0;
int endpostion = -1;
int result = 0;
do
{ //开始接收文件
result = recv(client_socket, buffer, BUF_SIZE, 0);
if (result > 0)
{
if (flag == 0)
{
//主要剔除http响应头,将http打印出来,并把剩下响应信息写入到文件中
char prechar = '0';
for (int i = 0; i < BUF_SIZE; i++)
{
printf("%c", buffer[i]);
if (prechar == '\r'&& buffer[i] == '\n' && buffer[i + 1] == '\r'&& buffer[i + 2] == '\n')
{
endpostion = i;
break;
}
prechar = buffer[i];
}
flag += 1;
endpostion += 4; //追加两个\r\n 4个字符,之后就是响应的主体报文
endpostion = endpostion - 1; //buffer是数组,下标从零开始
fwrite(buffer + endpostion, sizeof(char), BUF_SIZE - endpostion, pf);
continue;
}
fwrite(buffer, sizeof(char), result, pf);
memset(buffer, 0, BUF_SIZE);
}
} while (result > 0);
}
fclose(pf);
closesocket(client_socket);
WSACleanup();
FREE(hostname);
FREE(file_path);
FREE(file_name);
printf("ok");
return 0;
}
效果(查看下载文件是否能正常打开)

总结
1. 为了练手解析url地址,都是用了原始迭代判断实现的.
2. 了解http协议还是很有必要的.
秋风
2017-04-24