前言
项目中发现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");
}