设计实现
为了进行浏览器与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所示。