www.pudn.com > MyHttpClient.rar > Client.cpp


// 
//  TCP/IP Demo: 一个 HTTP 客户端的请求服务的简单实现. 
// 
// 
//  Client.cpp 
// 
//  实现向某个 HTTP 服务器发出请求,并将返回内容写入文件。基于 TCP 连接。 
// 
// 
//   
 
#include  
#include  
#include  
#include  
#include  
#include  
 
using namespace std; 
 
 
//--------------------------------------------------------------------------- 
//                            预编译指令 
//--------------------------------------------------------------------------- 
 
//  WinSock 程序需要的静态链接库 
#pragma comment(lib, "ws2_32.lib") 
 
#define PORT_HTTP_DEFAULT		80			//  缺省的 HTTP 服务端口 
#define ERR_WRONG_CMD_SYNTAX			-1	//  错误代码:命令行参数不正确 
#define ERR_NOT_START_UP				-2	//  错误代码:WSAStartup() 
#define ERR_WINSOCK_VER_NOT_SUPPORTED	-3		//  错误代码:WinSock 版本不支持 
 
 
//--------------------------------------------------------------------------- 
//                             函数声明 
//--------------------------------------------------------------------------- 
void GetHttpContent(string strURL, string strSaveFileName); 
bool ParseURL(string strURL, string& strServerName, u_short& uPortNumber, string& strFileName); 
 
 
//--------------------------------------------------------------------------- 
//                             主程序 
//--------------------------------------------------------------------------- 
int main(int argc, char **argv) 
{ 
	WORD wVersionRequested = MAKEWORD(1,1); 
	WSADATA wsaData; 
	int nRet; 
 
	string   strURL; 
	string   strSaveFileName; 
 
	// 检查输入的命令行参数 
	if (argc == 1)		//  如果不带任何参数启动程序,则缺省补充网址和保存文件名  
	{ 
//		strURL = "http://www.gdufs.edu.cn/index.htm"; 
		strURL  = "http://jsjx.gdufs.edu.cn/uploadfile/20061118140101.jpg"; 
		strSaveFileName = "pic.jpg"; 
	} 
	else if (argc == 3) 
	{ 
		strURL.assign(argv[1]);				//  传入的第二个参数为需要访问的网址 
		strSaveFileName.assign(argv[2]);	//  传入的第三个参数为保存内容的文件名 
	} 
	else 
	{ 
		fprintf(stderr, "\n启动命令: MyHttpClient  \n"); 
		return ERR_WRONG_CMD_SYNTAX; 
	} 
 
	printf("\n==============================================================================="); 
	printf("\n                               MyHttpClient.exe                                "); 
	printf("\n==============================================================================="); 
 
	// 初始化 WinSock 
	nRet = WSAStartup(wVersionRequested, &wsaData); 
	if (nRet) 
	{ 
		fprintf(stderr, "\nWSAStartup(): %d\n", nRet); 
		return ERR_NOT_START_UP; 
	} 
	 
	// 检查 WinSock 的版本 
	if (wsaData.wVersion != wVersionRequested) 
	{ 
		fprintf(stderr, "\nWinSock version not supported\n"); 
		WSACleanup(); 
		return ERR_WINSOCK_VER_NOT_SUPPORTED; 
	} 
 
	// 调用 GetHttpContent() 完成请求任务。 
	GetHttpContent(strURL, strSaveFileName); 
 
	// 清理并退出 WinSock 
	WSACleanup(); 
	 
	printf("\n\n程序执行完毕。\n\n"); 
	 
	return 0; 
} 
 
 
 
//--------------------------------------------------------------------------- 
//                            功能实现 
//--------------------------------------------------------------------------- 
 
