运用数据通信及网络课程所学的知识,设计一个简单的http服务器,在实现的过程中学习网络套接字编程、HTTP 协议、Web 服务器等知识;提供静态网页浏览服务功能;Web 服务器可配置参数有主目录、首页文件名、HTTP 端口号等项,锻炼UNIX环境下网络编程的能力,熟悉socket编程原理和http协议,从而巩固所学网络知识。
1)要能实现get方法;
2)能响应请求的html页面;
3)还能够响应请求的gif,jpg的等图片文件;
4)能响应请求的目录的信息,并以列表显示;
5) 能读取文本文件txt。
3.1 工作原理
网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用叫Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用,数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Linux网络编程中常用的系统调用函数包括:初始化(socket)、连接(connect)、绑定(bind)、监听(1isten)、接收(accept)、关闭(close)、发送(send)。
Socket接口通过以下函数为应用编程提供服务: 1)socket():创建一个套接字;
2) bind():用于将套接字与本地或远程某个地址和端口建立关联; 3) listen():用于在服务器一端监听是否有连接请求; 4) connect():客户通过调用该函数主动与服务器建立连接; 5) accept():用于接收来自客户的连接请求,服务器先创建一个新的套接字,用新套接字与客户进行通信,原套接字仍保持侦听状态,以便接收其他连接请求;6)send()和recv():SOCK_STREAM类型套接字数据的发送和接收; 7) close():关闭套接字。
HTTP协议是基于请求/响应模式的。一个客户机与服务器建立连接后,发送一个请求给服务器。服务器接到请求后,给予相应的响应信息。在Internet上,HTTP通信通常发生在TCP/IP连接之上,建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息:包括请求修饰符、客户机信息和可能的内容服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容,客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后客http工作流程图户机与服务器断开连接。
基于HTTP协议的客户/服务器模式的信息交换分四个步骤:建立连接、发送请求信息、发送响应信息和关闭连接
3.2设计过程
Web服务器的工作方式都是以套接字机制为基础的。对于一个B/S通信模型,在服务器端,首先创建一个套接字,并将此套接字与服务器公认的IP地址绑定在一起,接着将套接字转换成侦听套接字来侦听用户的请求,在接收到客户机的请求后就建立连接,然后服务器和客户机之间就可以进行通信了。多个请求时服务器使用不同的进程来
进行处理,互不干扰。以下为web服务器的工作流程图:
1、服务器端
1)创建一个socket;
2)将该socket与本机地址/端口号捆绑;
3)在监听端口上监听客户机的连接请求;
4) 当accept捕捉到一个连接请求时,就建立连接线路并返回一个新的通信文件描述符;
5)父进程创建一个子进程,父进程关闭通信文件描述符并继续监听端口上其它客户机的连接请求;
6)子进程通过通信文件描述符与客户机进行通信,通信结束后终止子进程并关闭通信文件描述符。
2、浏览器端
1)创建一个socket;
2)向指定的服务器主机及端口发出连接请求,请求成功将返回通信文件描述符;
3)调用connect函数与服务器建立连接;
4)连接成功之后与服务器进行通信:
①、建立子进程;
②、处理get方式请求的静态网页;
③、接收请求
④、执行相关程序(CGI,sh)
⑤、回应请求
5)通信完毕后关闭通信文件描述符。
本次Linux综合实训,这次我们成功实现了一个小型的http服务器,能访问我们自己的网页;其次,在实现http服务器的过程中,熟悉了http协议等网络相关知识,巩固并实践了上学期网络课程所学内容;还有,我们又锻炼了UNIX环境下编程开发能力,基本了解了Web服务器的设计和工作原理,更加深了对socket套接字建立服务器端的过程,掌握了一些Linux下c语言网络编程方法,更加深了我对书本知识的理解。
遇到的最大问题:实现CGI接口时,CGI无法获取到测试网页表单中的参数,表单提交后找不到路径,直接跳到404页面。原因是表单提交请求方式是get,get请求方式的参数显示在地址栏上文件后缀名之后,服务器没有匹配到文件类型字符串,故无法找到页面。
1、socket服务器端
intmake_server_socket_q(int,int);
intmake_server_socket(int protnum)
{
returnmake_server_socket_q(protnum,BACKLOG);
}
intmake_server_socket_q(int portnum,int backlog)
{
struct sockaddr_in saddr;
//创建服务器socket
intsock_id;
sock_id=socket(PF_INET, SOCK_STREAM, 0);
if(sock_id==-1)//失败
{
return -1;
}
bzero((void *)&saddr,sizeof(saddr));
saddr.sin_addr.s_addr=htonl(INADDR_ANY);
saddr.sin_port=htons(portnum);
saddr.sin_family=AF_INET;
//绑定
if(bind(sock_id,(struct sockaddr*)&saddr,sizeof(saddr))!=0)
return -1;
//监听
if(listen(sock_id,backlog)!=0)
return -1;
return sock_id;
}
intconnect_to_server(char *host,int portnum)
{
int sock;
struct sockaddr_in servadd;//the number tocall
struct hostent *hp;//used to get number
//
sock = socket(PF_INET,SOCK_STREAM,0);//geta line
if(sock==-1)
return -1;
//清空
bzero(&servadd,sizeof(servadd));
hp = gethostbyname(host);
if(hp==NULL)
return -1;
bcopy( hp->h_addr,(structsockaddr*)&servadd.sin_addr, hp->h_length);
// servadd.sin_addr=htonl(INADDE_ANY);
servadd.sin_port=htons(portnum);
servadd.sin_family=AF_INET;
if(connect(sock,(structsockaddr*)&servadd,sizeof(servadd))!=0)
return -1;
return sock;
}
2、浏览器端
main(intac,char*av[])
{
//socket描述符和accept描述符
int sock,fd;
FILE *fpin;
//保存请求
char request[BUFSIZ];
if(ac==1)
{
fprintf(stderr,"usage:wsportnum\n");
exit(1);
}
sock=make_server_socket(atoi(av[1]));//atoi方法将字符串变成整型
if(sock==-1) exit(2);
while(1)
{
//该函数会阻塞等待客户端请求到达
fd =accept(sock,NULL,NULL);
//只读方式接收请求(文件流)
fpin=fdopen(fd,"r");
//得到请求
fgets(request,BUFSIZ,fpin);
//打印到控制台请求记录
printf("got a call :request =%s",request);
read_til_crnl(fpin);
//处理请求
process_rq(request,fd);
//结束本次请求
fclose(fpin);
}
}
//读取完整的请求
read_til_crnl(FILE*fp)
{
char buf[BUFSIZ];
while(fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf,"\r\n")!=0);
}
//处理请求
process_rq(char*rq, int fd)
{
char cmd[BUFSIZ],arg[BUFSIZ];
//创建子进程,如果不是子进程则结束
if (fork()!=0)
return;
strcpy(arg,"./");
if(sscanf(rq,"%s%s",cmd,arg+2)!=2)
return;
if(strcmp(cmd,"GET")!=0)//只能处理静态网页get方式
cannot_do(fd);
else if (not_exist(arg))//请求出错
do_404(arg,fd);
else if (isadir(arg))//判断是否为目录
do_ls(arg,fd);
else if (ends_in_cgi(arg))//是否为cgi程序
do_exec(arg,fd);
else if (ends_in_sh(arg))//是否为sh程序
do_exec_sh(arg,fd);
else
do_cat(arg,fd);
}
//获取头部信息
header(FILE*fp,char*content_type)
{
fprintf(fp,"HTTP/1.0 200OK\r\n");
if(content_type)
fprintf(fp,"Content-type:%s\r\n",content_type);
}
//请求501错误
cannot_do(intfd)
{
FILE *fp =fdopen(fd,"w");
fprintf(fp,"HTTP/1.0 501 NotImplemented\r\n");
fprintf(fp,"Content-type:text/plain\r\n");
fprintf(fp,"\r\n");
fprintf(fp,"Sorry,HTTP 501!\n\n无法处理请求!");
fclose(fp);
}
//请求出错404
do_404(char*item,int fd)
{
FILE *fp=fdopen(fd,"w");
fprintf(fp,"HTTP/1.0 404 NotFound\r\n");
fprintf(fp,"Content-type:text/plain\r\n");
fprintf(fp,"\r\n");
fprintf(fp,"Sorry,HTTP 404!\n\nTheitem you requested: %s \r\nis not found\r\n",item);
fclose(fp);
}
//判断是否为目录
isadir(char*f)
{
struct stat info;
return(stat(f,&info)!=-1&&S_ISDIR(info.st_mode));
}
//不存在
not_exist(char*f)
{
struct stat info;
return (stat(f,&info)==-1);
}
//显示目录下内容
do_ls(char*dir,intfd)
{
FILE *fp;
fp = fdopen(fd,"w");
header(fp,"text/plain;charset=UTF-8");
fprintf(fp,"\r\n");
fflush(fp);
dup2(fd,1);
dup2(fd,2);
close(fd);
execlp("ls","ls","-l",dir,NULL);
perror(dir);
exit(1);
}
//返回文件类型
char*file_type(char *f)//return 'extension' of file
{
char *cp;
if((cp=strrchr(f,'.'))!=NULL)
return cp+1;
return " ";
}
//cgi类型文件
ends_in_cgi(char*f)
{
return(strcmp(file_type(f),"cgi")==0);
}
//执行shell程序
ends_in_sh(char*f)
{
return(strcmp(file_type(f),"sh")==0);
}
do_exec_sh(char*prog,int fd)
{
system(prog);
}//shell
//执行可执行程序cgi
do_exec(char*prog,int fd)
{
FILE *fp;
fp =fdopen(fd,"w");
header(fp,NULL);
fflush(fp);
dup2(fd,1);
dup2(fd,2);
close(fd);
execl(prog,prog,NULL);
perror(prog);
}