Windows 和 Linux下使用socket下载网页页面内容(可设置接收/发送超时)的代码

时间:2021-10-20 04:44:06

主要难点在于设置recv()与send()的超时时间,具体要注意的事项,请看代码注释部分,下面是代码:

  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <errno.h>
  6. #include <string.h>
  7. #ifdef _WIN32   ///包含win socket相关头文件
  8. #include <winsock.h>
  9. #pragma comment(lib,"ws2_32.lib")
  10. #else       ///包含linux socket相关头文件
  11. #include <unistd.h>
  12. #include <strings.h>
  13. #include <netinet/in.h>
  14. #include <sys/socket.h>
  15. #include <arpa/inet.h>
  16. #include <netdb.h>
  17. #include <fcntl.h>
  18. #include <stdint.h>
  19. #endif
  20. #ifdef _WIN32
  21. #ifdef __cplusplus
  22. extern "C"{
  23. #endif
  24. int strcasecmp(const char *s1, const char *s2)
  25. {
  26. while ((*s1 != '\0')
  27. && (tolower(*(unsigned char *) s1) ==
  28. tolower(*(unsigned char *) s2)))
  29. {
  30. s1++;
  31. s2++;
  32. }
  33. return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);
  34. }
  35. int strncasecmp(const char *s1, const char *s2, unsigned int n)
  36. {
  37. if (n == 0)
  38. return 0;
  39. while ((n-- != 0)
  40. && (tolower(*(unsigned char *) s1) ==
  41. tolower(*(unsigned char *) s2))) {
  42. if (n == 0 || *s1 == '\0' || *s2 == '\0')
  43. return 0;
  44. s1++;
  45. s2++;
  46. }
  47. return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);
  48. }
  49. #ifdef __cplusplus
  50. }
  51. #endif
  52. #endif
  53. /**********************************
  54. *功能:Base64编码
  55. *参数:
  56. src_data:待编码的字符串
  57. coded_data:编码后的字符串
  58. *返回值:-1,失败;0,成功
  59. ***********************************/
  60. int base64encode(const char * src_data/*in,待编码的字符串*/,char * coded_data/*out,编码后的字符串*/)
  61. {
  62. const char EncodeTable[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  63. int src_data_len = strlen(src_data);
  64. int i;
  65. int lineLength=0;
  66. int mod=src_data_len % 3;
  67. unsigned char tmp[4]={0};
  68. char buff[5]={0};
  69. for(i=0;i<(int)(src_data_len / 3);i++)
  70. {
  71. tmp[1] = *src_data++;
  72. tmp[2] = *src_data++;
  73. tmp[3] = *src_data++;
  74. 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]);
  75. strcat(coded_data,buff);
  76. if(lineLength+=4,lineLength==76)
  77. {
  78. strcat(coded_data,"\r\n");
  79. lineLength=0;
  80. }
  81. }
  82. if(mod==1)
  83. {
  84. tmp[1] = *src_data++;
  85. sprintf(buff,"%c%c==",EncodeTable[(tmp[1] & 0xFC) >> 2],EncodeTable[((tmp[1] & 0x03) << 4)]);
  86. strcat(coded_data,buff);
  87. }
  88. else if(mod==2)
  89. {
  90. tmp[1] = *src_data++;
  91. tmp[2] = *src_data++;
  92. sprintf(buff,"%c%c%c=",EncodeTable[(tmp[1] & 0xFC) >> 2],EncodeTable[((tmp[1] & 0x03) << 4) | ((tmp[2] & 0xF0) >> 4)],EncodeTable[((tmp[2] & 0x0F) << 2)]);
  93. strcat(coded_data,buff);
  94. }
  95. return 0;
  96. }
  97. //格式化http头,返回值:-1失败,-2用户名或密码无效;>=0 成功
  98. int format_http_header(const char * webserverip,
  99. unsigned short httpport/*web server 端口*/,
  100. const char * url/*页面相对url,下载的页面为:http://ip/url"*/,
  101. const char * username/*网站认证用户*/,
  102. const char * password/*认证密码*/,
  103. const char * ext_param/*访问网页附加参数*/,
  104. int net_timeout/*超时时间,秒*/,
  105. char header[512]/*out*/)
  106. {
  107. int len =0;
  108. char buf_auth[100]={0},auth[100]={0};
  109. sprintf(buf_auth,"%s:%s",username,password);
  110. base64encode(buf_auth,auth);
  111. if(ext_param)
  112. {
  113. len = strlen(ext_param);
  114. }
  115. if(len)
  116. {
  117. //GET
  118. return sprintf(header,
  119. "GET /%s?%s HTTP/1.1\r\n"
  120. "Host:%s:%u\r\n"
  121. "Content-Type: application/x-www-form-urlencoded\r\n"
  122. "Keep-Alive: Keep-Alive: timeout=%d\r\n"
  123. "Connection: keep-alive\r\n"
  124. "Accept:text/html\r\n"
  125. "Authorization: Basic %s\r\n"
  126. "\r\n"
  127. ,url,ext_param,webserverip,httpport,net_timeout,auth
  128. );
  129. }
  130. //GET
  131. return sprintf(header,
  132. "GET /%s HTTP/1.1\r\n"
  133. "Host:%s:%u\r\n"
  134. "Content-Type: application/x-www-form-urlencoded\r\n"
  135. "Keep-Alive: timeout=%d\r\n"
  136. "Connection: keep-alive\r\n"
  137. "Accept:text/html\r\n"
  138. "Authorization: Basic %s\r\n"
  139. "\r\n"
  140. ,url,webserverip,httpport,net_timeout,auth
  141. );
  142. /*POST /login.php HTTP/1.1 必有字段
  143. Host: www.webserver.com:80 必有字段
  144. User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/2008052906 Firefox/3.0
  145. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,**; q=.2\r\n");  必有字段
  146. Accept-Language: zh-cn,zh;q=0.5
  147. Accept-Encoding: gzip,deflate
  148. Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
  149. Keep-Alive: 300
  150. Connection: keep-alive
  151. Referer: http://www.vckbase.com/
  152. Cookie: ASPSESSIONIDCSAATTCD=DOMMILABJOPANJPNNAKAMCPK
  153. Content-Type: application/x-www-form-urlencoded 必有字段
  154. Content-Length: 79 post方式时必有字段*/
  155. /*GET方式HTTP头写法*/
  156. /*sprintf(header,
  157. "GET /ipnc/php/ipnc.php?%s HTTP/1.1\r\n"
  158. "Host:%s\r\n"
  159. "Content-Type:application/x-www-form-urlencoded\r\n"
  160. "Accept:text/html\r\n"
  161. "\r\n"
  162. ,parm,serverip
  163. );*/
  164. }
  165. int parse_response_http_header(const char * all_contents/*接收到的所有内容,包含http头*/,char ** contents/*返回自己需要的内容*/)
  166. {
  167. /**
  168. *根据需求分析网页的内容
  169. **/
  170. return 0;
  171. }
  172. //分析返回的内容的长度
  173. int parse_respose_contents_length(const char * header/*in,http头*/)
  174. {
  175. char * p = (char *)header;
  176. int tmp = 0;
  177. #if 1
  178. if(p)
  179. {
  180. //获取内容长度
  181. while(*p)
  182. {
  183. if(*p == '\r')
  184. {
  185. if(strncasecmp(p,"\r\n\r\n",4) != 0)//http头没有结束
  186. {
  187. p+=2;//过滤\n
  188. if(strncasecmp(p,"Content-Length",14) == 0)
  189. {
  190. while(*p)
  191. {
  192. if(*p == ':')
  193. {
  194. p++;
  195. tmp = atoi(p);
  196. break;
  197. }
  198. p++;
  199. }
  200. break;
  201. }
  202. }
  203. else
  204. {
  205. break;
  206. }
  207. }
  208. p++;
  209. }
  210. if(!tmp)//没有Content-Length字段
  211. {
  212. for(p = (char*)header;*p;p++)
  213. {
  214. if(*p == '\r')
  215. {
  216. if(strncmp(p,"\r\n\r\n",4) == 0)
  217. {
  218. p+=4;
  219. tmp = strlen(p);
  220. break;
  221. }
  222. }
  223. }
  224. }
  225. }
  226. #endif
  227. return tmp;
  228. }
  229. #define HTTP_RECV_BUFFER_SIZE 1024*1024*3 //3MB的接收缓存
  230. #define RECV_BUFF_SIZE  1024
  231. int download_web_page(const char * ipv4/*web server ip地址*/,
  232. unsigned short httpport/*web server 端口*/,
  233. const char * url/*页面相对url,下载的页面为:http://ip/url"*/,
  234. const char * username/*网站认证用户*/,
  235. const char * password/*认证密码*/,
  236. const char * ext_param/*访问网页附加参数*/,
  237. int net_timeout/*网络超时时间,秒*/,
  238. char ** contents/*out:返回的实际内容,无http头,需要使用free函数手动释放空间*/
  239. )
  240. {
  241. #ifdef _WIN32
  242. WSADATA wsaData;          //指向WinSocket信息结构的指针
  243. #endif
  244. struct sockaddr_in server_addr;
  245. int sockfd = -1;
  246. char szHttpHeader[1024]={0};
  247. char * pszBuffer    =   NULL;///堆栈溢出,所以使用堆空间
  248. char szRecvBuffer[RECV_BUFF_SIZE+1]={0};
  249. int len = 0,total_recv_len=0,total_contents_len = 0,re=-1;
  250. unsigned long flags;
  251. fd_set fs;
  252. char * pHttpHeaderEnd = NULL;
  253. #ifdef _WIN32
  254. /*
  255. *这里请注意
  256. *windows下设置接收/发送超时时间时,setsockopt函数对应的超时时间为int型(且超时时间的值的单位为毫秒,当时我直接填写为秒,老是接收超时)
  257. *linux下为struct timeval结构
  258. */
  259. int timeout = net_timeout*1000;
  260. struct timeval select_timeout={0};
  261. select_timeout.tv_sec=net_timeout;
  262. #else
  263. struct timeval timeout={.tv_sec=net_timeout,.tv_usec=0};
  264. #endif
  265. #ifdef _WIN32
  266. if(WSAStartup(MAKEWORD( 1, 1 ), &wsaData )!=0)//进行WinSocket的初始化
  267. {
  268. WSACleanup();
  269. return -1;//Can't initiates windows socket!初始化失败
  270. }
  271. #endif
  272. if((sockfd = socket(AF_INET,SOCK_STREAM,0)) <= 0)
  273. {
  274. #if defined CONSOLE || defined LINUX
  275. printf("创建socket失败.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  276. #endif
  277. return -1;//create socket fd failed
  278. }
  279. ///设置接收超时时间
  280. if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(timeout)) != 0)
  281. {
  282. #if defined CONSOLE || defined LINUX
  283. printf("设置socket发送超时时间失败.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  284. #endif
  285. #ifdef _WIN32
  286. closesocket(sockfd);
  287. #else
  288. close(sockfd);
  289. #endif
  290. return -1;
  291. }
  292. ///设置发送超时时间
  293. if(setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(timeout)) != 0)
  294. {
  295. #if defined CONSOLE || defined LINUX
  296. printf("设置socket接收超时时间失败.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  297. #endif
  298. #ifdef _WIN32
  299. closesocket(sockfd);
  300. #else
  301. close(sockfd);
  302. #endif
  303. return -1;
  304. }
  305. ///设置非阻塞方式,使用select来判断connect是否超时
  306. #ifdef _WIN32
  307. flags=1;
  308. if( ioctlsocket(sockfd,FIONBIO,&flags) != 0)
  309. #else
  310. flags=fcntl(sockfd,F_GETFL,0);
  311. flags |= O_NONBLOCK;
  312. if( fcntl(sockfd,F_SETFL,flags) != 0)
  313. #endif
  314. {
  315. #if defined CONSOLE || defined LINUX
  316. printf("设置socket为非阻塞失败.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  317. #endif
  318. #ifdef _WIN32
  319. closesocket(sockfd);
  320. #else
  321. close(sockfd);
  322. #endif
  323. return -1;
  324. }
  325. ///设置连接参数
  326. #ifdef _WIN32
  327. memset(&server_addr,0,sizeof(struct sockaddr_in));
  328. #else
  329. bzero(&server_addr,sizeof(struct sockaddr_in));
  330. #endif
  331. server_addr.sin_family      = AF_INET;
  332. server_addr.sin_port        = htons(httpport);
  333. server_addr.sin_addr.s_addr = inet_addr(ipv4);
  334. ///连接服务器
  335. if( connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) < 0)
  336. {
  337. int ret = 0;
  338. ///判断是否超时
  339. FD_ZERO(&fs);
  340. FD_SET(sockfd,&fs);
  341. #ifdef _WIN32
  342. ret = select(sockfd+1,NULL,&fs,NULL,&select_timeout);
  343. #else
  344. ret = select(sockfd+1,NULL,&fs,NULL,&timeout);
  345. #endif
  346. if(ret == 0)//超时
  347. {
  348. #if defined CONSOLE || defined LINUX
  349. printf("链接服务器超时.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  350. #endif
  351. #ifdef _WIN32
  352. closesocket(sockfd);
  353. #else
  354. close(sockfd);
  355. #endif
  356. return -1;//连接超时
  357. }
  358. else if(ret < 0)///错误
  359. {
  360. #if defined CONSOLE || defined LINUX
  361. printf("链接服务器时发生错误.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  362. #endif
  363. #ifdef _WIN32
  364. closesocket(sockfd);
  365. #else
  366. close(sockfd);
  367. #endif
  368. return -1;
  369. }
  370. }
  371. ///设置为阻塞方式发送和接收数据
  372. #ifdef _WIN32
  373. flags=0;
  374. if( ioctlsocket(sockfd,FIONBIO,&flags) != 0)
  375. #else
  376. flags=fcntl(sockfd,F_GETFL,0);
  377. flags &= ~O_NONBLOCK;
  378. if( fcntl(sockfd,F_SETFL,flags) != 0)
  379. #endif
  380. {
  381. #if defined CONSOLE || defined LINUX
  382. printf("设置socket为阻塞失败.错误代码:%d,错误原因:%s\n",errno,strerror(errno));
  383. #endif
  384. #ifdef _WIN32
  385. closesocket(sockfd);
  386. #else
  387. close(sockfd);
  388. #endif
  389. return -1;//ioctlsocket() error
  390. }
  391. format_http_header(ipv4,httpport,url,username,password,ext_param,net_timeout,szHttpHeader);
  392. len = strlen(szHttpHeader);
  393. ///发送http头
  394. if(send(sockfd,szHttpHeader,len,0) != len)
  395. {
  396. #if defined CONSOLE || defined LINUX
  397. printf("发送http头失败.错误代码:%d,错误原因:%s\nhttp头:\n%s\n",errno,strerror(errno),szHttpHeader);
  398. #endif
  399. #ifdef _WIN32
  400. closesocket(sockfd);
  401. #else
  402. close(sockfd);
  403. #endif
  404. return -1;//发送数据失败
  405. }
  406. ///准备接收数据
  407. pszBuffer = (char *)malloc(HTTP_RECV_BUFFER_SIZE);
  408. if(!pszBuffer)
  409. {
  410. #if defined CONSOLE || defined LINUX
  411. printf("内存分配失败\n");
  412. #endif
  413. #ifdef _WIN32
  414. closesocket(sockfd);
  415. #else
  416. close(sockfd);
  417. #endif
  418. return -1;//outof memory
  419. }
  420. #ifdef _WIN32
  421. memset(pszBuffer,0,HTTP_RECV_BUFFER_SIZE);
  422. #else
  423. bzero(pszBuffer,HTTP_RECV_BUFFER_SIZE);
  424. #endif
  425. while(1)
  426. {
  427. #ifdef _WIN32
  428. len = recv(sockfd,szRecvBuffer,RECV_BUFF_SIZE,0);
  429. #else
  430. len = recv(sockfd,szRecvBuffer,RECV_BUFF_SIZE,MSG_WAITALL);
  431. #endif
  432. if(len == 0)
  433. {
  434. #if defined CONSOLE || defined LINUX
  435. printf("接收数据超时,超时时间:%d s\n",net_timeout);
  436. #endif
  437. #ifdef _WIN32
  438. closesocket(sockfd);
  439. #else
  440. close(sockfd);
  441. #endif
  442. free(pszBuffer);
  443. return -1;//接收数据超时
  444. }
  445. if(len < 0 )
  446. {
  447. #if defined CONSOLE || defined LINUX
  448. printf("接收数据错误,recv返回值:%d \n",len);
  449. #endif
  450. #ifdef _WIN32
  451. closesocket(sockfd);
  452. #else
  453. close(sockfd);
  454. #endif
  455. free(pszBuffer);
  456. return -1;//timeout
  457. }
  458. //printf("%s",szBuffer);
  459. total_recv_len += len;
  460. if(total_recv_len > (HTTP_RECV_BUFFER_SIZE-1) )
  461. {
  462. #if defined CONSOLE || defined LINUX
  463. printf("接收数据buffer空间不足,当前buffer大小:%d B\n",HTTP_RECV_BUFFER_SIZE-1);
  464. #endif
  465. #ifdef _WIN32
  466. closesocket(sockfd);
  467. #else
  468. close(sockfd);
  469. #endif
  470. free(pszBuffer);
  471. return -1;//not enough buffer size
  472. }
  473. strcat(pszBuffer,szRecvBuffer);
  474. if(len < RECV_BUFF_SIZE)
  475. {
  476. pHttpHeaderEnd = strstr(pszBuffer,"\r\n\r\n");
  477. if(pHttpHeaderEnd )
  478. {
  479. if(!total_contents_len)///http返回头中标示的内容大小
  480. {
  481. total_contents_len = parse_respose_contents_length(pszBuffer);
  482. }
  483. pHttpHeaderEnd += 4;
  484. //如果接收到的内容长度已经达到http返回头中标示的内容大小,停止接收
  485. if( total_contents_len && strlen( pHttpHeaderEnd) >= total_contents_len )
  486. break;
  487. pHttpHeaderEnd = NULL;
  488. }
  489. }
  490. #ifdef _WIN32
  491. memset(szRecvBuffer,0,sizeof(szRecvBuffer));
  492. #else
  493. bzero(szRecvBuffer,sizeof(szRecvBuffer));
  494. #endif
  495. len = 0;
  496. }
  497. if(strcmp(pszBuffer,"") == 0)
  498. {
  499. #ifdef _WIN32
  500. closesocket(sockfd);
  501. #else
  502. close(sockfd);
  503. #endif
  504. free(pszBuffer);
  505. return -1;//recv data error
  506. }
  507. //printf("%s\n",szBuffer);
  508. * contents = NULL;
  509. re = parse_response_http_header(pszBuffer,contents);
  510. if( re != 0 || !(*contents))
  511. {
  512. if(*contents)
  513. {
  514. free(*contents);
  515. }
  516. #if defined CONSOLE || defined LINUX
  517. printf("分析服务器返回内容失败.返回内容为:\n%s\n",pszBuffer);
  518. #endif
  519. #ifdef _WIN32
  520. closesocket(sockfd);
  521. #else
  522. close(sockfd);
  523. #endif
  524. free(pszBuffer);
  525. if( -401 == re)
  526. return -1;//用户名/密码无效
  527. return -1;//unknown error
  528. }
  529. #ifdef _WIN32
  530. closesocket(sockfd);
  531. #else
  532. close(sockfd);
  533. #endif
  534. free(pszBuffer);
  535. #ifdef _WIN32
  536. WSACleanup();
  537. #endif
  538. return 0;
  539. }