浏览器更新HTTP服务器图片并显示
0赞1 实验目的
浏览器登录W5500服务器显示一张图片,再通过文件上传一个图片更新服务器中的图片;刷新浏览器显示更新后的图片。
2 实验总体设计
实验主要分两个部分:1:浏览器申请图片数据以及上传图片数据:;
2:W5500服务器接收数据以及处理HTTP请求和数据。
3 实验原理
该实现的原理过程就是先利用取模软件获取一个图片的十六进制数据数组,然后W5500服务器将数据写入FLASH里面并记下写入的位置,主函数循环检测浏览器客户端是否发送TCP连接请求,如果检测到请求报文服务器就会与客户端建立连接并传输第一包数据;第一包数据内包含一段有浏览器执行的HTML代码,浏览器获取第一包数据后开始执行HTML代码,即浏览器向服务器发送第一张图片数据请求,服务器返回图片的数据并由浏览器显示在网页上面,此时的图片是我们第一次写入FLASH的图片数据,而服务器返回数据的过程就是根据浏览器的第二个请求中的URL找到图片并从FLASH读取出来放在HTTP响应报文中发送给浏览器,浏览器再显示在网页。
图片更新的部分就是浏览器显示第一张图片后会根据我们编写的HTML代码在网页上显示一个选择按钮以及一个文本框,该部分是HTML利用FORM表单来上传文件到我们的服务器;我们在这里使用的就是将我们的图片以文件的形式上传到服务器,服务器根据表单的请求方式以及路径来接收图片数据并将数据写入内部FLASH,由于我们使用的W5500开发板的内部FLASH是256K(经过测试300K的照片也能写入,原因可能是内部FLASH实际上是512K的,这个可能是ST的问题在这里不做解释)的,所有我们把更新的图片直接覆盖掉原来的图片数据;这样也比较方便我们获取数据;数据写入后我们会在给浏览器的响应报文中加入一段HTML以及JAVASCRIPT的代码,以便于让我们的浏览器刷新页面并从新发送图片请求来获取更新后的图片并显示在页面。
4 内部FLASH操作
4.1 STM32RCT6内部FLASH
STM32F103RCT6为大容量产品,其FLASH的大小是256K,所以一共可以分为128页,每页得大小是2K,地址范围0X0800 0000-0X0803 FFFF;内部FLASH是存放我们下载的程序的,所以要把内部FLASH当做存储器来使用的话就要考虑写入地址与存放代码的地址是否重合,这里我把前64页放我的代码,从64页开始用来存放我写入的数据;即开始写入或读取的地址是0X0802 0000。(根据这里的解释我们每次更新的图片的大小应该小于64K,但是我上面说过了实际测试的时候是可以上传300K左右的数据的)
内部FLASH读写代码
uint16_t Flash_Write_Without_check(uint32_t iAddress, uint8_t *buf, uint16_t iNumByteToWrite)
{
uint16_t i=0;
while((i < iNumByteToWrite) && (FLASHStatus == FLASH_COMPLETE))
{
FLASHStatus = FLASH_ProgramHalfWord(iAddress, *(uint16_t*)buf);
i = i+2;
iAddress = iAddress + 2;
buf = buf + 2;
}
return iNumByteToWrite;
}
int Flash_Write(uint32_t iAddress, uint8_t *buf, uint32_t iNbrToWrite)
{
uint32_t secpos;
uint32_t iNumByteToWrite = iNbrToWrite;
uint16_t secoff;
uint16_t secremain;
uint16_t i = 0;
static uint8_t tmp[FLASH_PAGE_SIZE];
FLASH_UnlockBank1();
secpos=iAddress & (~(FLASH_PAGE_SIZE -1 )) ;//扇区地址
secoff=iAddress & (FLASH_PAGE_SIZE -1); //在扇区内的偏移
secremain=FLASH_PAGE_SIZE-secoff; //扇区剩余空间大小
if(iNumByteToWrite<=secremain) secremain = iNumByteToWrite;//不大于4096个字节
while(1)
{
ReadFlashNBtye(secpos, tmp, FLASH_PAGE_SIZE); //读出整个扇区
for(i=0;i
{
if(tmp[secoff+i]!=0XFF)break; //需要擦除
}
if(i
{
FLASHStatus = FLASH_ErasePage(secpos); //擦除这个扇区
if(FLASHStatus != FLASH_COMPLETE)
return -1;
for(i=0;i
{
tmp[i+secoff]=buf[i]; //复制
}
Flash_Write_Without_check(secpos ,tmp ,FLASH_PAGE_SIZE);//写入整个扇区
}
else
{
Flash_Write_Without_check(iAddress,buf,secremain);//写已经擦除了的,直接写入扇区剩余区间.
}
if(iNumByteToWrite==secremain) //写入结束了
break;
else
{
secpos += FLASH_PAGE_SIZE;
secoff = 0;//偏移位置为0
buf += secremain; //指针偏移
iAddress += secremain;//写地址偏移
iNumByteToWrite -= secremain; //字节数递减
if(iNumByteToWrite>FLASH_PAGE_SIZE) secremain=FLASH_PAGE_SIZE;//下一个扇区还是写不完
else secremain = iNumByteToWrite; //下一个扇区可以写完了
}
}
FLASH_LockBank1();
return iNbrToWrite;
}
int ReadFlashNBtye(uint32_t ReadAddress, uint8_t *ReadBuff, uint32_t ReadNum)
{
int DataNum=0; //定义一个变量记录读取的个数
while(DataNum
{
*(ReadBuff+DataNum)=*(__IO uint8_t*)ReadAddress++; //读一个数据地址加一
DataNum++;
}
return DataNum;
}
4.2 HTML部分代码
#define INDEX_HTML ""\
""\
""\
"
"\
""\
""\
"
"\
"
This is a test
"\
""\
"
Pitcure Update
"\
"
"\
"
"\
"
"\
"
"\
"
"\
4.3 HTTP状态机
该部分是循环检测HTTP请求,获取请求后经过解析函数获取请求头信息以及URL和数据的类型与长度,再根据这些数据来选择执行下一步操作,例如接收或者发送图片数据;
void do_https(void)
{
uint8 ch=SOCK_HTTPS;
uint16 len;
st_http_request *http_request;
memset(rx_buf,0x00,MAX_URI_SIZE);
http_request = (st_http_request*)rx_buf;
switch(getSn_SR(ch))
{
case SOCK_INIT:
listen(ch);
break;
case SOCK_LISTEN:
break;
case SOCK_ESTABLISHED:
if(getSn_IR(ch) & Sn_IR_CON)
{
setSn_IR(ch, Sn_IR_CON);
}
if ((len = getSn_RX_RSR(ch)) > 0)
{
len = recv(ch, (uint8*)http_request, len);
*(((uint8*)http_request)+len) = 0;
proc_http(ch, (uint8*)http_request);
disconnect(ch);
}
break;
case SOCK_CLOSE_WAIT:
if ((len = getSn_RX_RSR(ch)) > 0)
{
len = recv(ch, (uint8*)http_request, len);
*(((uint8*)http_request)+len) = 0;
proc_http(ch, (uint8*)http_request);
}
disconnect(ch);
break;
case SOCK_CLOSED:
socket(ch, Sn_MR_TCP, 80, 0x00);
break;
default:
break;
}
}
4.4 HTTP响应部分
void proc_http(SOCKET s, uint8 * buf)
{
int8* name;
int8 req_name[32]={0x00,};
unsigned long file_len=0;
uint16 send_len=0;
uint8* http_response;
int8 sub[10],numbuff[5];
uint32 content_len=0,rx_len=0;
uint16 fw_offset = 0;
uint16 wr_len = 0;
uint32 upload_file_len=0;
uint32 n_pages;
uint32 n_erased;
uint8 remain_len = 0;
uint16 i=0;
uint16 tmp_len=0;
uint8 remain_buf[3]={0xff,};
uint32 flash_dest =STARTADDR;
uint32 calc_checksum = 0;
st_http_request *http_request;
memset(tx_buf,0x00,MAX_URI_SIZE);
http_response = (uint8*)rx_buf;
http_request = (st_http_request*)tx_buf;
parse_http_request(http_request, buf);
switch (http_request->METHOD)
{
case METHOD_ERR :
memcpy(http_response, ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
send(s, (uint8 *)http_response, strlen((int8 const*)http_response));
break;
case METHOD_HEAD:
case METHOD_GET:
name = http_request->URI;
if(strcmp(name,"/index.htm")==0||strcmp(name,"/")==0||(strcmp(name,"/index.html")==0))
{
file_len = strlen(INDEX_HTML);
make_http_response_head((uint8*)http_response, PTYPE_HTML,file_len);
send(s,http_response,strlen((char const*)http_response));
send_len=0;
while(file_len)
{
if(file_len>1024)
{
if(getSn_SR(s)!=SOCK_ESTABLISHED)
{
return;
}
send(s, (uint8 *)INDEX_HTML+send_len, 1024);
send_len+=1024;
file_len-=1024;
}
else
{
send(s, (uint8 *)INDEX_HTML+send_len, file_len);
send_len+=file_len;
file_len-=file_len;
}
}
}
else if(strcmp(name,"/img.jpg")==0)
{
send_img_jpg_page(s, http_response);
}
else if(strcmp(name,"/w5500.js")==0)
{
memset(tx_buf,0,MAX_URI_SIZE);
make_basic_config_setting_json_callback(tx_buf,ConfigMsg);
sprintf((char*)http_response,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length:%d\r\n\r\n%s",strlen(tx_buf),tx_buf);
send(s, (u_char *)http_response, strlen((char const*)http_response));
}
break;
case METHOD_POST:
mid(http_request->URI, "/", " ", req_name);
if(strcmp(req_name,"pitcure.cgi")==0)
{
mid((int8*)http_request->URI,"boundary=", "\r\n", (int8*)boundary);//boundary=
#ifdef DEBUG_HTTP
printf("boundary=%s\r\n",boundary);
#endif
memset(sub,0,10);
mid((char*)http_request->URI,"Content-Length: ","\r\n",sub);
content_len=atoi32(sub,10);
#ifdef DEBUG_HTTP
printf("content_len=%d\r\n",(uint32_t)content_len);
ee_WriteBytes((uint8_t *)sub,(uint16_t)STARTADDRDATA,strlen((char *)sub));
delay_ms(5);
memset(numbuff,0,5);
numbuff[0]=strlen((char *)sub);
ee_WriteBytes((uint8_t *)numbuff,(uint16_t)STARTADDRDATA+2,1);
#endif
if(content_len<0xffff7)//长度小于1M
{
while(rx_len!=content_len)
{
tmp_len=getSn_RX_RSR(s);
if(tmp_len>0)
{
if(tmp_len>1460) tmp_len=1460;
tmp_len=recv(s, (uint8*)rx_buf, tmp_len);
if(rx_len==0)//the first packet with header
{
int8* pos1;
int8* pos2;
uint16 hdr_len;
#ifdef DEBUG_HTTP
printf("http request=%d\r\n",strlen((int8*)rx_buf));
#endif
pos1=strstr((int8*)rx_buf,(int8*)boundary);
#ifdef DEBUG_HTTP
printf("pos1=%d, %s\r\n", strlen(pos1),pos1);
#endif
pos2=strstr((int8*)rx_buf,"\r\n\r\n");
hdr_len=strlen((int8*)boundary)+6+2+(pos2-pos1)+4+2;
upload_file_len=content_len-hdr_len;
#ifdef DEBUG_HTTP
printf("pos2=%d,boundarylen=%d,uploadfilelen=%d\r\n",pos2-pos1,strlen((int8*)boundary),upload_file_len);
#endif
fw_offset = pos2-pos1+4+2;
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
//erase required page
n_pages = FLASH_PagesMask(upload_file_len);
for(n_erased = 0; n_erased < n_pages; n_erased++)
{
FLASH_ErasePage(STARTADDR + 0x400 * n_erased);
IWDG_ReloadCounter();//feed wdg
}
wr_len = tmp_len-fw_offset;
//make a buf with lengh of multiful 4 and save remain bytes
remain_len = wr_len % 4;
memcpy(tmp_buf,&rx_buf[fw_offset],wr_len-remain_len);
wr_len = wr_len - remain_len;//real length
if(remain_len!=0)
{
memcpy(remain_buf,&rx_buf[tmp_len-remain_len],remain_len);
}
}else{
if(rx_len+tmp_len==content_len)//the last packet that includes boundary
{
wr_len = tmp_len - strlen((int8*)boundary) - 8;
}else{
wr_len = tmp_len;
}
fw_offset=0;
//make buffer
if(remain_len)
{
memcpy(tmp_buf,remain_buf,remain_len);
}
if(wr_len+remain_len>1460)
{
memcpy(&tmp_buf[remain_len],rx_buf,wr_len-remain_len);
//save remain bytes
memcpy(remain_buf,&rx_buf[tmp_len-remain_len],remain_len);
}else{
memcpy(&tmp_buf[remain_len],rx_buf, wr_len);
memset(&tmp_buf[wr_len+remain_len],0xff,3);
wr_len=wr_len+remain_len;
}
}
//program
for(i = 0; i
{
FLASH_ProgramWord(flash_dest, *(uint32*)((uint32)(tmp_buf + i)));
//checksum
calc_checksum += *(uint32*)(flash_dest);
flash_dest += 4;
}
rx_len+=tmp_len;
#ifdef DEBUG_HTTP
printf("len=%d, rx len=%d, wr len=%d, @x\r\n",tmp_len, rx_len, wr_len, flash_dest);
#endif
//program over
if(rx_len==content_len)
{
//lock flash again
FLASH_Lock();
make_delay_jump_msg(tx_buf,"The pitcure is updated...",5);
sprintf((char *)http_response,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length:%d\r\n\r\n%s",strlen(tx_buf),tx_buf);
send(s, (uint8 *)http_response, strlen((char *)http_response));
}
}
}
}// while 结束
}//end of if(firmware upload)
break;
default :
break;
}
}
4.5 发送图片给浏览器
该部分是在服务器接收到浏览器的图片请求后从FLASH读取图片数据然后发送给浏览器;浏览器显示。
void send_img_jpg_page(SOCKET s, uint8 * buf)
{
uint32 file_len = 0;
uint32 send_len = 0,addr=0;
uint16 len = 0,lenth=0;
uint8 ReadBuff[ReadBuffsize];
uint8 img_size[10],numbuff[5];;
memset(numbuff,0,5);
ee_ReadBytes(numbuff,(uint16)STARTADDRDATA+2,1);
printf("num=%d",numbuff[0]);
memset(img_size,0,10);
ee_ReadBytes(img_size,(uint16)STARTADDRDATA,numbuff[0]);
delay_ms(10);
file_len=atoi32((char *)img_size,10);
printf("file_len=%d\r\n",(int)file_len);
make_http_response_head((unsigned char*)buf,PTYPE_JPEG, (u_long)file_len);
send(s, buf, strlen((char const*)buf));
while(file_len!=0)
{
memset(ReadBuff,0,ReadBuffsize);
lenth=ReadFlashNBtye(STARTADDR+addr,ReadBuff,ReadBuffsize);
delay_ms(5);
printf("%d",lenth);
send_len=0;
if(file_len)
{
if(file_len>1024)
{
if(getSn_SR(s)!=SOCK_ESTABLISHED)
{
return;
}
len=send(s, (uint8 *)ReadBuff, 1024);
delay_ms(5);
send_len+=len;
file_len-=len;
addr+=len;
printf("%d",lenth);
}
else
{
send(s, (uint8 *)ReadBuff, file_len);
send_len+=file_len;
file_len-=file_len;
printf("file_len=%d",(int)file_len);
}
}
}
return;
}
5 总结
该实验其实是一个比较简单的实验;只要会写简单的HTML网页描述语言来告诉浏览器执行图片显示以及上传的操作,然后服务器根据接收到的HTTP报文来选择读取或写入图片数据并将数据发送给浏览器就可以了。最主要的就是我们的W5500开发板的使用,通过调用开发板自带的SCOKET接口函数来接收和发送HTTP报文,然后在调用解析函数解析报文再根据解析的内容选择执行响应的操作,在W5500开发板中这些关于以太网的接口函数都已经编写好了,我们只需要调用这些函数然后将接收到的数据或者存放在内存的数据发送到浏览器就可以了。