STM32 W5500 Http Client Get請求 下載bin文件思路和實現

這兩天在做STM32 W5500通過HTTP GET請求的方式下載bin文件,以實現OTA在線升級,到網上查了一圈,發現並沒有很多有效的信息和資料。於是我就實現了一下,把思路和實現過程分享出來。

實現W5500文件下載的幾個前提:

1、STM32 W5500的基礎配置,使得 電腦端的CMD命令窗口能夠PING通W5500,《STM32F103RC驅動W5500入網,並可ping通》

2、STM32 W5500的TCP Client收發數據測試沒有問題,《STM32F1 W5500 TCP Client 迴環測試》

3、一個可用的文件服務器,文件的URL地址放到瀏覽器的地址欄後,可以正常下載文件。

4、對HTTP協議有基本的認識。

文件的URL地址放到瀏覽器的地址欄後,通過Wireshark工具,先看看文件下載的過程,都發生可一些什麼

下載請求的header信息,包括以下幾個關鍵信息:

1、請求方式+接口名稱+協議和協議版本(GET /file/FLASH_OPER.bin_1.1.3 HTTP/1.1\r\n)

2、文件服務器主機IP+端口 (舉例:192.168.1.105:8888\r\n)

3、連接狀態(Connection: Keep-Alive\r\n)

4、請求端名稱(User-Agent: W5500\r\n)

5、請求端接收的編碼(Accept-Encoding: gzip,deflate\r\n)

6、請求頭結束標記(\r\n)

以上是請求的分析,文件服務器返回的信息如下:

從文件服務器返回來的數據分析,報文和文件內容不是一次性傳輸的,是流的方式多次傳輸的。

返回的報文,包含以下幾個關鍵點:

1、返回的狀態碼(HTTP/1.1 200\r\n)

2、接收範圍 字節流(Accept-Ranges: bytes\r\n)

3、內容長度(Content-Length: 3904\r\n)

4、頭結束標記(\r\n)

5、字節流Data長度和Content-Length是相等的。

以上是基於瀏覽器下載文件的過程分析的,那麼STM32 W5500下載文件的過程,模擬瀏覽器下載文件,從理論上來講也是會實現的。

HTTP協議是在TCP的基礎上封裝的協議,W5500的TCP Client端與服務端簡歷連接的過程在此不描述。

STM32 W5500下載文件有幾個難點:

1、解析文件服務器返回的報文,判斷返回碼是否爲200,只有200才代表成功;判斷是否爲字節流;解析出文件的大小(長度)

2、文件字節流和返回報文的分割,以及緩存數組的數據反覆搬運(畢竟W5500 一次最多可以收2Kbytes 的數據)。

3、判斷文件下載是否完成的依據。

STM32 W5500請求和接收的過程:

即發送一次請求報文後,不斷接收文件服務器返回的信息,結果是要麼接收到文件字節流和 Content-Length的相等,文件下載成功,要麼各種原因失敗(連接超時,讀取超時,返回碼不正確,不是字節流等)。

好像該講的都講了,下面給出STM32 W5500 Http GET方式下載文件的代碼(我測試下載 4k/13k/67k 左右的文件都可以),例程中打印了 各個分支的log信息,將文件下載的內容通過串口DMA的方式打印出來。(工程可以看我的 基礎配置文章下載,再將這個主測試函數替換)

#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif

#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif

#ifndef __Z_HARDWARE_LED_H
#define __Z_HARDWARE_LED_H
#include "z_hardware_led.h"
#endif

#ifndef __Z_HARDWARE_SPI_H
#define __Z_HARDWARE_SPI_H
#include "z_hardware_spi.h"
#endif

#ifndef __W5500_H
#define __W5500_H
#include "w5500.h"
#endif

#ifndef __SOCKET_H
#define __SOCKET_H
#include "socket.h"
#endif

#ifndef __W5500_CONF_H
#define __W5500_CONF_H
#include "w5500_conf.h"
#endif

#ifndef __DHCP_H
#define __DHCP_H
#include "dhcp.h"
#endif

#ifndef __Z_HARDWARE_USART2_H
#define __Z_HARDWARE_USART2_H
#include "z_hardware_usart2.h"
#endif

#include <stdlib.h>

void log_net(char *log);	

void func_pack_http_get_download_header(u8* buff, u16 size_buff, u8* srv_ip, u16 srv_port, char* interface);
u8 func_http_get_download_file(u8 sockno, u8* srv_ip, u16 srv_port, char* interface, u16 timeout_ms, u8* buff, u16 size_buff);
u8 func_analysis_http_download_header(u8* buffer, u16 len_buf, u8 *resp_code, u8 *is_stream, u32 *cont_len);

