天天看點

Windows 和 Linux下使用socket下載下傳網頁頁面内容(可設定接收/發送逾時)的代碼

主要難點在于設定recv()與send()的逾時時間,具體要注意的事項,請看代碼注釋部分,下面是代碼:

#include <stdio.h>  

#include <sys/types.h>  

#include <stdlib.h>  

#include <string.h>  

#include <errno.h>  

#ifdef _WIN32   ///包含win socket相關頭檔案  

#include <winsock.h>  

#pragma comment(lib,"ws2_32.lib")  

#else       ///包含linux socket相關頭檔案  

#include <unistd.h>  

#include <strings.h>  

#include <netinet/in.h>  

#include <sys/socket.h>  

#include <arpa/inet.h>  

#include <netdb.h>  

#include <fcntl.h>  

#include <stdint.h>  

#endif  

#ifdef _WIN32  

#ifdef __cplusplus  

extern "C"{  

int strcasecmp(const char *s1, const char *s2)  

{  

    while ((*s1 != '\0')  

        && (tolower(*(unsigned char *) s1) ==  

        tolower(*(unsigned char *) s2)))   

    {  

        s1++;  

        s2++;  

    }  

    return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);  

}  

int strncasecmp(const char *s1, const char *s2, unsigned int n)  

    if (n == 0)  

        return 0;  

    while ((n-- != 0)  

        tolower(*(unsigned char *) s2))) {  

            if (n == 0 || *s1 == '\0' || *s2 == '\0')  

                return 0;  

            s1++;  

            s2++;  

/********************************** 

*功能:Base64編碼 

*參數: 

    src_data:待編碼的字元串 

    coded_data:編碼後的字元串 

*傳回值:-1,失敗;0,成功 

***********************************/  

int base64encode(const char * src_data/*in,待編碼的字元串*/,char * coded_data/*out,編碼後的字元串*/)  

    const char EncodeTable[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";  

    int src_data_len = strlen(src_data);  

    int i;  

    int lineLength=0;  

    int mod=src_data_len % 3;  

    unsigned char tmp[4]={0};  

    char buff[5]={0};  

    for(i=0;i<(int)(src_data_len / 3);i++)  

        tmp[1] = *src_data++;  

        tmp[2] = *src_data++;  

        tmp[3] = *src_data++;  

        sprintf(buff,"%c%c%c%c", EncodeTable[tmp[1] >> 2],EncodeTable[((tmp[1] << 4) | (tmp[2] >> 4)) & 0x3F],EncodeTable[((tmp[2] << 2) | (tmp[3] >> 6)) & 0x3F],EncodeTable[tmp[3] & 0x3F]);  

        strcat(coded_data,buff);  

        if(lineLength+=4,lineLength==76)   

        {  

            strcat(coded_data,"\r\n");  

            lineLength=0;  

        }  

    }     

    if(mod==1)  

        sprintf(buff,"%c%c==",EncodeTable[(tmp[1] & 0xFC) >> 2],EncodeTable[((tmp[1] & 0x03) << 4)]);  

    else if(mod==2)  

        sprintf(buff,"%c%c%c=",EncodeTable[(tmp[1] & 0xFC) >> 2],EncodeTable[((tmp[1] & 0x03) << 4) | ((tmp[2] & 0xF0) >> 4)],EncodeTable[((tmp[2] & 0x0F) << 2)]);  

    return 0;  

//格式化http頭,傳回值:-1失敗,-2使用者名或密碼無效;>=0 成功  

int format_http_header(const char * webserverip,  

            unsigned short httpport/*web server 端口*/,  

            const char * url/*頁面相對url,下載下傳的頁面為:http://ip/url"*/,  

            const char * username/*網站認證使用者*/,  

            const char * password/*認證密碼*/,  

            const char * ext_param/*通路網頁附加參數*/,  

            int net_timeout/*逾時時間,秒*/,  

            char header[512]/*out*/)  

    int len =0;   

    char buf_auth[100]={0},auth[100]={0};  

    sprintf(buf_auth,"%s:%s",username,password);  

    base64encode(buf_auth,auth);  

    if(ext_param)   

        len = strlen(ext_param);  

    if(len)  

        //GET  

        return sprintf(header,  

                "GET /%s?%s HTTP/1.1\r\n"  

                "Host:%s:%u\r\n"  

                "Content-Type: application/x-www-form-urlencoded\r\n"  

                "Keep-Alive: Keep-Alive: timeout=%d\r\n"  

                "Connection: keep-alive\r\n"  

                "Accept:text/html\r\n"  

                "Authorization: Basic %s\r\n"  

                "\r\n"  

                ,url,ext_param,webserverip,httpport,net_timeout,auth  

                );  

    //GET  

    return sprintf(header,  

            "GET /%s HTTP/1.1\r\n"  

            "Host:%s:%u\r\n"  

            "Content-Type: application/x-www-form-urlencoded\r\n"  

            "Keep-Alive: timeout=%d\r\n"  

            "Connection: keep-alive\r\n"  

            "Accept:text/html\r\n"  

            "Authorization: Basic %s\r\n"  

            "\r\n"  

            ,url,webserverip,httpport,net_timeout,auth  

            );  

    /*POST /login.php HTTP/1.1 必有字段 

    Host: www.webserver.com:80 必有字段 

    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/2008052906 Firefox/3.0 

    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,**; q=.2\r\n");  必有字段 

    Accept-Language: zh-cn,zh;q=0.5 

    Accept-Encoding: gzip,deflate 

    Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7 

    Keep-Alive: 300 

    Connection: keep-alive 

    Referer: http://www.vckbase.com/ 

    Cookie: ASPSESSIONIDCSAATTCD=DOMMILABJOPANJPNNAKAMCPK 

    Content-Type: application/x-www-form-urlencoded 必有字段 

    Content-Length: 79 post方式時必有字段*/  

    /*GET方式HTTP頭寫法*/  

    /*sprintf(header, 

            "GET /ipnc/php/ipnc.php?%s HTTP/1.1\r\n" 

            "Host:%s\r\n" 

            "Content-Type:application/x-www-form-urlencoded\r\n" 

            "Accept:text/html\r\n" 

            "\r\n" 

            ,parm,serverip 

            );*/  

int parse_response_http_header(const char * all_contents/*接收到的所有内容,包含http頭*/,char ** contents/*傳回自己需要的内容*/)  

    /** 

    *根據需求分析網頁的内容 

    **/  

//分析傳回的内容的長度  

int parse_respose_contents_length(const char * header/*in,http頭*/)  

    char * p = (char *)header;  

    int tmp = 0;  

#if 1  

    if(p)  

        //擷取内容長度  

        while(*p)  

            if(*p == '\r')  

            {  

                if(strncasecmp(p,"\r\n\r\n",4) != 0)//http頭沒有結束  

                {  

                    p+=2;//過濾\n  

                    if(strncasecmp(p,"Content-Length",14) == 0)  

                    {  

                        while(*p)  

                        {  

                            if(*p == ':')  

                            {  

                                p++;  

                                tmp = atoi(p);  

                                break;  

                            }  

                            p++;  

                        }  

                        break;  

                    }  

                }  

                else  

                    break;  

            }  

            p++;  

        if(!tmp)//沒有Content-Length字段  

            for(p = (char*)header;*p;p++)  

                if(*p == '\r')  

                    if(strncmp(p,"\r\n\r\n",4) == 0)  

                        p+=4;  

                        tmp = strlen(p);  

    return tmp;  

#define HTTP_RECV_BUFFER_SIZE 1024*1024*3 //3MB的接收緩存  

#define RECV_BUFF_SIZE  1024  

int download_web_page(const char * ipv4/*web server ip位址*/,  

            int net_timeout/*網絡逾時時間,秒*/,  

            char ** contents/*out:傳回的實際内容,無http頭,需要使用free函數手動釋放空間*/  

            )  

    WSADATA wsaData;          //指向WinSocket資訊結構的指針  

    struct sockaddr_in server_addr;  

    int sockfd = -1;  

    char szHttpHeader[1024]={0};  

    char * pszBuffer    =   NULL;///堆棧溢出,是以使用堆空間  

    char szRecvBuffer[RECV_BUFF_SIZE+1]={0};  

    int len = 0,total_recv_len=0,total_contents_len = 0,re=-1;  

    unsigned long flags;  

    fd_set fs;  

    char * pHttpHeaderEnd = NULL;  

    /* 

    *這裡請注意 

    *windows下設定接收/發送逾時時間時,setsockopt函數對應的逾時時間為int型(且逾時時間的值的機關為毫秒,當時我直接填寫為秒,老是接收逾時) 

    *linux下為struct timeval結構 

    */  

    int timeout = net_timeout*1000;  

    struct timeval select_timeout={0};  

    select_timeout.tv_sec=net_timeout;  

#else  

    struct timeval timeout={.tv_sec=net_timeout,.tv_usec=0};  

    if(WSAStartup(MAKEWORD( 1, 1 ), &wsaData )!=0)//進行WinSocket的初始化  

        WSACleanup();  

        return -1;//Can't initiates windows socket!初始化失敗  

    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) <= 0)  

