主要難點在于設定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();