u8 buf[2048];
int main(void)
{
	
	u8 mac[6]={0, };
	DHCP_Get dhcp_get;
	
	//FIXME input your server ip to download file
	u8 srv_ip[] = {192, 168, 1, 105};
	u16 srv_port = 8888;
	
	
	systick_configuration();
	init_led();
	init_hardware_usart2_dma(115200);
	
	init_system_spi();
	func_w5500_reset();	
	
	getMacByLockCode(mac);
	setSHAR(mac);
	
	sysinit(txsize, rxsize);
	setRTR(2000);
  setRCR(3);
	
	//USART DMA problem: 2 bytes missing
	func_usart2_dma_send_bytes(mac, 2);
	delay_ms(100);
	
	//DHCP
	for(;func_dhcp_get_ip_sub_gw(1, mac, &dhcp_get, 500) != 0;);	
	if(func_dhcp_get_ip_sub_gw(1, mac, &dhcp_get, 500) == 0)
	{
		setSUBR(dhcp_get.sub);
		setGAR(dhcp_get.gw);
		setSIPR(dhcp_get.lip);
		close(1);
		
		log_net("0");
	}
	
	memset(buf, 0 , sizeof(buf));
	func_http_get_download_file(0, srv_ip, srv_port, "/file/FLASH_OPER.bin_1.1.3", 5000, buf, sizeof(buf));
	
	for(;;)
	{
		func_led1_toggle();
		delay_ms(500);	
	}
}

u8 func_http_get_download_file(u8 sockno, u8* srv_ip, u16 srv_port, char* interface, u16 timeout_ms, u8* buff, u16 size_buff)
{
	u16 local_port = 60000, len = 0, cnt = 0;
	u8 sendok = 0, recv_start = 0;
	
	u8 cache[2048];
	u16 cache_cont_len;
	u32 cont_len, download_cont_len;
	
	func_pack_http_get_download_header(buff, size_buff, srv_ip, srv_port, interface);
	
//	setkeepalive(sockno);//auto
	IINCHIP_WRITE(Sn_KPALVTR(sockno),0x00);//manual
	
	memset(cache, 0 , sizeof(cache));
	cache_cont_len = 0;
	
	for(;;)
	{
		switch(getSn_SR(sockno))
		{
			case SOCK_INIT:
					connect(sockno, srv_ip, srv_port);
			break;
			case SOCK_ESTABLISHED:
			{
				if(sendok == 0)
				{
					log_net("1");
					
					len = strlen((char *)buff);
					send(sockno, buff, len);
					sendok ++;
					if(getSn_IR(sockno) & Sn_IR_CON)   					
					{
						setSn_IR(sockno, Sn_IR_CON);
						break;
					}
				}
				
				len = getSn_RX_RSR(sockno);
				if(len > 0)
				{
					cnt = 0;
					
					memset(buff, 0, size_buff);
					len = recv(sockno, buff, len);
					if(recv_start == 0)
					{
						u8 res, resp_code, isstream;
						
						log_net("2");
						
						if(cache_cont_len + len <= sizeof(cache))
						{
							log_net("3");
							memcpy(cache+cache_cont_len, buff, len);
							cache_cont_len += len;
							
							memset(buff, 0, size_buff);
							len = 0;
						}
						else
						{
							u32 len_copy;
							log_net("4");
							
							len_copy = sizeof(cache) - cache_cont_len;
							memcpy(cache+cache_cont_len, buff, len_copy);
							memcpy(buff, buff + len_copy, len - len_copy);
							memset(buff + (len - len_copy), 0, size_buff - (len - len_copy));
							len = len - len_copy;
							cache_cont_len = sizeof(cache);
							
						}							
						
						res = func_analysis_http_download_header(cache, cache_cont_len, &resp_code, &isstream, &cont_len);
						if(res == 0)
						{
							if(resp_code != 200 || isstream != 1)//response code 200, must be iostream
							{
								log_net("22");
								return 3;// response not ok
							}
							recv_start++;
							
							//logic -> file size should be no more than 108 Kbytes
							if(cont_len > 108*1024)
							{
								log_net("23");
								return 4;
							}
							
							//if download file done
							{
								u32 tmp_len;
								u8* pos_header;
								pos_header = (u8*)strstr((char*)cache, "\r\n\r\n");
								//remove header, save file byte to cache
								tmp_len = cache_cont_len - (pos_header + 4 - cache);
								memcpy(cache, pos_header + 4, tmp_len);
								memset(cache + tmp_len, 0 , sizeof(cache) - tmp_len);
								cache_cont_len = tmp_len;
								download_cont_len = cache_cont_len;
								
								if(len > 0)
								{
									if(cache_cont_len + len <= sizeof(cache))
									{
										log_net("5");
										memcpy(cache+cache_cont_len, buff, len);
										cache_cont_len += len;										
										download_cont_len += len;
										
										memset(buff, 0, size_buff);
										len = 0;
									}
									else
									{
										u32 len_copy;
										log_net("6");
										
										len_copy = sizeof(cache) - cache_cont_len;
										memcpy(cache+cache_cont_len, buff, len_copy);
										memcpy(buff, buff + len_copy, len - len_copy);
										memset(buff + (len - len_copy), 0, size_buff - (len - len_copy));
										len = len - len_copy;
										cache_cont_len = sizeof(cache);
										
										//TODO write to FLASH backup
										func_usart2_dma_send_bytes(cache, cache_cont_len);
										memset(cache, 0, cache_cont_len);
										cache_cont_len = 0;
										memcpy(cache+cache_cont_len, buff, len);
										cache_cont_len = len;
										download_cont_len = sizeof(cache) + cache_cont_len;
										
									}
								}
								
								if(download_cont_len == cont_len)// small file download done
								{
									log_net("7");
									func_usart2_dma_send_bytes(cache, cache_cont_len);
									//TODO write to FLASH backup and record
									
									disconnect(sockno);
									return 0;//download file done
								}
								
							}								
						}
					}
					else
					{
						IINCHIP_WRITE(Sn_CR(sockno),Sn_CR_SEND_KEEP);//keep-alive manual
						
						//file size is big, write to FALSH several times
						download_cont_len += len;
						if(cache_cont_len + len <= sizeof(cache))
						{
							log_net("8");
							memcpy(cache+cache_cont_len, buff, len);
							cache_cont_len += len;
							memset(buff, 0, size_buff);
							len = 0;
						}
						else
						{
							u32 len_copy;
							log_net("9");
							len_copy = sizeof(cache) - cache_cont_len;
							memcpy(cache+cache_cont_len, buff, len_copy);
							memcpy(buff, buff + len_copy, len - len_copy);
							memset(buff + (len - len_copy), 0, size_buff - (len - len_copy));
							len = len - len_copy;
							cache_cont_len = sizeof(cache);
							
							//TODO write to FLASH backup
							func_usart2_dma_send_bytes(cache, cache_cont_len);
							memset(cache, 0, cache_cont_len);
							cache_cont_len = 0;
							memcpy(cache+cache_cont_len, buff, len);
							cache_cont_len = len;
						}						
						
						//if donwload file done
						if(download_cont_len == cont_len)
						{
							log_net("10");
							//TODO write to FLASH backup and record
							func_usart2_dma_send_bytes(cache, cache_cont_len);
							disconnect(sockno);
							return 0;//download file done
						}							
					}
				}
			}			
			break;
			case SOCK_CLOSE_WAIT:
					close(sockno);
					log_net("21");
					return 2;//read timeout
			case SOCK_CLOSED:
					close(sockno);
					sendok = 0;
					recv_start = 0;
					if(local_port > 65400)
					{
						local_port = 60000;
					}
					socket(sockno, Sn_MR_TCP, local_port++, Sn_MR_ND);
			break;
		}
		
		cnt ++;
		if(cnt >= timeout_ms)
		{
			log_net("20");
			return 1;//connect timeout
		}
		delay_ms(1);
	}
}

