一、背景
我们都知道使用浏览器访问网址的方法,将网址输入进地址框中就会显示出相应的文字、图片、视频等信息。实际上基于socket的客户/服务器原理是相似的,虽然可能承载信息的载体不同,但其背后的原理是基本一致的。通过理解这些原理(关于socket,我上一篇博客分析过),我们就可以编写一个简单的WEB服务器并做检测。
二、基本步骤
实际上基于socket的客户/服务器系统的步骤可以简单的概括为3步:
1、服务器建立服务
2、客户端连接服务器
3、客户端与服务器进行数据交换
三、编写socklib.c文件
这个文件中主要包含了一些常用的函数。便于模块化编程。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<time.h>
#include<strings.h>
#define HOSTLEN 256
#define BACKLOG 1
int make_server_socket_q(int ,int );
int make_server_socket(int portnum)
{
return make_server_socket_q(portnum,BACKLOG);
}
int make_server_socket_q(int portnum,int backlog)
{
struct sockaddr_in saddr;
struct hostent *hp;
char hostname[HOSTLEN];
int sock_id;
sock_id = socket(PF_INET,SOCK_STREAM,0);
if (sock_id == -1)
return -1;
bzero((void *)&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp = gethostbyname(hostname);
bcopy((void *)hp->h_addr,(void *)&saddr.sin_addr,hp->h_length);
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;
struct hostent *hp;
sock = socket(AF_INET,SOCK_STREAM,0);
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_port = htons(portnum);
servadd.sin_family = AF_INET;
if(connect(sock,(struct sockaddr *)&servadd,sizeof(servadd)) != 0)
return -1;
return sock;
}
make_server_socket函数返回一个服务器的socket。
connect_to_server函数返回一个连接后的socket。
四、编写WEB服务器
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
int main(int ac,char *av[])
{
int sock,fd;
FILE *fpin;
char request[BUFSIZ];
if(ac == 1){
fprintf(stderr,"usage:ws portnum\n");
exit(1);
}
sock = make_server_socket(atoi(av[1]));
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)
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))
do_exec(arg,fd);
else
do_cat(arg,fd);
}
header(FILE *fp,char *content_type)
{
fprintf(fp,"HTTP/1.0 200 OK\r\n");
if(content_type)
fprintf(fp,"Content-type:%s\r\n",content_type);
}
cannot_do(int fd)
{
FILE *fp = fdopen(fd,"w");
fprintf(fp,"HTTP/1.0 501 Not Implemented\r\n");
fprintf(fp,"Content-type:text/plain\r\n");
fprintf(fp,"\r\n");
fprintf(fp,"That command is not yet implemented\r\n");
fclose(fp);
}
do_404(char *item,int fd)
{
FILE *fp = fdopen(fd,"w");
fprintf(fp,"HTTP/1.0 404 Not Found\r\n");
fprintf(fp,"Content-type:text/plain\r\n");
fprintf(fp,"\r\n");
fprintf(fp,"The item 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,int fd)
{
FILE *fp;
fp = fdopen(fd,"w");
header(fp,"text/plain");
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)
{
char *cp;
if((cp = strrchr(f,'.')) != NULL)
return cp+1;
return "";
}
ends_in_cgi(char *f)
{
return (strcmp(file_type(f),"cgi") == 0);
}
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/plain";
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";
fpsock = fdopen(fd,"w");
fpfile = fopen(f,"r");
if(fpsock != NULL && fpfile != NULL)
{
header(fpsock,content);
fprintf(fpsock,"\r\n");
while((c = getc(fpfile)) != EOF)
putc(c,fpsock);
fclose(fpfile);
fclose(fpsock);
}
exit(0);
}
五、测试WEB服务器
使用gcc webserv.c socklib.c -o webserv进行编译
接着运行webserv,后面跟端口号(12345)
在浏览器中输入你计算机的主机名:12345
然后后面加上你需要打开的文件或者需要运行的文件
六、总结
WEB服务器与一般的时间查询服务器原理是相通的,都是基于socket的。这里只是简单的编写了WEB服务器。后续还有许多需要接着学习。