1.功能需求:
(1)学习网络套接字编程、HTPP协议、Web服务器等知识;(2)设计一简单Web服务器,提供静态网页浏览服务功能。
2.实现的功能:
(1)C语言实现基于socket的Web服务器
(2)使用socket通信,使用进程运行
(3)实现遍历指定目录
(4)实现对静态网页的浏览
(5)访问普通文本
(6)执行cgi程序
(7)执行shell程序
(8)浏览图片(jpg,jpeg,gif)
(9)记录日志文件
3.开发环境:
Vmware Workstation 6.4 虚拟机下,用C语言进行开发,开发工具包括:vim,gcc,gdb。
4.实现细节:
客户和服务器都是进程,服务器设立服务,然后进入循环接收和处理请求。客户连接到服务器,然后发送,接收挥着交换数据,最后退出。该交互过程中主要包含三个操作:
(1) 服务器设立服务
a) 建立服务器端socket
i. 创建一个socket
ii. 绑定地址
iii. 监听接入请求
Socket=make_soerver_socket(int protnum)
return -1 if error,
or a server socket listening at port”protnum”
(2) 客户连接到服务器(浏览器)
a) 建立到服务器的链接
i. 创建一个socket
ii. 连接到服务器
(3) 服务器和客户处理事
a) 具体的会话内容
b) 使用fork
i. 当有一个新的访问请求时便创建一个进程来完成事务。
Process_request(fd);
c) 服务器的功能
i. 列举目录信息
ii. Cat文件
iii. 运行程序
(4) web服务器协议
a) http请求:get
Telnet创建一个socket并调用connect来连接web服务器。服务器接受连接请求,并创建一个基于socket的从客户端的键盘到web服务进程的数据通道。
b) http应答:ok
服务器读取请求,检查请求,然后返回一个请求。应答有两部分:头部和内容。头部以状态起始。状态行含有两个或更多的字符串。第一个字符串是协议的版本,第二个是返回码。
结构体:
sockaddr_in(在netinet/in.h中定义):
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
struct hostent {
char *h_name; /*地址的正式名称*/
char **h_aliases; /* 空字节-地址的预备名称的指针*/
int h_addrtype; /*地址类型; 通常是AF_INET*/
int h_length; /*地址的比特长度*/
char **h_addr_list; /* 零字节-主机网络地址指针。网络字节顺序*/
};
struct in_addr {
in_addr_t s_addr; /*结构体in_addr 用来表示一个32位的IPv4地址*/
};
5.实现代码:在我的上传资源中有完整代码
socklib.c
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <strings.h> #include <netdb.h> #include <netinet/in.h> #include <time.h> #include <sys/utsname.h> #define HOSTLEN 256 #define BACKLOG 10 int make_server_socket_q(int,int); int make_server_socket(int protnum) { return make_server_socket_q(protnum,BACKLOG); } int make_server_socket_q(int portnum,int backlog) { struct sockaddr_in saddr; int sock_id; //创建服务器socket 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; } int connect_to_server(char *host,int portnum) { int sock; struct sockaddr_in servadd;//the number to call struct hostent *hp;//used to get number //得到一个socket sock = socket(PF_INET,SOCK_STREAM,0);//get a line if(sock==-1) return -1; //链接 bzero(&servadd,sizeof(servadd)); hp = gethostbyname(host); if(hp==NULL) return -1; bcopy( hp->h_addr,(struct sockaddr*)&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,(struct sockaddr*)&servadd,sizeof(servadd))!=0) return -1; return sock; }webserv.c
/* build :gcc webserv.c socklib.c -o webserv -w run :./webserv 8080 */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <sys/utsname.h> #include <fcntl.h> #include <unistd.h> #include <time.h> main(int ac,char*av[]) { //socket描述符和accept描述符 int sock,fd; FILE *fpin; //保存请求 char request[BUFSIZ]; if(ac==1) { fprintf(stderr,"usage:ws portnum "); exit(1); } sock =make_server_socket(atoi(av[1]));//atoi方法将字符串变成整型 if(sock==-1) exit(2); //创建日志文件 createLog(); while(1) { //该函数会阻塞等待客户端请求到达 fd =accept(sock,NULL,NULL); //只读方式接收请求(文件流) fpin=fdopen(fd,"r"); //得到请求 fgets(request,BUFSIZ,fpin); //打印到控制台请求记录 printf("got a call :request = %s",request); //记录日志文件 writeLog(request); read_til_crnl(fpin); //处理请求 process_rq(request,fd); //结束本次请求 fclose(fpin); } } //创建日志文件 createLog() { if((access("./log",F_OK))!=-1) { //日志文件存在则清空内容 int ret = open("./log", O_WRONLY | O_TRUNC); if(ret == -1) { printf("打开日志文件失败! "); return; } close(ret); } else { //日志文件不存在则创建 system("touch ./log"); system("chmod 744 ./log"); } } //记录日志文件 writeLog(char *request) { char temp[225]="got a call : request = "; strcat(temp,request); int logfd; //打开文件 if((logfd=open("./log",O_RDWR|O_APPEND,0644))<0) { perror("打开日志文件出错!"); exit(1); } //获取当前时间 int z; struct tm *t; time_t tt; time(&tt); t = localtime(&tt); char time[18]; sprintf(time,"%4d年%02d月%02d日 %02d:%02d:%02d ", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); z=write(logfd,time,150); if(z<0) { perror("写入日志文件出错!"); exit(1); } //关闭文件 if(close(logfd)) { perror("关闭日志文件出错!"); exit(1); } } //读取完整的请求 read_til_crnl(FILE*fp) { char buf[BUFSIZ]; while(fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf," ")!=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 200 OK "); if(content_type) fprintf(fp,"Content-type: %s ",content_type); } //请求501错误 cannot_do(int fd) { FILE *fp =fdopen(fd,"w"); fprintf(fp,"HTTP/1.0 501 Not Implemented "); fprintf(fp,"Content-type:text/plain "); fprintf(fp," "); fprintf(fp,"Sorry,HTTP 501! 无法处理请求!"); fclose(fp); } //请求出错404 do_404(char *item,int fd) { FILE *fp=fdopen(fd,"w"); fprintf(fp,"HTTP/1.0 404 Not Found "); fprintf(fp,"Content-type:text/plain "); fprintf(fp," "); fprintf(fp,"Sorry,HTTP 404! The item you requested: %s is not found ",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,int fd) { FILE *fp; fp = fdopen(fd,"w"); header(fp,"text/plain;charset=UTF-8"); fprintf(fp," "); 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); } //显示当前目录下全部文件或目录 do_cat(char*f,int fd) { char *extension=file_type(f); char *content="text/html"; FILE *fpsock,*fpfile; int c; if(strcmp(extension,"html")==0) content="text/html"; else if (strcmp(extension,"gif")==0) content="image/gif"; else if(strcmp(extension,"jpg")==0) content="image/jpeg"; else if(strcmp(extension,"jpeg")==0) content="image/jpeg"; fpsock = fdopen(fd,"w"); fpfile = fopen(f,"r"); if(fpsock!=NULL&&fpfile!=NULL) { header(fpsock,content); fprintf(fpsock," "); while((c=getc(fpfile))!=EOF) putc(c,fpsock); fclose(fpfile); fclose(fpsock); } exit(0); }