u8 func_analysis_http_download_header(u8* buffer, u16 len_buf, u8 *resp_code, u8 *is_stream, u32 *cont_len)
{
	*resp_code = 0;
	*is_stream = 0;
	*cont_len = 0;
	if(strstr((char*)buffer, "\r\n\r\n") != NULL)//response header ok
	{
		char *p1, *p2;
		
		p1 = strstr((char*)buffer, "HTTP/1.1 200");	
		if(p1 != NULL)// io stream
		{
			*resp_code = 200;
		}		
		
		p1 = strstr((char*)buffer, "Accept-Ranges: bytes");	
		if(p1 != NULL)// io stream
		{
			*is_stream = 1;
		}
		
		p1 = strstr((char*)buffer, "Content-Length: ");										
		if(p1 != NULL)
		{
			p2 = strstr(p1, "\r\n");
			if(p2 != NULL)
			{
				char str_len[8] = {0,};
				memcpy(str_len, p1 + strlen("Content-Length: "), p2 - p1 - strlen("Content-Length: "));
				*cont_len = atoi(str_len);
				return 0;
			}
		}
	}
	return 1;
}

void log_net(char *log)
{
	u8 logsrv_ip[] = {192, 168, 1, 105};
	socket(2, Sn_MR_UDP, 7890, 0x00);
	sendto(2, (u8*)log, strlen(log), logsrv_ip, 9876);
}

void func_pack_http_get_download_header(u8* buff, u16 size_buff, u8* srv_ip, u16 srv_port, char* interface)
{
	u8 tmp[64];
	memset(buff, 0, size_buff);
	//header
	memcpy(buff, "GET ", strlen("GET "));
	strcat((char *)buff, interface);
	strcat((char *)buff, " HTTP/1.1\r\n");
	memset(tmp, 0 , sizeof(tmp));
	sprintf((char*)tmp, "Host: %d.%d.%d.%d:%d\r\n", srv_ip[0], srv_ip[1], srv_ip[2], srv_ip[3], srv_port);
	strcat((char *)buff, (char*)tmp);
	strcat((char *)buff, "Connection: Keep-Alive\r\n");
	strcat((char *)buff, "User-Agent: W5500\r\n");
	strcat((char *)buff, "Accept-Encoding: gzip,deflate\r\n");
	strcat((char *)buff, "\r\n");
}

最後,附上我測試的效果圖:

串口打印的字節數和 Content-Length的長度一致,並和原bin文件做了對比,內容一致。

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