C++用socket實現簡單的http請求

學習了幾天http相關的東西,用C++實現了一個簡單的 HTTP請求

1 . HttpRes.h

//
//  HttpReq.hpp
//  HttpClient
//
//  Created by LiYong on 2018/1/23.
//

#ifndef HttpReq_hpp
#define HttpReq_hpp

#include <iostream>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>

#define BUFSIZE 4096
#define URLSIZE 2048
#define INVALID_SOCKET -1
#define __DEBUG__

using namespace std;

class HttpRes
{
public:
    HttpRes();
    ~HttpRes();

    static HttpRes *getInstance();
    void debugOut(string fmt,...);
    int httpGet(string strUrl,string &strResponse);
    int httpPost(string strUrl,string strData,string &strResponse);

private:
    int httpResquestExec(string strMethod,string strUrl,string strData,string &strResponse);
    string httpHeadCreate(string strMethod,string strUrl,string strData);
    string httpDataTransmit(string strHttpHead,int isSocFd);

    int getPortFromUrl(string strUrl);
    string getIpFromUrl(string strUrl);
    string getParamFromUrl(string strUrl);
    string getHostAddFromUrl(string strUrl);

    int socketFdCheck(const int iSockFd);

    static int m_iSocketFd;
};

#endif /* HttpReq_hpp */

2.HttpReq.cpp

//
//  HttpReq.cpp
//  HttpClient
//
//  Created by LiYong on 2018/1/23.
//

