服务器/客户系统的三个步骤:
(1):服务器设立服务
(2):客户连接到服务器
(3):服务器与客户处理事务
步骤一与步骤二加起来就是建立起服务器与客户的连接
步骤1:建立服务器端socket
过程比较固定,将三个步骤合成一个函数:make_server_socket
//返回sock,可以用fdopen进行读写
sock=make_server_socket(int portnum)
步骤2:建立到服务器的连接
将两个操作合成一个函数:connect_to_server
//返回对应的文件描述符,可以用read和write读写
fd=connect_to_server(houstname,portnum)
步骤3:客户/服务器的会话
●一般的服务器端
调用process_request函数来处理请求
●一般的客户端
调用talk_with_server来与服务器通信
●服务器处理请求:process_request函数
有两种处理请求的方法:DIY或者代理
(1)DIY:自己做,服务器接收请求,自己处理工作
自己做用于快速简单的任务,计算当前的日期和时间需要系统调用time和库函数ctime,而使用代理的话需要fork和exec来运行date,至少需要三个系统调用核创建一个新的进程。所以此时效率最高的方法就是服务器自己来完成工作并且在listen中限制队列的大小。
(2)代理:服务器接收请求,然后创建一个新进程来处理工作
处理耗时的任务或等待资源时,需要代理来完成工作,服务器可以使用fork创建新进程来处理每个请求。通过这种方式,服务器可以同时处理多个任务。
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程。它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态,当父进程查询完后,子进程全部删除。要注意使用SIGCHLD调用wait来阻止僵尸进程问题,但是当多个子进程同时退出时,要使用waitpid函数,wait是waitpid的简单版本,调用wait(&status)相当于调用waitpid(-1,&status,0)
编写web服务器
●具备的用户操作:
(1)列举目录信息
(2)cat文件
(3)运行程序
●web服务器协议
(1)客户发送请求
GET filename HTTP/version
(2)服务器发送应答
HTTP/version status-code status-message
/* socklib.c
* This file contains functions used lots when writing internet Client/Server programs. The two main function here are:
* -------int make_server_socket(portnum) return a server socket or -1 if error
* -------int make_server_socket_q(portnum,backlog)
* -------int connect_to_server(char * hostname,int portnum) return a connected socket or -1 if error
*/
#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) //返回sock_id
{
struct sockaddr_in saddr; /* build our address here */
struct hostent * hp; /* this is part of our */
char hostname[HOSTLEN]; /* address */
int sock_id; /* the socket */
sock_id = socket(PF_INET,SOCK_STREAM,0); /* get a socket */
if(sock_id == -1)
return -1;
/* build address andbind it to socket */
bzero((void *)&saddr,sizeof(saddr)); /* clear out struct */
gethostname(hostname,HOSTLEN); /* where am I ? */
hp = gethostbyname(hostname); /* get infoabout host fill in host part */
bcopy((void *)hp -> h_addr,(void *)&saddr.sin_addr,hp->h_length);
saddr.sin_port = htons(portnum); /* fill in socket port */
saddr.sin_family = AF_INET; /* fill in addr family */
if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr)) != 0)
return -1;
/* arrange for incoming calls */
if(listen(sock_id,backlog) != 0)
return -1;
return sock_id;
}
int connect_to_server(char * host,int portnum) //返回sock
{
int sock;
struct sockaddr_in servadd; /* the number to call */
struct hostent *hp; /* used to get number */
/* step 1 : GEt a socket */
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock == -1)
return -1;
/* Step 2: connect to server */
bzero(&servadd,sizeof(servadd)); /* zero the address */
hp = gethostbyname(host); /* lookup host‘s ip # */
if(hp == NULL)
return -1;
bcopy(hp->h_addr,(struct sockaddr *)&servadd.sin_addr,hp->h_length);
servadd.sin_port = htons(portnum); /* fill in port number */
servadd.sin_family = AF_INET; /* fill in socket type */
if(connect(sock,(struct sockaddr *)&servadd,sizeof(servadd)) != 0)
return -1;
return sock;
}
/* webserv.c-一个最小服务器
* 只支持GET
* 在当前目录运行
* fork子进程来解决每个请求
* 有很多安全漏洞
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
int make_server_socket(int);
int make_server_socket_q(int ,int);
int connect_to_server(char*,int);
void read_til_crnl(FILE *);
void process_rq(char*,int);
void cannot_do(int fd);
void do_404(char *item,int fd);
int isadir(char *f);
int not_exist(char *f);
void do_ls(char * dir,int fd);
char * file_type(char *f);
int ends_in_cgi(char *f);
void do_exec(char * prog,int fd);
void do_cat(char *f,int fd);
int main(int ac,char *av[])
{
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]));
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);
}
}
/*
*一直读直到\r\n
*/
void read_til_crnl(FILE *fp)
{
char buf[BUFSIZ];
while(fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf,"\r\n")!=0);
}
/*
*处理请求并回复
*在新的进程中处理请求
*rq是一个HTTP command rq=GET filename HTTP/version
*/
void process_rq(char *rq,int fd)
{
char cmd[BUFSIZ],arg[BUFSIZ];
if(fork()!=0)
return;
strcpy(arg,"./"); //arg中加入./
if(sscanf(rq,"%s%s",cmd,arg+2)!=2) //将GET放到cmd中,filename放到./后
return ;
if(strcmp(cmd,"GET")!=0) //如果cmd不等于GET,不能处理
cannot_do(fd);
else if(not_exist(arg)) //如果是get请求但找不到arg,404
do_404(arg,fd);
else if(isadir(arg)) //如果arg是目录,执行ls并回复
do_ls(arg,fd);
else if(ends_in_cgi(arg)) //如果后缀是cgi,执行
do_exec(arg,fd);
else
do_cat(arg,fd); //如果不是目录也不是cgi,显示内容
}
/*
*所有回复共同的头部
*content_type可以为空
*/
void 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);
}
/*
*无法执行的命令
*/
void 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");
fclose(fp);
}
/*
*找不到文件
*/
void 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\n is not found \r\n",item);
fclose(fp);
}
/*
*调用stat查看是不是目录
*/
int isadir(char *f)
{
struct stat info;
return (stat(f,&info)!=-1&&S_ISDIR(info.st_mode));
}
/*
*调用stat看文件存不存在
*/
int not_exist(char *f)
{
struct stat info;
return (stat(f,&info)==-1);
}
void do_ls(char * dir,int fd)
{
FILE * fp;
fp = fdopen(fd,"w");
fprintf(fp,"text/plain\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)
/* returns 'extension' of file */
{
char * cp;
if((cp = strrchr(f,'.')) != NULL)
return cp+1;
return "";
}
/*
*利用file_type看扩展名是否为cgi
*/
int ends_in_cgi(char *f)
{
return (strcmp(file_type(f),"cgi") == 0);
}
/*
*执行cgi文件
*/
void 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);
}
/*
* 显示不同的文件
*/
void 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 = "text/gif";
else if(strcmp(extension,"jpg") == 0)
content = "text/jpeg";
else if(strcmp(extension,"jpeg") == 0)
content = "text/jpeg";
fpsock = fdopen(fd,"w");
fpfile = fopen(f,"r");
if(fpsock != NULL && fpfile != NULL)
{
header(fpsock,content); //加入头部即HTTP应答
fprintf(fpsock,"\r\n");
while((c = getc(fpfile)) != EOF)
putc(c,fpsock);
fclose(fpfile);
fclose(fpsock);
}
exit(0);
}