C++ 实现http 超时客户端 FD_SET模型

C++ 实现http 超时客户端 FD_SET模型

前言

项目中发现libcurl实现的http客户端,有速度慢的问题,可能也是我没用好。突发奇想,自己写个http通信类,实现可控。因为只是用于研究,没有正式上线,只能保证基本能用,不保证没bug。

头文件


#pragma once
#include<sstream>
#include <string>
#include <iostream>
using namespace std;

#ifdef WIN32
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#endif

class HttpConnect
{
public:
	HttpConnect(void);
	~HttpConnect(void);

public:
    //通信接口
	void SocketComminucation(string host,int port, std::string request,int mSeptime);
protect:    
    //连接超时
	bool MyConnect(char *host,int port, int timeout);
    //post接口
	void postData(std::string host, std::string path, std::string post_content);
	//get接口
    void getData(std::string host, std::string path, std::string get_content);

	//发送超时接口
	int SendData(SOCKET m_sock, const char* msg, int len, int* pBytesSend);
	//接收超时接口
	int RecvData(SOCKET m_sock, char* pData, int* len);
	//错误信息打印
    void printSocketErrorInfo();

private:
	int m_nSecTime;
};

cpp实现

#include "StdAfx.h"
#include "HttpConnect.h"



#define SEND_TRY_TIMES   0



HttpConnect::HttpConnect():
	m_nSecTime(0)
{
#ifdef WIN32
	//此处一定要初始化一下,否则gethostbyname返回一直为空
	WSADATA wsa = { 0 };
	WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
}

HttpConnect::~HttpConnect()
{

}

//只有全部发送完时才返回大于0(同时也等于*pBytesSend的值)
//否则返回值同select或send函数,pBytesSend保存实际发送字节数,内部会先select
int HttpConnect::SendData(SOCKET m_sock, const char* msg, int len, int* pBytesSend)
{
	fd_set fdWrite;
	timeval TimeOut;
	int ret, nLeft(len), idx(0), i(0);
	TimeOut.tv_sec = m_nSecTime;
	TimeOut.tv_usec = 0;
	do
	{
		FD_ZERO(&fdWrite);
		FD_SET(m_sock, &fdWrite);
		ret = select(m_sock+1, NULL, &fdWrite, NULL, &TimeOut);
		if (ret > 0)
		{
			ret = send(m_sock, msg+idx, nLeft, 0);
			if (ret == 0) //对方连接中断
			{
				*pBytesSend = idx;
				return ret;
			}
			if (ret == SOCKET_ERROR)
			{
				*pBytesSend = idx;
				return ret;
			}
			idx += ret;
			nLeft -= ret;
		}
		else if(SOCKET_ERROR==ret)
		{
			*pBytesSend = idx;
			return ret;
		}
		else//select超时
		{
			if(++i>SEND_TRY_TIMES)//重试SEND_RECV_TRY_TIMES次失败则返回失败
			{
				*pBytesSend = idx;
				return ret;
			}
		}
	}
	while (nLeft > 0);
	*pBytesSend = idx;
	return idx;
}


int HttpConnect::RecvData(SOCKET m_sock, char* pData, int* len)
{
	fd_set fdRead;
	timeval TimeOut;
	int ret, rc(0), offset(0);
	TimeOut.tv_sec = m_nSecTime;
	TimeOut.tv_usec = 0;
	do
	{
		FD_ZERO(&fdRead);
		FD_SET(m_sock, &fdRead);
		ret = select(m_sock+1, &fdRead, NULL, NULL, &TimeOut);
		if (ret > 0)
		{
#ifdef WIN32
			while(rc = recv(m_sock,(char*) pData+offset, 1024,0))
#else
			while(rc = read(hSocket, pData+offset, 1024))
#endif
			{
				if (rc >0)
				{
					offset += rc;
					std::cout  <<"size:"<< rc << std::endl;
				}
			}

			*len = offset;
		}
		else if(SOCKET_ERROR==ret)
		{
			printSocketErrorInfo();
			return ret;
		}
		else//select超时
		{
			std::cout<<"超时"<<endl;
		}
	}
	while (0);

	return *len;
}


void HttpConnect::SocketComminucation(string host,int port, std::string request,int mSeptime)
{
	//if (!SocketConnect(host,port))
	//{
	//	std::cout  << "connect failed" << std::endl;
	//	return;
	//}

	m_nSecTime = mSeptime;

	struct sockaddr_in serverAddress;
	SOCKET hSocket = NULL;
	hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	//hSocket = WSASocket(AF_INET, SOCK_STREAM, TPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if( hSocket==INVALID_SOCKET)
	{
		return;
	}

	memset(&serverAddress, 0, sizeof(serverAddress));     
	serverAddress.sin_family      = AF_INET;
	serverAddress.sin_addr.s_addr = inet_addr(host.c_str());   
	serverAddress.sin_port        = htons((short)port);

	int iMode = 0;//block
	ioctlsocket(hSocket, FIONBIO, (u_long FAR*) &iMode);

	DWORD TimeOut=mSeptime;

	if( SOCKET_ERROR==connect(hSocket, (sockaddr*)&serverAddress, sizeof(serverAddress)) )
	{        
		closesocket(hSocket);
		DWORD gle = WSAGetLastError();
		return;
	}

	int nRealSend,nRealRecv;
	if (SendData(hSocket,request.c_str(),request.length(),&nRealSend) > 0)
	{
		char buf[1024*1024] = {0};
		RecvData(hSocket,buf,&nRealRecv);
		std::cout  << buf << std::endl;
	}



#ifdef WIN32
	closesocket(hSocket);
#else
	close(hSocket);
#endif

}

