1、服务器设计
学习了网络编程的基本操作和设计原则,建立一个Web服务器,这是一个典型的socket流的客户端/服务器系统。服务器设立服务,然后进入循环接收和处理请求。客户端连接到服务器,然后发送、接收或者交换数据,最后退出。该交互过程主要包含3个操作:
- 服务器设立服务
- 客户连接到服务器
- 服务器和客户处理事务
2、建立服务器socket
设立一个服务一般需要3个步骤:
- 创建一个socket:socket=socket(PF_INET,SOCK_STREAM,0)
- 给socket绑定一个地址:bind(sockid,&addr,sizeof(addr))
- 监听接入请求:listen(sockid,queue_size)
3、建立客户端socket
基于流的网络客户连接到服务器需要2个步骤:
- 创建一个socket:socket=socket(PF_INET,SOCK_STREAM,0)
- 使用该socket连接到服务器:connect(sockid,&serv_addr,sizeof(serv_addr))
4、服务器的设计问题——自己做或代理
服务器有两种设计方法,一种是自己做(DIY)服务器接收请求自己处理工作,另一种是代理也就是服务器接收请求然后创建一个新进程来处理工作。
- 自己做用于快速简单的任务
使用fork和exec来运行date至少需要3个系统调用和创建一个新进程,对于一些服务器,效率最高的方法是服务器自己来完成工作并且在listen中限制连接队列的大小
- 代理用于慢速复杂的任务
服务器处理耗时的任务或等待资源时,需要代理来完成其工作,服务器使用fork创建一个新进程来处理每个请求,通过这种方式,服务器可以同时处理多个任务。
- 使用SIGCHLD来阻止僵尸zombie问题
当子进程退出或被终止时,内核发送SIGCHLD给父进程,父进程可以为SIGCHLD设置一个信号处理函数,它可以调用wait,子进程从进程表中被删除,父进程从处理函数返回到主函数。
5、编写Web服务器
Web服务器通常具备的3种用户操作:
- 列举目录信息ls
- cat文件
- exec运行程序
webserv.c
Web服务器端程序
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<netdb.h>
#include<time.h>
#include<stdbool.h>
#include<sys/stat.h>
#include<string.h>
#define HOSTLEN 256
#define BACKLOG 1
#define oops(msg) {perror(msg); exit(1);}
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)
oops("socket");
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)
oops("bind");
if(listen(sock_id,backlog)!=0)
oops("listen");
return sock_id;
}
int make_server_socket(int portnum)
{
return make_server_socket_q(portnum,BACKLOG);
}
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,"That command is not yet implement\r\n");
fclose(fp);
}
bool not_exist(char *f)
{
struct stat info;
//stat把文件fname的信息复制到指针bufp所指的结构
return (stat(f,&info)==-1);
}
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,"The item you requested:%s is not found\r\n",item);
fclose(fp);
}
bool isadir(char *f)
{
struct stat info;
//stat把文件fname的信息复制到指针bufp所指的结构
return (stat(f,&info)!=-1 && S_ISDIR(info.st_mode));
}
void do_ls(char *dir,int fd)
{
FILE *fp=fdopen(fd,"w");
header(fp,"text/plain");
fprintf(fp,"\r\n");
fflush(fp);
//标准输入输出全部定向到缓存区,然后通过socket传给客户端
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 "";
}
bool ends_in_cgi(char *f)
{
return (strcmp(file_type(f),"cgi")==0);
}
void do_exec(char *prog,int fd)
{
FILE *fp=fdopen(fd,"w");
header(fp,"text/plain");
fprintf(fp,"\r\n");
fflush(fp);
//标准输入输出全部定向到缓存区,然后通过socket传给客户端
dup2(fd,1);
dup2(fd,2);
close(fd);
execl(prog,prog,NULL);
perror(prog);
exit(1);
}
void do_cat(char *f,int fd)
{
char *extension=file_type(f);
char *content="text/plain";
FILE *fpsock,*fpfile;
char 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);
fprintf(fpsock,"\r\n");
while((c=getc(fpfile))!=EOF)
putc(c,fpsock);
fclose(fpfile);
fclose(fpsock);
}
exit(0);
}
void process_rq(char *rq,int fd)
{
char cmd[BUFSIZ],arg[BUFSIZ];
if(fork()!=0)
return;
if(sscanf(rq,"%s %s",cmd,arg)!=2)
return;
if(strcmp(cmd,"GET")!=0) //必须通过GET实现请求
cannot_do(fd);
else if(not_exist(arg)) //arg必须是已存在的文件或目录
do_404(arg,fd);
else if(isadir(arg)) //如果arg是目录执行ls命令
do_ls(arg,fd);
else if(ends_in_cgi(arg)) //如果arg是.cgi文件执行该文件
do_exec(arg,fd);
else //如果是其他文件执行cat
do_cat(arg,fd);
}
int main(int argc,char *argv[])
{
int sock,fd;
FILE *fpin;
char request[BUFSIZ];
if(argc==1)
oops("usage:webserv portnum\n");
sock=make_server_socket(atoi(argv[1]));
if(sock==-1)
oops("sock");
while(1)
{
fd=accept(sock,NULL,NULL);
fpin=fdopen(fd,"r");
fgets(request,BUFSIZ,fpin);
printf("got a call:request=%s",request);
int len = strlen(request);
request[len-1]=0; //去掉\n
process_rq(request,fd);
fclose(fpin);
}
return 0;
}
webclnt.c
Web客户端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>
#include<stdlib.h>
#include<strings.h>
#define oops(msg) {perror(msg); exit(1);}
int connect_to_server(char *host,int portnum)
{
struct sockaddr_in servadd;
struct hostent *hp;
int sock_id;
sock_id=socket(AF_INET,SOCK_STREAM,0);
if(sock_id==-1)
oops("socket");
bzero(&servadd,sizeof(servadd));
hp=gethostbyname(host);
if(hp==NULL)
oops(host);
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_id,(struct sockaddr*)&servadd,sizeof(servadd))!=0)
oops("connect");
return sock_id;
}
int main(int argc,char *argv[])
{
struct sockaddr_in servadd;
struct hostent *hp;
int sock_id,sock_fd;
char message[BUFSIZ];
int messlen,n_read;
sock_id=connect_to_server(argv[1],atoi(argv[2]));
read(0, message, BUFSIZ);
if(write(sock_id,message,BUFSIZ)==-1)
oops("write");
while((n_read=read(sock_id,message,BUFSIZ))>0)
if(write(1,message,n_read)==-1)
oops("write");
close(sock_id);
return 0;
}