流程图
关键socket
// socket()
sock_id=socket(PF_INET,SOCK_STREAM,0);
if(sock_id==-1)
{
return -1;
}
// 初始化地址 sockaddr_in
bzero(&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
printf("hostname:%s\n",hostname);
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;
// bind()
if(bind(sock_id,(struct sockaddr*)&saddr,sizeof(saddr))!=0)
{
return -1;
}
// listen()
if(listen(sock_id,backlog)!=0)
{
return -1;
}
// accept()
fd=accept(sock,NULL,NULL);
文件描述符转文件指针(带缓冲区),读取
// 文件描述符转文件指针(带缓冲区),读取
fpin=fdopen(fd,"r");
// 读取
fgets(request,BUFSIZ,fpin);
printf("got a call: request = %s",request);
read_til_crnl(fpin);
// 读清
void read_til_crnl(FILE* fp)
{
char buf[BUFSIZ];
// 读取不为空,不等于“\r\n” -> 一直读下去
while (fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf,"\r\n")!=0);
}
文件属性
int isadir(char* f)
{
struct stat info;
//stat() 得到文件(目录文件是一种特殊文件)属性
return(stat(f,&info)!=-1&&S_ISDIR(info.st_mode));
}
io重定向,execlp()
int do_ls(char *dir,int fd)
{
FILE* fp;
fp=fdopen(fd,"w");
header(fp,"text/plain");
fprintf(fp,"\r\n");
fflush(fp);
// stdout和stderror定向到fd
dup2(fd,1);
dup2(fd,2);
close(fd);
// execlp() 运行程序输出到fd
execlp("ls","ls","-1",dir,NULL);
perror(dir);
exit(1);
}
strrchr()
char* file_type(char* f)
{
char *cp;
// 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置
// a.txt 返回txt
if((cp=strrchr(f,'.'))!=NULL)
{
return cp+1;
}
return "";
}
源码
// socklib.h
#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 <string.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;
// socket()
sock_id=socket(PF_INET,SOCK_STREAM,0);
if(sock_id==-1)
{
return -1;
}
// init a sockaddr_in struct
bzero(&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
printf("hostname:%s\n",hostname);
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;
// bind()
if(bind(sock_id,(struct sockaddr*)&saddr,sizeof(saddr))!=0)
{
return -1;
}
// listen()
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;
}
// webserv.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include"socklib.h"
// 读清
void read_til_crnl(FILE* fp)
{
char buf[BUFSIZ];
// 读取不为空,不等于“\r\n” -> 一直读下去
while (fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf,"\r\n")!=0);
}
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\r\n");
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);
}
int isadir(char* f)
{
struct stat info;
//stat() 得到文件(目录文件是一种特殊文件)属性
return(stat(f,&info)!=-1&&S_ISDIR(info.st_mode));
}
int not_exist(char* f)
{
struct stat info;
return(stat(f,&info)==-1);
}
int do_ls(char *dir,int fd)
{
FILE* fp;
fp=fdopen(fd,"w");
header(fp,"text/plain");
fprintf(fp,"\r\n");
fflush(fp);
// stdout和stderror定向到fd
dup2(fd,1);
dup2(fd,2);
close(fd);
// execlp() 运行程序输出到fd
execlp("ls","ls","-1",dir,NULL);
perror(dir);
exit(1);
}
char* file_type(char* f)
{
char *cp;
// 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置
// a.txt 返回txt
if((cp=strrchr(f,'.'))!=NULL)
{
return cp+1;
}
return "";
}
int ends_in_cgi(char* f)
{
return (strcmp(file_type(f),"cgi")==0);
}
void do_exec(char* prog,int fd)
{
printf("do_exec %s\n",prog);
FILE* fp;
fp=fdopen(fd,"w");
// in header
header(fp,NULL);
fflush(fp);
// stdout stderr -> fd
dup2(fd,1);
dup2(fd,2);
close(fd);
execlp(prog,prog,NULL);
perror(prog);
}
int do_cat(char* f,int fd)
{
char* extension=file_type(f);
char* content="text/plain";
FILE* fpsock, *fpfile;
int c;
if(strcpy(extension,"html")==0)
{
content="text/html";
}else if(strcmp(extension,"gif")==0)
{
content="image/gif";
}else if(strcmp(extension,"jpg")==0)
{
content="image/jpg";
}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,"\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];
// fork()
if(fork()!=0)
{
// 副线程返回
return;
}
// 当前地址
strcpy(arg,"./");
// 分解request
if(sscanf(rq,"%s%s",cmd,arg+2)!=2)
{
return;
}
// strcmp
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);
}
}
int main(int ac,char* av[])
{
printf("start\n");
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)
{
// accept()
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);
}
return 0;
}
问题一
子进程会成为僵尸进程
- 父进程wait()或waitpid(),会导致父进程挂起
- signal函数为SIGCHLD安装handler,handler中wait
- signal(SIGCHLD,SIG_IGN)通知内核对子进程结束不感兴趣
- fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。
问题二
accpet 得到的fd未做处理
当fd不可写时关闭,避免文件描述符不够用,导致新连接无法生成。
参考文献
《Unix-Linux编程实践教程》