支持ipv4与ipv6双协议栈的web服务器设计-尊龙凯时平台入口

支持ipv4与ipv6双协议栈的web服务器设计-尊龙凯时平台入口
当前位置 : 尊龙凯时平台入口 - 工作研究 - 工作研究 - 网络管理 - 正文

支持ipv4与ipv6双协议栈的web服务器设计


发布时间:2018年09月17日

  嵌入式web技术因其跨平台的特点得到了广泛的应用[1]。用户只需要登录浏览器即可实现对嵌入式设备状态的查看与控制。随着物联网技术的发展,网络地址的需求量剧增,未来ipv6将在嵌入式领域发挥巨大的作用[2]。然而,目前ipv4技术还无法完全被新的ipv6技术所取代,这使得现有的应用程序必须同时兼容ipv4地址与ipv6地址。如何在嵌入式web服务器中同时使用ipv4地址和ipv6地址则成为了嵌入式领域中的一个重要问题[3]。本文从实际应用出发,设计了一个能够同时支持ipv4与ipv6双协议栈的嵌入式web服务器。

  基本原理

  嵌入式web服务器的基本原理是:用户在浏览器中输入嵌入式设备的ip地址,随后浏览器向嵌入式web服务器发出http请求,嵌入式web服务器针对该请求作出http响应,最后浏览器对响应的内容进行解析,以网页的形式呈现给用户。嵌入式web服务器原理如图1所示。

  http请求和响应的报文是通过网络进行传输的。浏览器向web服务器请求网页数据的具体流程如图2所示[4]。

  浏览器和web服务器之间是通过tcp协议进行通信的,tcp协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。web服务器监听特定的网络端口,当浏览器向web服务器发出请求时,两者之间通过tcp协议建立连接,然后传输http请求报文和http响应报文。web服务器实际上也是一个tcp服务器,典型的tcp服务器的架构如图3所示。

  针对现代农业物联网技术的应用需求,为了使系统中的嵌入式web服务器在支持ipv4地址访问的基础上,还能支持ipv6地址的访问,本文按照图3所示的典型tcp服务器架构设计了一个同时支持ipv4地址与ipv6地址访问请求的嵌入式web服务器,具体实现过程如下。

  设计实现

  为了进行浏览器与web服务器之间的通信,首先就要建立网络连接,采用的方式为socket通信。socket又称为套接字,应用程序通常情况下通过套接字向网络发出请求或者应答网络请求[5]。web服务器需要为每一个与其连接的客户端分配一个socket套接字,作为相互通信的基础。传统的ipv4网络服务器建立socket描述符的代码如下所示:

  structsockaddr_inserver_addr;/*服务器端ip地址*/

  structsockaddr_inclient_addr;/*客户端ip地址*/

  sockfd=socket(af_inet,sock_stream,0);

  bzero(&server_addr,sizeof(structsockaddr_in));

  server_addr.sin_family=af_inet;

  server_addr.sin_addr.s_addr=htonl(inaddr_any);

  server_addr.sin_port=htons(80);

  上述代码中,第一行和第二行分别定义了服务器和客户端的套接字地址变量。第三行代码的作用为服务器端建立socket描述符,af_inet表明服务器使用的是ipv4协议族,而sock_stream表明使用的是tcp协议。第四行代码是为了清空sockaddr_in结构体变量,为填充内容做好准备。第五行是为sockaddr_in结构体变量填入ipv4协议族。第六行填入inaddr_any表明该服务器可以接收任意ip地址的数据,即绑定到所有的ip地址。第七行是为sockaddr_in结构体变量填入80端口号,80端口号为web服务器中的htto专用的端口号。

  参照ipv4服务器建立socket描述符的过程,为了实现对ipv6地址的支持,对上述代码进行如下修改:

  structsockaddr_in6server_addr;/*服务器端ip地址*/

  structsockaddr_in6client_addr;/*客户端ip地址*/

  server_socket=socket(pf_inet6,sock_stream,0));

  bzero(&server_addr,sizeof(structsockaddr_in6));

  server_addr.sin6_family=pf_inet6;

  server_addr.sin6_addr=in6addr_any;

  server_addr.sin6_port=htons(8080);

  新的web服务器代码将sockaddr_in结构体更改为sockaddr_in6结构体,而sockaddr_in6结构体的成员如下所示:

  structsockaddr_in6{

  sa_family_tsin6_family;

  in_port_tsin6_port;

  structin6_addrsin6_addr;

  ……

  };

  成员sin6_family表明所使用的地址协议族,pf_inet6表明使用的是ipv6协议族;sin6_addr为web服务器监听的ip地址,将其设为in6addr_any是要接收任意ip地址发送的数据,即“inaddr_any”的ipv6版本;成员sin6_port则表明了web服务器所使用的端口,使用8080端口而

  不是80端口的原因是为了防止与嵌入式linux设备上现有的web服务器相冲突。用ipv6建立服务器端的话,即使客户端仍用ipv4的socket连接也可以正常通信,ipv4的地址会被转换成这种地址“::ffff:ipv4地址”,即ipv4映射地址。

  图4给出了浏览器向web服务器发送的http请求报文的格式,其中,url是用户所需的资源。例如,当用户在浏览器地址栏输入“192.168.1.1:8080/index.html”时,http请求报文的请求行为“get/index.htmlhttp/1.1”。从该行中即可得到用户所需的资源信息。设计的get_user_url(unsignedchar*url,unsignedchar*request)函数则可以获得浏览器所需的url。随后,将根据该url搜索相应的资源,并为组合http响应报文做好准备。

  web服务器的主要工作就是组合http响应报文,然后将其发送给请求网页的浏览器。http响应报文的格式如图5所示。

  http请求报文和响应报文的头部字段主要有content-length、content-type等。为了实现http响应报文的组合,本文设计了函数response_by_source(unsignedchar*source,intclient_socket)。该函数首先将构造http响应头部,然后和http响应报文的内容即用户请求的资源进行组合。函数代码如下所示:

  strcpy(response_buf,“http/1.0200ok\r\n”);

  get_mime_type(mime_type,source);

  strcat(response_buf,mime_type);

  sprintf(response_tmp,“content-length:%ld\r\n”,file_size);

  strcat(response_buf,response_tmp);

  strcat(response_buf,“\r\n”);

  第1行的作用为构造http响应报文的状态行,向请求的服务器回应“http/1.0200ok”,表明请求已成功,请求的响应头或数据体将随此响应返回。第2、3行的作用是为了构造头部字段content-type,函数get_mime_type(mime_type,source)的主要作用就是通过用户请求的url得出请求资源的类型。第4行关键字content-length指的是用户请求的资源大小。第5行的作用是把http响应报文头部内容填入数据发送缓冲区中,web服务器将会把数据发送缓冲区中的内容发送至浏览器。第6行为数据发送缓冲区中的内容添加一个空行,因为http响应报文的头部与内容要用一个换行符隔开。

  报文头部content-type表明了http响应报文的内容类型,浏览器将根据内容的类型来进行相应的处理。get_mime_type(unsignedchar*mime_type,unsignedchar*source)的代码如下所示:

  /*功能:根据客户端的请求确定应答的mime类型*/

  voidget_mime_type(unsignedchar*mime_type,unsignedchar*source)

  {

  unsignedchar*pchar=null;/*字符指针*/

  unsignedchartype[20]={0};/*存放source字符串中的type信息*/

  pchar=strrchr(source,‘.’);/*寻找source中最后一个‘.’号

  */

  strcpy(type,pchar);

  if(strncmp(type,“.html”,strlen(type))==0)

  {

  strcpy(mime_type,“content-type:text/html\r\n”);

  }

  elseif(strncmp(type,“.jpg”,strlen(type))==0)

  {

  strcpy(mime_type,“content-type:image/jpeg\r\n”);

  }

  elseif(strncmp(type,“.png”,strlen(type))==0)

  {

  strcpy(mime_type,“content-type:image/png\r\n”);

  }

  return;

  }

  上述代码目前可以对html、jpg和png

  格式的文件进行处理。如果需要对其他类型的文件进行处理,可以再进行适当修改。

  content-length为http响应报文中内容的长度,可以用如下代码进行计算:

  fseek(fp,0l,seek_end);

  file_size=ftell(fp);

  fseek(fp,0l,seek_set);

  计算响应报文内容长度的原理是将文件指针移到文件尾,然后计算出文件尾距离文件头的距离,即是文件的大小;计算结束后还原文件指针的位置。

  在对http响应报文的头部构造完成后,可以先将其进行发送,发送代码如下所示:

  write(client_socket,response_buf,http_header_len);

  这样就可以把http响应报文的头部发送给浏览器。接下来,就要对报文的内容进行发送。发送报文内容部分的代码对发送大文件进行了特殊的处理,首先从文件中读取一定数量的内容,然后将其发送至浏览器。循环往复,直到读到文件尾为止,最后对文件进行关闭操作。代码如下所示:

  do{

  unsignedinti=0;/*用于计数的变量*/

  /*从文件中读取20000个数据项,每个数据项的大小为1个字

  节,即读取20000字节的内容,返回实际读到的字节数*/

  read_count=fread(response_content_buf,1,20000,fp);

  for(i=0;i

  {

  response_buf[i]=response_content_buf[i];

  }

  /*分批发送http应答报文中的内容*/

  if(write(client_socket,response_buf,read_count)==-1)

  {

  fprintf(stderr,“writeerror:%s\n”,strerror(errno));

  exit(1);

  }

  memset(response_buf,0,sizeof(response_buf));

  memset(response_content_buf,0,sizeof(response_content_buf));

  }while(read_count!=0);fclose(fp);

  为了能对多个浏览器同时进行服务,该web服务器还增加了多线程的机制。每当一个浏览器与之建立连接时,web服务器会产生一个线程为其进行服务,确保了服务的实时性。多线程的代码如下所示:

  pthread_ta_thread;

  void*thread_result=null;

  pthread_create(&a_thread,null,server_thread,(void

  *)&client_socket);/*创建服务器线程*/

  整个web服务器处理的流程如图6所示。

  系统测试

  在嵌入式linux平台下,输入命令“ifconfig”,即可得到当前设备的ip地址,如图7所示。由图可见,该设备的ipv4地址为“192.168.1.106”,ipv6地址则为“fe80::c23f:eff:fef4:394b”。

  在嵌入式linux设备中启动web服务器程序,并在后台运行。在浏览器中输入web服务器的ipv4地址,即使用ipv4地址访问web服务器,如图8所示。得到web服务器反馈的网页如图9所示。由图9可见,web服务器能够输出html网页以及png格式的图片。在网页中输入web服务器的ipv6地址,即用ipv6地址来访问web服务器,如图10所示,得到如图11所示的web服务器反馈网页。

  同时使用其他浏览器访问web服务器也会得到同样的响应结果,说明本文设计的web服务器能够同时支持ipv4与ipv6地址进行访问。

  本文完成了一个支持ipv4与ipv6地址同时进行访问的嵌入式web服务器设计,但目前也仅仅实现了输出网页内容的功能,还无法对cgi脚本进行处理,并与用户进行交互。后续将不断完善系统功能,增加对cgi脚本进行处理的功能。

(南通大学电子信息学院 付康为 刘德靖 孙玲 施佺)


网站地图