#include "HttpReq.hpp"
HttpRes::HttpRes()
{
    m_iSocketFd = INVALID_SOCKET;
}
HttpRes::~HttpRes()
{

}
HttpRes *HttpRes::getInstance()
{
    HttpRes *http = new HttpRes();
    if (http)
    {
        return http;
    }
    return nullptr;
}
int HttpRes::httpGet(string strUrl, string &strResponse)
{
    return httpResquestExec("GET", strUrl, "", strResponse);
}
int HttpRes::httpPost(string strUrl,string strData, string &strResponse)
{
    return httpResquestExec("POST",strUrl, strData, strResponse);
}
int HttpRes::httpResquestExec(string strMethod,string strUrl,string strData, string &strResponse)
{
    //判斷URL是否有效
    if (strUrl == "")
    {
        debugOut("URL爲空\n");
        return 0;
    }
    //限制URL的長度
    if (URLSIZE < strUrl.size())
    {
        debugOut("URL的長度不能超過:%d\n",URLSIZE);
        return 0;
    }

    //創建HTTP協議表頭
    string strHttpHead = httpHeadCreate(strMethod, strUrl, strData);

    if (m_iSocketFd != INVALID_SOCKET)
    {
        string strResult = httpDataTransmit(strHttpHead, m_iSocketFd);
        if (strResult != "")
        {
            strResponse = strResult;
            return 1;
        }
    }
    //創建Socket
    m_iSocketFd = INVALID_SOCKET;
    m_iSocketFd = socket(AF_INET, SOCK_STREAM, 0);
    if (m_iSocketFd < 0)
    {
        debugOut("socket error! Error code: %d,Error message: %s\n",errno,strerror(errno));
        return 0;
    }

    //綁定地址端口
    int iPort = getPortFromUrl(strUrl);
    if (iPort < 0)
    {
        debugOut("獲取URL端口失敗\n");
        return 0;
    }
    string strIP = getIpFromUrl(strUrl);
    if (strIP == "")
    {
        debugOut("從URL獲取IP地址失敗\n");
        return 0;
    }

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(iPort);
    if (inet_pton(AF_INET,strIP.data(), &servaddr.sin_addr) <= 0)
    {
        debugOut("inet_pton error ! Error code: %d,Error message:%s\n",errno,strerror(errno));
        close(m_iSocketFd);
        m_iSocketFd = INVALID_SOCKET;
        return 0;
    }

    int flags = fcntl(m_iSocketFd, F_SETFL, 0);
    if (fcntl(m_iSocketFd, F_SETFL,flags|O_NONBLOCK) == -1)
    {
        close(m_iSocketFd);
        m_iSocketFd = INVALID_SOCKET;
        debugOut("fcntl error! Error code: %d,Error message: %s",errno,strerror(errno));
        return 0;
    }

    //非阻塞方式連接
    int iRet = connect(m_iSocketFd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (iRet == 0)
    {
        string strResult = httpDataTransmit(strHttpHead, m_iSocketFd);
        if (NULL == strResult.c_str())
        {
            close(m_iSocketFd);
            m_iSocketFd = INVALID_SOCKET;
            return 0;
        }
        else
        {
            strResponse = strResult;
            return 1;
        }
    }
    else if(iRet < 0)
    {
        if (errno != EINPROGRESS)
        {
            return 0;
        }
    }

    iRet = socketFdCheck(m_iSocketFd);
    if (iRet > 0)
    {
        string strResult = httpDataTransmit(strHttpHead, m_iSocketFd);
        if (strResult == "")
        {
            close(m_iSocketFd);
            m_iSocketFd = INVALID_SOCKET;
            return 0;
        }
        else
        {
            strResponse = strResult;

            return 1;
        }
    }
    else
    {
        close(m_iSocketFd);
        m_iSocketFd = INVALID_SOCKET;
        return 0;
    }
    return 1;
}
string HttpRes::httpHeadCreate(string strMethod,string strUrl,string strData)
{
    string strHost = getHostAddFromUrl(strUrl);
    string strParam = getParamFromUrl(strUrl);

    string strHttpHead;
    strHttpHead.append(strMethod);
    strHttpHead.append(" /");
    strHttpHead.append(strParam);
    strHttpHead.append(" HTTP/1.1\r\n");
    strHttpHead.append("Accept: */*\r\n");
    strHttpHead.append("Accept-Language: cn\r\n");
    strHttpHead.append("User-Agent: Mozilla/4.0\r\n");
    strHttpHead.append("Host: ");
    strHttpHead.append(strHost);
    strHttpHead.append("\r\n");
    strHttpHead.append("Cache-Control: no-cache\r\n");
    strHttpHead.append("Connection: Keep-Alive\r\n");
    if (strMethod == "POST")
    {
        char len[8] = {0};
        unsigned long iLen = strData.size();
        sprintf(len, "%lu",iLen);

        strHttpHead.append("Content-Type: application/x-www-form-urlencoded\r\n");
        strHttpHead.append("Content-Length: ");
        strHttpHead.append(len);
        strHttpHead.append("\r\n\r\n");
        strHttpHead.append(strData);
    }
    strHttpHead.append("\r\n\r\n");

    return strHttpHead;
}
//發送HTTP請求並且接受響應
string HttpRes::httpDataTransmit(string strHttpHead,int isSocFd)
{
    char *buf = (char *)malloc(BUFSIZ);
    memset(buf, 0, BUFSIZ);
    char *head = (char *)strHttpHead.data();
    long ret = send(isSocFd, (void *)head, strlen(head)+1, 0);
    if (ret < 0)
    {
        debugOut("send error ! Error code: %d,Error message: %s\n",errno,strerror(errno));
        close(isSocFd);
        return nullptr;
    }
    while (1)
    {
        ret = recv(isSocFd, (void *)buf, BUFSIZ, 0);
        if (ret == 0)
        {
            close(isSocFd);
            free(buf);
            return nullptr;
        }
        else if (ret > 0)
        {
            string strRecv = buf;
            free(buf);
            return strRecv;
        }
        else if (ret < 0)
        {
            if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
            {
                continue;
            }
            else
            {
                close(isSocFd);
                free(buf);
                return nullptr;
            }
        }
    }
}
//從URL中獲取Host地址
string HttpRes::getHostAddFromUrl(string strUrl)
{

    char url[URLSIZE] = {0};
    strcpy(url, strUrl.c_str());
    char *strAddr = strstr(url, "http://");
    if (strAddr == NULL)
    {
        strAddr = strstr(url, "https://");
        if (strAddr != NULL)
        {
            strAddr += 8;
        }
    }
    else
    {
        strAddr += 7;
    }
    if (strAddr == NULL)
    {
        strAddr = url;
    }

    char * strHostAddr = (char *)malloc(strlen(strAddr) +1);

    memset(strHostAddr, 0, strlen(strAddr) + 1);
    for (int i = 0; i < strlen(strAddr) + 1; i++)
    {
        if (strAddr[i] == '/')
        {
            break;
        }
        else
        {
            strHostAddr[i] = strAddr[i];
        }
    }
    string host = strHostAddr;
    free(strHostAddr);
    return host;
}
//從HTTP請求URL中獲取HTTP請求參數
string HttpRes::getParamFromUrl(string strUrl)
{
    char url[URLSIZE] = {0};
    strcpy(url, strUrl.c_str());

    char *strAddr = strstr(url, "http://");
    if (strAddr == NULL)
    {
        strAddr = strstr(url, "https://");
        if (strAddr != NULL)
        {
            strAddr += 8;
        }
    }
    else
    {
        strAddr += 7;
    }
    if (strAddr == NULL)
    {
        strAddr = url;
    }

    char *strParam = (char *)malloc(strlen(strAddr)+1);
    memset(strParam, 0, strlen(strAddr)+1);
    int iPos = -1;
    for (int i = 0; i < strlen(strAddr)+1; i++)
    {
        if (strAddr[i] == '/')
        {
            iPos = i;
            break;
        }
    }
    if (iPos == -1)
    {
        strcpy(strParam, "");
    }
    else
    {
        strcpy(strParam, strAddr+iPos+1);
    }
    string param = strParam;
    free(strParam);
    return param;
}
//從HTTP請求URL中獲取端口號
int HttpRes::getPortFromUrl(string strUrl)
{
    int nPort = -1;
    char *strHostAddr = (char *)getHostAddFromUrl(strUrl).data();

    if (strHostAddr == NULL)
    {
        return -1;
    }

    char strAddr[URLSIZE] = {0};
    strcpy(strAddr, strHostAddr);
    char *strPort = strchr(strAddr, ':');
    if (strPort == NULL)
    {
        nPort = 80;
    }
    else
    {
        nPort = atoi(++strPort);
    }
    return nPort;
}
//從Http請求URL中獲取IP地址
string HttpRes::getIpFromUrl(string strUrl)
{

    string url = getHostAddFromUrl(strUrl);
    char *strHostAddr = (char *)url.data();
    char *strAddr = (char *)malloc(strlen(strHostAddr) + 1);
    memset(strAddr, 0, strlen(strAddr)+1);
    int nCount = 0;
    int nFlag = 0;
    for (int i = 0; i < strlen(strAddr) + 1; i++)
    {
        if (strHostAddr[i] == ':')
        {
            break;
        }
        strAddr[i] = strHostAddr[i];
        if (strHostAddr[i] == '.')
        {
            nCount++;
            continue;
        }
        if (nFlag == 1)
        {
            continue;
        }
        if ((strHostAddr[i] >= 0)||(strHostAddr[i] <= '9'))
        {
            nFlag = 0;
        }
        else
        {
            nFlag = 1;
        }
    }
    if (strlen(strAddr) <= 1)
    {
        return NULL;
    }

    //判斷是否爲點分十進制IP,否則通過域名獲取IP
    if ((nCount == 3) && (nFlag == 0))
    {
        return strAddr;
    }
    else
    {
        struct hostent *he = gethostbyname(strAddr);
        free(strAddr);
        if (he == NULL)
        {
            return NULL;
        }
        else
        {
            struct in_addr** addr_list = (struct in_addr **)he->h_addr_list;
            for (int i = 0; addr_list[i] != NULL; i++)
            {
                return inet_ntoa(*addr_list[i]);
            }
            return NULL;
        }
    }
}
//檢查socketFd是否爲可寫不可讀狀態
int HttpRes::socketFdCheck(const int iSockFd)
{
    struct timeval timeout ;
    fd_set rset,wset;
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_SET(iSockFd, &rset);
    FD_SET(iSockFd, &wset);
    timeout.tv_sec = 3;
    timeout.tv_usec = 500;
    int iRet = select(iSockFd+1, &rset, &wset, NULL, &timeout);
    if(iRet > 0)
    {
        //判斷SocketFd是否爲可寫不可讀狀態
        int iW = FD_ISSET(iSockFd,&wset);
        int iR = FD_ISSET(iSockFd,&rset);
        if(iW && !iR)
        {
            char error[4] = "";
            socklen_t len = sizeof(error);
            int ret = getsockopt(iSockFd,SOL_SOCKET,SO_ERROR,error,&len);
            if(ret == 0)
            {
                if(!strcmp(error, ""))
                {
                    return iRet;//表示已經準備好的描述符數
                }
                else
                {
                    debugOut("%s %s %d\tgetsockopt error code:%d,error message:%s", __FILE__, __FUNCTION__, __LINE__, errno, strerror(errno));
                }
            }
            else
            {
                debugOut("%s %s %d\tgetsockopt failed. error code:%d,error message:%s", __FILE__, __FUNCTION__, __LINE__, errno, strerror(errno));
            }
        }
        else
        {
            debugOut("%s %s %d\tsockFd是否在可寫字符集中:%d,是否在可讀字符集中:%d\t(0表示不在)\n", __FILE__, __FUNCTION__, __LINE__, iW, iR);
        }
    }
    else if(iRet == 0)
    {
        return 0;//表示超時
    }
    else
    {
        return -1;//select出錯,所有描述符集清0
    }
    return -2;//其他錯誤
}
void HttpRes::debugOut(string fmt, ...)
{
#ifdef __DEBUG__
    va_list ap;
    va_start(ap, fmt);
    vprintf(fmt.c_str(), ap);
    va_end(ap);
#endif
}
int HttpRes::m_iSocketFd = INVALID_SOCKET;

3.main.cpp

//
//  main.cpp
//  HttpClient
//
//  Created by LiYong on 2018/1/23.
//

#include <iostream>
#include "HttpReq.hpp"
using namespace std;

int main(int argc, const char * argv[])
{
    HttpRes *http = HttpRes::getInstance();

    string url = "http://192.168.0.72:8000/";
    string return_msg;
    if (http->httpGet(url , return_msg))
    {
        cout << return_msg <<endl;
    }
    return 0;
}

響應服務端我是用Django但搭建。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章