//变相的实现connect的超时,我要讲的就是这个方法,原理上是这样的:
//	1.建立socket
//	2.将该socket设置为非阻塞模式
//	3.调用connect()
//	4.使用select()检查该socket描述符是否可写(注意,是可写)
//	5.根据select()返回的结果判断connect()结果
//	6.将socket设置为阻塞模式(如果你的程序不需要用阻塞模式的,这步就省了,不过一般情况下都是用阻塞模式的,这样也容易管理)
bool HttpConnect::MyConnect(char *host,int port, int timeout)
{
	bool bRet = false;
	SOCKET sock = INVALID_SOCKET;
	do 
	{
		TIMEVAL Timeout;
		Timeout.tv_sec = timeout;
		Timeout.tv_usec = 0;
		struct sockaddr_in address;  /* the libc network address data structure */   

		SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sock == INVALID_SOCKET)
		{
			printSocketErrorInfo();
			break;
		}

		address.sin_addr.s_addr = inet_addr(host); /* assign the address */
		address.sin_port = htons(port);            /* translate int2port num */
		address.sin_family = AF_INET;

		//set the socket in non-blocking
		unsigned long iMode = 1;
		int iResult = ioctlsocket(sock, FIONBIO, &iMode);
		if (iResult != NO_ERROR)
		{
			printf("ioctlsocket failed with error: %ld\n", iResult);
			printSocketErrorInfo();
			break;
		}

		if( SOCKET_ERROR==connect(sock, (sockaddr*)&address, sizeof(address)) )
		{
			int error = -1;
			timeval tm;
			fd_set set_w;
			fd_set set_r;
			int len = sizeof(int);
			tm.tv_sec  = timeout;
			tm.tv_usec = 0;
			FD_ZERO(&set_w);
			FD_SET(sock, &set_w);

			FD_ZERO(&set_r);
			FD_SET(sock, &set_r);

			if( select(sock+1 ,&set_r ,&set_w, NULL, &tm) > 0)
			{
				getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, /*(socklen_t *)*/&len);
				if(error == 0) 
					bRet = true;
			} 

			if (!bRet)
			{
				printSocketErrorInfo();
				break;
			}
		}else
		{
			bRet = true;
		}

		// restart the socket mode
		iMode = 0;
		iResult = ioctlsocket(sock, FIONBIO, &iMode);
		if (iResult != NO_ERROR)
		{
			printf("ioctlsocket failed with error: %ld\n", iResult);
			printSocketErrorInfo();
			break;
		}
	} while (0);

#ifdef WIN32
	closesocket(sock);
#else
	close(sock);
#endif

	return bRet;
}


void HttpConnect::postData(std::string host, std::string path, std::string post_content)
{
	//POST请求方式 
	std::stringstream stream; 
	stream << "POST " << path; 
	stream << " HTTP/1.0\r\n"; 
	stream << "Host: "<< host << "\r\n";
	stream << "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3\r\n";
	stream << "Content-Type:application/json\r\n";
	stream << "Content-Length:" << post_content.length()<<"\r\n";
	stream << "Connection:close\r\n\r\n"; 
	stream << post_content.c_str();

	string strInfo = stream.str();
	SocketComminucation(host,5557,strInfo,6);
} 

void HttpConnect::getData(std::string host, std::string path, std::string get_content)
{
	//GET请求方式 
	std::stringstream stream; 
	stream << "GET " << path << "?" << get_content; 
	stream << " HTTP/1.0\r\n"; 
	stream << "Host: " << host << "\r\n"; 
	stream <<"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3\r\n"; 
	stream <<"Connection:close\r\n\r\n"; 

	string strInfo = stream.str();
	SocketComminucation(host,5557,strInfo,1000); 
}



void HttpConnect::printSocketErrorInfo()
{
	int id = WSAGetLastError();
	switch (id)
	{
		case WSANOTINITIALISED: printf("not initialized\n"); break;
		case WSASYSNOTREADY: printf("sub sys not ready\n"); break;
		case WSAHOST_NOT_FOUND: printf("name server not found\n"); break;
		case WSATRY_AGAIN: printf("server fail\n"); break;
		case WSANO_RECOVERY: printf("no recovery\n"); break;
		case WSAEINPROGRESS: printf("socket blocked by other prog\n"); break;
		case WSANO_DATA: printf("no data record\n"); break;
		case WSAEINTR: printf("blocking call canciled\n"); break;
		case WSAEPROCLIM: printf("limit exceeded\n");
		case WSAEFAULT: printf("lpWSAData in startup not valid\n");
		default: printf("unknown error id = %d\n",id); break;
	};
	printf("receive error.\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值