客户端和web服务器交互的基本结构如下:
(1)客户端发送请求
GET filename HTTP/version
可选参数
空行
(2)服务器发送应答
HTTP/version status-code status-message
附加信息
空行
内容
webserv.c
1 /* webserv.c a minimal web server (version 0.2) 2 * usage : webserv portnumber 3 */ 4 #include <stdio.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <string.h> 8 #include <sys/stat.h> 9 #include <netinet/in.h> 10 #include <netdb.h> 11 12 /************************************************************** 13 * 下面I/O的函数一些总结: 14 * 1.可以在参数列表中指定并可以格式化输出函数: fprintf(); 15 * eg:fprintf(stderr, "usage: webserv portnum"); 16 * fprintf((FILE*)fp, "HTTP/1.0 200 OK "); 17 * 2.从指定FILE指针中读取一字符串, char* fgets(char* buf, int size, FILE* stream) 18 * buf为保存字符的内存空间,直到出现换行、文件尾、已读了size-1个字符为止,最后加NULL作为字符串结束。返回NULL表示出错。 19 * eg:fgets(request, BUFSIZ, fpin); 20 * 3.读一个字符,写一个字符。 eg: while((c=getc(fpfile))!=EOF) putc(c, fpsock); 21 *************************************************************/ 22 23 24 int make_server_socket(int); 25 26 int main(int argc, char* argv[]) { 27 int sock, fd; 28 FILE * fpin; 29 char request[BUFSIZ]; 30 31 if(argc==1) { 32 fprintf(stderr , "usage :webserv portnum. "); 33 exit(1); 34 } 35 sock=make_server_socket(atoi(argv[1])); 36 if(sock==-1) 37 exit(2); 38 while(1) { 39 /* take a call and buffer it */ 40 fd=accept(sock, NULL, NULL); 41 fpin=fdopen(fd, "r"); 42 /* read request */ 43 fgets(request, BUFSIZ, fpin); 44 printf("get a call ; request = %s", request); 45 read_til_crnl(fpin); 46 /* do what client asks */ 47 process_rq(request, fd); 48 49 fclose(fpin); 50 } 51 return 0; 52 } 53 54 int make_server_socket(int portnum) { 55 int sock; 56 struct sockaddr_in saddr; 57 struct hostent *hp; 58 char hostname[BUFSIZ]; 59 60 sock=socket(PF_INET, SOCK_STREAM, 0); 61 if(sock==-1) 62 return -1; 63 64 gethostname(hostname, BUFSIZ); 65 hp=gethostbyname(hostname); 66 bzero(&saddr, sizeof(saddr)); 67 bcopy((void*)hp->h_addr, (void*)&saddr.sin_addr, hp->h_length); 68 saddr.sin_port=htons(portnum); 69 saddr.sin_family=AF_INET; 70 71 if(bind(sock, (struct sockaddr*)&saddr, sizeof(saddr))!=0) 72 return -1; 73 if(listen(sock ,1)!=0) 74 return -1; 75 return sock; 76 } 77 78 /********************************************************************** 79 * read_til_crnl(FILE* ) 80 * skip over all request info until a CRNL is seen 81 *********************************************************************/ 82 read_til_crnl(FILE* fp) { 83 char buf[BUFSIZ]; 84 while(fgets(buf, BUFSIZ, fp)!=NULL && strcmp(buf, " ")!=0) 85 ; 86 } 87 88 /********************************************************************* 89 * process_rq(char* rq, int fd) 90 * do what the request asks for and write reply to fd 91 * handles request in a new process 92 * rq is HTTP command: GET /foo/bar.html HTTP/1.0 93 ********************************************************************/ 94 process_rq(char *rq, int fd) { 95 char cmd[BUFSIZ], arg[BUFSIZ]; 96 /*creat a new process and return if not the child*/ 97 if(fork()!=0) 98 return ; 99 strcpy(arg, "./"); /* precede args with .*/ 100 if(sscanf(rq, "%s %s", cmd, arg+2)!=2) 101 return ; 102 if(strcmp(cmd, "GET")!=0) 103 cannot_do(fd); 104 else if(not_exist(arg)) 105 do_404(arg, fd); 106 else if(isadir(arg)) 107 do_ls(arg, fd); 108 else if(ends_in_cgi(arg)) 109 do_exec(arg, fd); 110 else 111 do_cat(arg, fd); 112 } 113 114 115 116 /********************************************************* 117 * the reply header thing: all functions need one 118 * if content_type is NULL then don't send content type 119 ********************************************************/ 120 header(FILE* fp, char * content_type) { 121 fprintf(fp, "HTTP/1.0 200 OK "); 122 if(content_type) 123 fprintf(fp, "Content-type: %s ", content_type); 124 } 125 126 /********************************************************* 127 * simple functions first: 128 * cannot_do(fd): unimplemented HTTP command 129 * and do_404*(item ,fd) no such object 130 ********************************************************/ 131 132 cannot_do(fd) { 133 FILE *fp=fdopen(fd, "w"); 134 135 fprintf(fp, "HTTP/1.0 501 Not Implemented "); 136 fprintf(fp, "Content-type: text/plain "); 137 fprintf(fp, " "); 138 139 fprintf(fp, "That command is not yet implemented "); 140 fclose(fp); 141 } 142 143 do_404(char* item, int fd) { 144 FILE* fp=fdopen(fd, "w"); 145 146 fprintf(fp, "HTTP/1.0 404 Not Found "); 147 fprintf(fp, "Content-type:text/plain "); 148 fprintf(fp, " " ); 149 150 fprintf(fp, "The item you requested: %s is not found ", item); 151 fclose(fp); 152 } 153 154 /****************************************************************** 155 * the directory listing sectoin 156 * isadir() uses stat, not_exist() uses stat 157 * do_ls runs ls. It should not 158 *****************************************************************/ 159 160 isadir(char *f) { 161 struct stat info; 162 return (stat(f, &info)!=-1 && S_ISDIR(info.st_mode)); 163 } 164 165 not_exist(char *f) { 166 struct stat info; 167 return (stat(f, &info)==-1); 168 } 169 170 do_ls(char *dir, int fd) { 171 FILE* fp; 172 fp=fdopen(fd, "w"); 173 header(fp, "text/plain"); 174 fprintf(fp, " "); 175 fflush(fp); 176 177 dup2(fd, 1); /*注意这里的文件标识符为fd,即是socket单位,而不是fdopen()的返回值,切记切记*/ 178 dup2(fd, 2); 179 close(fd); 180 execlp("ls", "ls", "-l",dir, NULL); 181 perror(dir); 182 exit(1); 183 } 184 185 /******************************************************* 186 * the cgi stuff function to check extension and 187 * one to run the program. 188 ******************************************************/ 189 char * file_type(char* f) { 190 /* returns 'extension' of file */ 191 char *cp; 192 if((cp=strrchr(f, '.'))!=NULL) 193 return cp+1; 194 return ""; 195 } 196 197 ends_in_cgi(char* f) { 198 return (strcmp(file_type(f) , "cgi")==0); 199 } 200 201 do_exec(char *prog, int fd) { 202 FILE *fp; 203 204 fp=fdopen(fd, "w"); 205 header(fp, NULL); 206 fflush(fp); 207 dup2(fd, 1); 208 dup2(fd, 2); 209 close(fd); 210 execl(prog, prog, NULL); 211 perror(prog); 212 } 213 214 /******************************************************************* 215 * do_cat(filename, fd) 216 * sends back contents after a header 217 ******************************************************************/ 218 do_cat(char* f, int fd) { 219 char *extension = file_type(f); 220 char *content = "text/plain"; 221 FILE *fpsock, *fpfile; 222 int c; 223 224 if(strcmp(extension, "html")==0) 225 content = "text/html"; 226 else if(strcmp(extension, "gif")==0) 227 content = "image/gif"; 228 else if(strcmp(extension, "jpg")==0) 229 content = "image/jpeg"; 230 else if(strcmp(extension, "jpeg")==0) 231 content = "image/jpeg"; 232 233 fpsock = fdopen(fd, "w"); 234 fpfile = fopen(f, "r"); 235 if(fpsock != NULL && fpfile!=NULL) { 236 header(fpsock, content); 237 fprintf(fpsock, " "); 238 while((c=getc(fpfile))!=EOF) 239 putc(c, fpsock); 240 fclose(fpfile); 241 fclose(fpsock); 242 } 243 exit(0); 244 }
用法说明: gcc webserv.c -o webserv
./webserv 12345(端口号)
然后在浏览器中输入主机名加端口号可访问
注意:在do_ls()中,dup2(sock_in, 1),dup2(sock_in,2),一定要注意这一点,重定向的应该是sockid而不是fin=fdopen(sockid,"w").