当前位置 : 首页 - 工作研究 - 工作研究 - 网络管理 - 正文

支持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脚本进行处理的功能。

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