#if defined CONSOLE || defined LINUX  

        printf("建立socket失敗.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

        return -1;//create socket fd failed  

    ///設定接收逾時時間  

    if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(timeout)) != 0)  

        printf("設定socket發送逾時時間失敗.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

        closesocket(sockfd);  

        close(sockfd);  

        return -1;  

    ///設定發送逾時時間  

    if(setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(timeout)) != 0)  

        printf("設定socket接收逾時時間失敗.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

    ///設定非阻塞方式,使用select來判斷connect是否逾時  

    flags=1;  

    if( ioctlsocket(sockfd,FIONBIO,&flags) != 0)  

    flags=fcntl(sockfd,F_GETFL,0);  

    flags |= O_NONBLOCK;  

    if( fcntl(sockfd,F_SETFL,flags) != 0)  

        printf("設定socket為非阻塞失敗.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

    ///設定連接配接參數  

    memset(&server_addr,0,sizeof(struct sockaddr_in));  

    bzero(&server_addr,sizeof(struct sockaddr_in));  

    server_addr.sin_family      = AF_INET;  

    server_addr.sin_port        = htons(httpport);  

    server_addr.sin_addr.s_addr = inet_addr(ipv4);  

    ///連接配接伺服器  

    if( connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) < 0)  

        int ret = 0;  

        ///判斷是否逾時  

        FD_ZERO(&fs);  

        FD_SET(sockfd,&fs);  

        ret = select(sockfd+1,NULL,&fs,NULL,&select_timeout);  

        ret = select(sockfd+1,NULL,&fs,NULL,&timeout);  

        if(ret == 0)//逾時  

            printf("連結伺服器逾時.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

            closesocket(sockfd);  

            close(sockfd);  

            return -1;//連接配接逾時  

        else if(ret < 0)///錯誤  

            printf("連結伺服器時發生錯誤.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

            return -1;  

    ///設定為阻塞方式發送和接收資料  

    flags=0;  

    flags &= ~O_NONBLOCK;  

        printf("設定socket為阻塞失敗.錯誤代碼:%d,錯誤原因:%s\n",errno,strerror(errno));  

        return -1;//ioctlsocket() error  

    format_http_header(ipv4,httpport,url,username,password,ext_param,net_timeout,szHttpHeader);  

    len = strlen(szHttpHeader);  

    ///發送http頭  

    if(send(sockfd,szHttpHeader,len,0) != len)  

        printf("發送http頭失敗.錯誤代碼:%d,錯誤原因:%s\nhttp頭:\n%s\n",errno,strerror(errno),szHttpHeader);  

        return -1;//發送資料失敗  

    ///準備接收資料  

    pszBuffer = (char *)malloc(HTTP_RECV_BUFFER_SIZE);  

    if(!pszBuffer)  

        printf("記憶體配置設定失敗\n");  

        return -1;//outof memory  

    memset(pszBuffer,0,HTTP_RECV_BUFFER_SIZE);  

    bzero(pszBuffer,HTTP_RECV_BUFFER_SIZE);  

    while(1)  

        len = recv(sockfd,szRecvBuffer,RECV_BUFF_SIZE,0);  

        len = recv(sockfd,szRecvBuffer,RECV_BUFF_SIZE,MSG_WAITALL);  

        if(len == 0)  

            printf("接收資料逾時,逾時時間:%d s\n",net_timeout);  

            free(pszBuffer);  

            return -1;//接收資料逾時  

        if(len < 0 )  

            printf("接收資料錯誤,recv傳回值:%d \n",len);  

            return -1;//timeout  

        //printf("%s",szBuffer);  

        total_recv_len += len;  

        if(total_recv_len > (HTTP_RECV_BUFFER_SIZE-1) )  

            printf("接收資料buffer空間不足,目前buffer大小:%d B\n",HTTP_RECV_BUFFER_SIZE-1);  

            return -1;//not enough buffer size  

        strcat(pszBuffer,szRecvBuffer);  

        if(len < RECV_BUFF_SIZE)  

            pHttpHeaderEnd = strstr(pszBuffer,"\r\n\r\n");  

            if(pHttpHeaderEnd )  

                if(!total_contents_len)///http傳回頭中标示的内容大小  

                    total_contents_len = parse_respose_contents_length(pszBuffer);  

                pHttpHeaderEnd += 4;  

                //如果接收到的内容長度已經達到http傳回頭中标示的内容大小,停止接收  

                if( total_contents_len && strlen( pHttpHeaderEnd) >= total_contents_len )  

                pHttpHeaderEnd = NULL;  

            }             

        memset(szRecvBuffer,0,sizeof(szRecvBuffer));  

        bzero(szRecvBuffer,sizeof(szRecvBuffer));  

        len = 0;  

    if(strcmp(pszBuffer,"") == 0)  

        free(pszBuffer);  

        return -1;//recv data error  

    //printf("%s\n",szBuffer);  

    * contents = NULL;  

    re = parse_response_http_header(pszBuffer,contents);  

    if( re != 0 || !(*contents))  

        if(*contents)  

            free(*contents);  

        printf("分析伺服器傳回内容失敗.傳回内容為:\n%s\n",pszBuffer);  

        if( -401 == re)  

            return -1;//使用者名/密碼無效  

        return -1;//unknown error  

    closesocket(sockfd);  

    close(sockfd);  

    free(pszBuffer);  

    WSACleanup();  

繼續閱讀