void GetHttpContent(string strURL, string strSaveFileName) 
{ 
	string   strServerName; 
	string   strFileName; 
	u_short  uPortNumber; 
 
	//  解析 URL 格式:http://主机名:端口/文件路径 
	if (! ParseURL(strURL, strServerName, uPortNumber, strFileName)) 
	{ 
		fprintf(stderr, "\nError: ParseURL(): 输入的 URL 不正确。URL 格式:http://主机名:端口/文件路径"); 
		return; 
	} 
 
	//  通过 inet_addr() 判断 strServerName 是 IP 地址,还是一个主机名 
	IN_ADDR		iaHost; 
	LPHOSTENT	lpHostEntry; 
 
	iaHost.s_addr = inet_addr(strServerName.c_str()); 
	if (iaHost.s_addr == INADDR_NONE) 
	{ 
		//  如果不是 IP 地址, 则假设它是一个主机名 
		lpHostEntry = gethostbyname(strServerName.c_str()); 
	} 
	else 
	{ 
		//  由 IP 地址获得主机 
		lpHostEntry = gethostbyaddr((const char *)&iaHost,  
						sizeof(struct in_addr), AF_INET); 
	} 
	if (lpHostEntry == NULL) 
	{ 
		fprintf(stderr, "\nWinSock error: %s : %d\n", "gethostbyname()", WSAGetLastError()); 
		return; 
	} 
 
	// 创建 TCP/IP socket 
	SOCKET	Socket;	 
 
	Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
	if (Socket == INVALID_SOCKET) 
	{ 
		fprintf(stderr, "\nWinSock error: %s : %d\n", "socket()", WSAGetLastError());		 
		return; 
	} 
 
	// 查找基于 TCP 连接的 HTTP 服务的端口号 
	SOCKADDR_IN saServer; 
/* 
	LPSERVENT lpServEnt; 
	lpServEnt = getservbyname("http", "tcp"); 
	if (lpServEnt == NULL) 
		saServer.sin_port = htons(uPortNumber); 
	else 
		saServer.sin_port = lpServEnt->s_port; 
//*/ 
	saServer.sin_port = htons(uPortNumber); 
 
	//  其它地址信息 
	saServer.sin_family = AF_INET; 
	saServer.sin_addr = *((LPIN_ADDR)*lpHostEntry->h_addr_list); 
 
 
	//  开始建立连接 
	int nRet; 
 
	nRet = connect(Socket, (LPSOCKADDR)&saServer, sizeof(SOCKADDR_IN)); 
	if (nRet == SOCKET_ERROR) 
	{ 
		fprintf(stderr, "\nWinSock error: %s : %d\n", "connect()", WSAGetLastError());		 
		closesocket(Socket); 
		return; 
	} 
 
	printf("\n\n已经建立 Socket 连接。"); 
 
	// 构造 HTTP 协议的请求报文头	 
	string strSendBuffer; 
	strSendBuffer.reserve(1024); 
	char szTmpBuf[1024]; 
	sprintf(szTmpBuf, "GET %s\n", strFileName.c_str()); 
//	sprintf(szTmpBuf, "GET %s HTTP/1.0\n", strFileName.c_str()); 
	strSendBuffer.append(szTmpBuf); 
//	strSendBuffer.append("\nAccept: image/gif, image/x-xbitmap, image/jpeg, application/x-shockwave-flash, */*"); 
//	strSendBuffer.append("\nAccept-Language: zh-cn"); 
//	strSendBuffer.append("\nHost: "); 
//	strSendBuffer.append(strServerName); 
//	strSendBuffer.append("\nReferer: "); 
//	strSendBuffer.append(strFileName); 
//	strSendBuffer.append("\nUser-Agent: MyHttpClient.exe"); 
 
	nRet = send(Socket, strSendBuffer.c_str(), strSendBuffer.length(), 0); 
	if (nRet == SOCKET_ERROR) 
	{ 
		fprintf(stderr, "\nWinSock error: %s : %d\n", "send()", WSAGetLastError());		 
		closesocket(Socket);	 
		return; 
	} 
 
	printf("\n\n已经发送 HTTP 请求报文:\n%s \n", strSendBuffer.c_str()); 
 
	//  打开保存内容的文件 
	ofstream saveFile; 
	saveFile.open(strSaveFileName.c_str(), ios::out | ios::trunc | ios::binary);	 
 
	if (! saveFile) 
	{ 
		fprintf(stderr, "\nError: 创建输出文件失败。"); 
		return; 
	} 
 
	//  接收服务器返回的应答报文内容,并将其保存到文件中。 
	printf("\n\n正在接收 HTTP 应答报文:"); 
	char szRecvBuf[1024]; 
	bool isProcessingHead = true; 
	for (;;) 
	{ 
		//  阻塞式接收, nRet = NumberOfBytesReceived 
		nRet = recv(Socket, szRecvBuf, sizeof(szRecvBuf), 0); 
		if (nRet == SOCKET_ERROR) 
		{ 
			fprintf(stderr, "\nWinSock error: %s : %d\n", "recv()", WSAGetLastError());			 
			break; 
		} 
 
		fprintf(stderr,"\nrecv() returned %d bytes", nRet); 
 
		//  是否已经断开连接? 
		if (nRet == 0) 
			break; 
 
		//  处理 HTTP 响应报文头,注意此处只是非常简单的处理 
		if (isProcessingHead) 
		{ 
			if (strncmp(szRecvBuf, "HTTP", 4) == 0) 
			{ 
				int iCount = 0; 
				char * p = szRecvBuf; 
				for (; ; p++, iCount++) 
				{ 
					if ((*p != '\0') && (iCount>sizeof(szRecvBuf))) 
					{ 
						//szRecvBuf[0] = '\0'; 
						//nRet = 0; 
						break; 
					} 
					else if ((iCount<(sizeof(szRecvBuf)-3)) && *p == '\r' && *(p+1)=='\n' && *(p+2)=='\r' && *(p+3)=='\n') 
					{ 
						memcpy(szRecvBuf, p+4, sizeof(szRecvBuf) - iCount+1); 
						break; 
					} 
				} 
			} 
 
			isProcessingHead = false; 
		} 
 
		//  将内容写入文件 
		saveFile.write(szRecvBuf, nRet); 
	} 
 
	saveFile.close(); 
 
	printf("\n\n文件下载成功,已保存至:%s", strSaveFileName.c_str()); 
	 
	//  关闭连接 
	closesocket(Socket);	 
} 
 
 
bool ParseURL(string strURL, string& strServerName, u_short& uPortNumber, string& strFileName) 
{ 
	strServerName = ""; 
	uPortNumber = PORT_HTTP_DEFAULT; 
	strFileName = ""; 
 
	string strPrefix = strURL.substr(0, 7); 
//	printf("\n strPrefix = %s", strPrefix.c_str()); 
	if (strPrefix.compare("http://"))	//  必须以 http:// 开头 
	{ 
		return false; 
	} 
 
	int idxFisrtSlash = strURL.find('/', 7); 
	string strNameOrPort = strURL.substr(7, idxFisrtSlash-7); 
	strFileName = strURL.substr(idxFisrtSlash); 
 
	//  检查 URL 中是否有端口号 
	int idxColon = strNameOrPort.find(':'); 
	if (idxColon != -1) 
	{ 
		strServerName = strNameOrPort.substr(0, idxColon); 
		string strPortNumber = strNameOrPort.substr(idxColon + 1); 
		uPortNumber = atoi(strPortNumber.c_str()); 
	} 
	else 
	{ 
		strServerName = strNameOrPort; 
	} 
 
	printf("\nURL 解析成功:\n\n\t服务器   =  %s \n\t端口     = %d \n\t文件路径 = %s", strServerName.c_str(), uPortNumber, strFileName.c_str()); 
 
	return true; 
}