用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;
}

效果(查看下载文件是否能正常打开)

用socket实现http下载文件

总结

  1. 为了练手解析url地址,都是用了原始迭代判断实现的.

  2. 了解http协议还是很有必要的. 

秋风 2017-04-24