├── Makefile ├── README ├── example ├── README.md ├── UDPclient.cpp ├── UDPserver.cpp ├── echoclient.cpp └── echoserver.cpp ├── htdocs ├── README ├── check.cgi ├── color.cgi └── index.html ├── httpd.c └── simpleclient.c /Makefile: -------------------------------------------------------------------------------- 1 | all: httpd 2 | 3 | httpd: httpd.c 4 | gcc -W -Wall -lpthread -o httpd httpd.c 5 | 6 | clean: 7 | rm httpd 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | tinyhttpd 是一个简易的 http 服务器,支持CGI。代码量少,非常容易阅读,十分适合网络编程初学者学习的项目。 2 | 麻雀虽小,五脏俱全。在tinyhttpd中可以学到 linux 上进程的创建,管道的使用。linux 下 socket 编程基本方法和http 协议的最基本结构。 3 | 在学习过程中本人结合了《The linux programming interface》这本书在代码中做了详细的注释。如果对某些函数不熟悉或未曾见过的话,阅读本注释版是一个提高学习效率很好途径。 4 | 注释版只对部分重要的代码做注释,其余一切都保留下来,并未做任何的修改,包括源代码的缩进 :) 5 | 6 | example 文件夹不属于原项目,是本人写的简单示例代码 7 | 8 | 以下内容为原来 tinyhttpd 的 README 原版内容。 9 | 10 | 11 | This software is copyright 1999 by J. David Blackstone. Permission 12 | is granted to redistribute and modify this software under the terms of 13 | the GNU General Public License, available at http://www.gnu.org/ . 14 | 15 | If you use this software or examine the code, I would appreciate 16 | knowing and would be overjoyed to hear about it at 17 | jdavidb@sourceforge.net . 18 | 19 | This software is not production quality. It comes with no warranty 20 | of any kind, not even an implied warranty of fitness for a particular 21 | purpose. I am not responsible for the damage that will likely result 22 | if you use this software on your computer system. 23 | 24 | I wrote this webserver for an assignment in my networking class in 25 | 1999. We were told that at a bare minimum the server had to serve 26 | pages, and told that we would get extra credit for doing "extras." 27 | Perl had introduced me to a whole lot of UNIX functionality (I learned 28 | sockets and fork from Perl!), and O'Reilly's lion book on UNIX system 29 | calls plus O'Reilly's books on CGI and writing web clients in Perl got 30 | me thinking and I realized I could make my webserver support CGI with 31 | little trouble. 32 | 33 | Now, if you're a member of the Apache core group, you might not be 34 | impressed. But my professor was blown over. Try the color.cgi sample 35 | script and type in "chartreuse." Made me seem smarter than I am, at 36 | any rate. :) 37 | 38 | Apache it's not. But I do hope that this program is a good 39 | educational tool for those interested in http/socket programming, as 40 | well as UNIX system calls. (There's some textbook uses of pipes, 41 | environment variables, forks, and so on.) 42 | 43 | One last thing: if you look at my webserver or (are you out of 44 | mind?!?) use it, I would just be overjoyed to hear about it. Please 45 | email me. I probably won't really be releasing major updates, but if 46 | I help you learn something, I'd love to know! 47 | 48 | Happy hacking! 49 | 50 | J. David Blackstone 51 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | echoclient.cpp : 单进程单线程 echo 客户端 (AF_INET) 2 | 3 | echoserver.cpp : 单进程单线程 echo 服务端 (AF_INET) 4 | 5 | UDPserver.cpp : 单进程单线程的 UDP 服务端 (AF_INET) 6 | 7 | UDPclient.cpp : 单进程单线程的 UDP 客户端 (AF_INET) 8 | -------------------------------------------------------------------------------- /example/UDPclient.cpp: -------------------------------------------------------------------------------- 1 | /*linux socket AF_INET UDP 编程示例,客户端,单进程单线程。*/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() 9 | { 10 | int cli_sock = socket(AF_INET, SOCK_DGRAM, 0); 11 | 12 | //conn_addr 是要连接的服务器地址结构 13 | struct sockaddr_in conn_addr; 14 | conn_addr.sin_family = AF_INET; 15 | conn_addr.sin_port = htons(8345); 16 | //conn_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 17 | //将 inet_addr() 改用 inet_pton() 这个现代的方法,支持 IPV4 和 IPV6 18 | if (inet_pton(AF_INET, "127.0.0.1", &conn_addr.sin_addr) == -1) { 19 | printf("inet_pton error\n"); 20 | close(cli_sock); 21 | return 0; 22 | } 23 | 24 | //serv_addr 是用来存储 recvfrom 中的地址结构 25 | struct sockaddr_in serv_addr; 26 | socklen_t serv_addr_len = sizeof(serv_addr); 27 | 28 | char c = 1, buf[255]; 29 | int num = 0; 30 | while (c < 100) { 31 | sendto(cli_sock, &c, sizeof(c), 0, 32 | (struct sockaddr*)&conn_addr, 33 | sizeof(conn_addr) ); 34 | int n = recvfrom(cli_sock, buf, sizeof(buf), 0, 35 | (struct sockaddr*)&serv_addr, 36 | &serv_addr_len ); 37 | 38 | if (n > 0) { 39 | num++; 40 | //将 inet_ntoa() 改用 inet_ntop() 这个现代方法,支持 IPV4 和 IPV6 41 | //printf("recv data from %s : %d %c\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port), buf[0]); 42 | //INET_ADDRSTRLEN 是标识 IPV4 地址展现字符串的大小常量,INET6_ADDRSTRLEN是 IPV6 的 43 | char serv_ip[INET_ADDRSTRLEN]; 44 | if (inet_ntop(AF_INET, &serv_addr.sin_addr, serv_ip, sizeof(serv_ip)) == NULL) { 45 | printf("inet_ntop error\n"); 46 | close(cli_sock); 47 | return 0; 48 | } 49 | printf("recv data from %s : %d %c\n", serv_ip, ntohs(serv_addr.sin_port), buf[0]); 50 | } 51 | ++c; 52 | } 53 | c='\0'; 54 | sendto(cli_sock, &c, sizeof(c), 0, 55 | (struct sockaddr*)&conn_addr, 56 | sizeof(conn_addr) ); 57 | close(cli_sock); 58 | printf("\nnum : %d\n", num); 59 | return 0; 60 | } -------------------------------------------------------------------------------- /example/UDPserver.cpp: -------------------------------------------------------------------------------- 1 | /*linux socket AF_INET UDP 编程示例,客户端,单进程单线程。*/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() 9 | { 10 | int serv_sock = socket(AF_INET, SOCK_DGRAM, 0); 11 | 12 | //服务端自己的地址结构 13 | struct sockaddr_in serv_addr; 14 | serv_addr.sin_family = AF_INET; 15 | serv_addr.sin_port = htons(8345); 16 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 17 | 18 | //将 inet_ntoa() 改用 inet_ntop() 这个现代方法,支持 IPV4 和 IPV6 19 | //printf("bind in %s : %d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port)); 20 | //INET_ADDRSTRLEN 是标识 IPV4 地址展现字符串的大小常量,INET6_ADDRSTRLEN是 IPV6 的 21 | char serv_ip[INET_ADDRSTRLEN]; 22 | if (inet_ntop(AF_INET, &serv_addr.sin_addr, serv_ip, sizeof(serv_ip)) == NULL) { 23 | printf("inet_ntop error\n"); 24 | close(serv_sock); 25 | return 0; 26 | } 27 | printf("bind in %s : %d\n", serv_ip, ntohs(serv_addr.sin_port)); 28 | 29 | if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { 30 | printf("bind error\n"); 31 | return 0; 32 | } 33 | 34 | //客户端的地址结构 35 | struct sockaddr_in cli_addr; 36 | socklen_t cli_addr_len = sizeof(cli_addr); 37 | 38 | char buf[255]; 39 | int num = 0; 40 | ssize_t n = recvfrom(serv_sock, buf, sizeof(buf), 0, 41 | (struct sockaddr*)&cli_addr, 42 | &cli_addr_len ); 43 | while (buf[0] != '\0') { 44 | num++; 45 | printf("recv data from %s : %d %c\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf[0]); 46 | ssize_t nn = sendto(serv_sock, buf, sizeof(buf), 0, 47 | (struct sockaddr*)&cli_addr, 48 | sizeof(cli_addr) ); 49 | 50 | if (nn == -1) 51 | printf("sendto error\n"); 52 | n = recvfrom(serv_sock, buf, sizeof(buf), 0, 53 | (struct sockaddr*)&cli_addr, 54 | &cli_addr_len ); 55 | } 56 | printf("\nnum: %d\n", num); 57 | close(serv_sock); 58 | return 0; 59 | } -------------------------------------------------------------------------------- /example/echoclient.cpp: -------------------------------------------------------------------------------- 1 | /*linux socket AF_INET TCP 编程示例,单进程单线程,ehco 客户端*/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | int cli_sock = socket(AF_INET, SOCK_STREAM, 0); 12 | 13 | struct sockaddr_in serv_addr; 14 | serv_addr.sin_family = AF_INET; 15 | serv_addr.sin_port = htons(8123); 16 | //serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 17 | //将 inet_addr() 改用 inet_pton() 这个现代的方法,支持 IPV4 和 IPV6 18 | if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) == -1) { 19 | printf("inet_pton error\n"); 20 | close(cli_sock); 21 | return 0; 22 | } 23 | 24 | //将 inet_ntoa() 改用 inet_ntop() 这个现代方法,支持 IPV4 和 IPV6 25 | //printf("bind in %s : %d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port)); 26 | //INET_ADDRSTRLEN 是标识 IPV4 地址展现字符串的大小常量,INET6_ADDRSTRLEN是 IPV6 的 27 | char serv_ip[INET_ADDRSTRLEN]; 28 | if (inet_ntop(AF_INET, &serv_addr.sin_addr, serv_ip, sizeof(serv_ip)) == NULL) { 29 | printf("inet_ntop error\n"); 30 | close(cli_sock); 31 | return 0; 32 | } 33 | printf("bind in %s : %d\n", serv_ip, ntohs(serv_addr.sin_port)); 34 | 35 | int conn = connect(cli_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 36 | if (conn == -1) { 37 | printf("connect error\n"); 38 | return 0; 39 | } 40 | 41 | char s[] = "hello world"; 42 | char buf[256]; 43 | write(cli_sock, s, strlen(s) + 1); 44 | read(cli_sock, buf, sizeof(buf)); 45 | printf("read %s\n", buf); 46 | 47 | close(cli_sock); 48 | return 0; 49 | } -------------------------------------------------------------------------------- /example/echoserver.cpp: -------------------------------------------------------------------------------- 1 | /*linux socket AF_INET TCP 编程示例,单进程单线程,ehco 服务端*/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int main() 13 | { 14 | int server_sock = socket(AF_INET, SOCK_STREAM, 0); 15 | 16 | struct sockaddr_in serv_addr; 17 | int serv_addr_len = sizeof(serv_addr); 18 | serv_addr.sin_family = AF_INET; 19 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 20 | serv_addr.sin_port = htons(8123); 21 | 22 | //将 inet_ntoa() 改用 inet_ntop() 这个现代方法,支持 IPV4 和 IPV6 23 | //printf("bind in %s : %d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port)); 24 | //INET_ADDRSTRLEN 是标识 IPV4 地址展现字符串的大小常量,INET6_ADDRSTRLEN是 IPV6 的 25 | char serv_ip[INET_ADDRSTRLEN]; 26 | if (inet_ntop(AF_INET, &serv_addr.sin_addr, serv_ip, sizeof(serv_ip)) == NULL) { 27 | printf("inet_ntop error\n"); 28 | close(server_sock); 29 | return 0; 30 | } 31 | 32 | printf("bind in %s : %d\n", serv_ip, ntohs(serv_addr.sin_port)); 33 | if (bind(server_sock, (struct sockaddr*)&serv_addr, serv_addr_len) < 0) { 34 | printf("bind error\n"); 35 | return 0; 36 | } 37 | 38 | if (listen(server_sock, 1)) { 39 | printf("listen error\n"); 40 | return 0; 41 | } 42 | 43 | char buf[1024]; 44 | while (1) { 45 | struct sockaddr_in client_addr; 46 | int client_addr_len = sizeof(client_addr); 47 | 48 | printf("等待链接的到来\n"); 49 | int client_sock = accept( server_sock, 50 | (struct sockaddr*)&client_addr, 51 | (unsigned int *)&client_addr_len ); 52 | 53 | if (client_sock == -1) { 54 | printf("accept error\n"); 55 | return 0; 56 | } 57 | printf("有新连接到来\n"); 58 | int n = recv(client_sock, buf, sizeof(buf), 0); 59 | while (n > 0) { 60 | if (send(client_sock, buf, sizeof(buf), 0) < 0) { 61 | printf("send error\n"); 62 | return 0; 63 | } 64 | n = recv(client_sock, buf, sizeof(buf), 0); 65 | } 66 | close(client_sock); 67 | printf("连接离开\n"); 68 | } 69 | 70 | close(server_sock); 71 | return 0; 72 | } -------------------------------------------------------------------------------- /htdocs/README: -------------------------------------------------------------------------------- 1 | These are sample CGI scripts and webpages for tinyhttpd. They can 2 | be redistributed under the terms of the GPL. 3 | 4 | The most impressive demonstration I gave of tinyhttpd to my 5 | professor and my classmates was to load color.cgi with a value of 6 | "chartreuse." :) It's actually a very simple script, guys. 7 | 8 | jdb 9 | -------------------------------------------------------------------------------- /htdocs/check.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -Tw 2 | 3 | use strict; 4 | use CGI; 5 | 6 | my($cgi) = new CGI; 7 | 8 | print $cgi->header('text/html'); 9 | print $cgi->start_html(-title => "Example CGI script", 10 | -BGCOLOR => 'red'); 11 | print $cgi->h1("CGI Example"); 12 | print $cgi->p, "This is an example of CGI\n"; 13 | print $cgi->p, "Parameters given to this script:\n"; 14 | print "
    \n"; 15 | foreach my $param ($cgi->param) 16 | { 17 | print "
  • ", "$param ", $cgi->param($param), "\n"; 18 | } 19 | print "
"; 20 | print $cgi->end_html, "\n"; 21 | -------------------------------------------------------------------------------- /htdocs/color.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -Tw 2 | 3 | use strict; 4 | use CGI; 5 | 6 | my($cgi) = new CGI; 7 | 8 | print $cgi->header; 9 | my($color) = "blue"; 10 | $color = $cgi->param('color') if defined $cgi->param('color'); 11 | 12 | print $cgi->start_html(-title => uc($color), 13 | -BGCOLOR => $color); 14 | print $cgi->h1("This is $color"); 15 | print $cgi->end_html; 16 | -------------------------------------------------------------------------------- /htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | Index 3 | 4 |

Welcome to J. David's webserver. 5 |

CGI demo 6 |
7 | Enter a color: 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /httpd.c: -------------------------------------------------------------------------------- 1 | /* J. David's webserver */ 2 | /* This is a simple webserver. 3 | * Created November 1999 by J. David Blackstone. 4 | * CSE 4344 (Network concepts), Prof. Zeigler 5 | * University of Texas at Arlington 6 | */ 7 | /* This program compiles for Sparc Solaris 2.6. 8 | * To compile for Linux: 9 | * 1) Comment out the #include line. 10 | * 2) Comment out the line that defines the variable newthread. 11 | * 3) Comment out the two lines that run pthread_create(). 12 | * 4) Uncomment the line that runs accept_request(). 13 | * 5) Remove -lsocket from the Makefile. 14 | */ 15 | 16 | /* 17 | 代码中除了用到 C 语言标准库的一些函数,也用到了一些与环境有关的函数(例如POSIX标准) 18 | 具体可以参读《The Linux Programming Interface》,以下简称《TLPI》,页码指示均为英文版 19 | 20 | 注释者: github: cbsheng 21 | */ 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | //#include 33 | #include 34 | #include 35 | 36 | #define ISspace(x) isspace((int)(x)) 37 | 38 | #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" 39 | 40 | void accept_request(int); 41 | void bad_request(int); 42 | void cat(int, FILE *); 43 | void cannot_execute(int); 44 | void error_die(const char *); 45 | void execute_cgi(int, const char *, const char *, const char *); 46 | int get_line(int, char *, int); 47 | void headers(int, const char *); 48 | void not_found(int); 49 | void serve_file(int, const char *); 50 | int startup(u_short *); 51 | void unimplemented(int); 52 | 53 | /**********************************************************************/ 54 | /* A request has caused a call to accept() on the server port to 55 | * return. Process the request appropriately. 56 | * Parameters: the socket connected to the client */ 57 | /**********************************************************************/ 58 | void accept_request(int client) 59 | { 60 | char buf[1024]; 61 | int numchars; 62 | char method[255]; 63 | char url[255]; 64 | char path[512]; 65 | size_t i, j; 66 | struct stat st; 67 | int cgi = 0; /* becomes true if server decides this is a CGI 68 | * program */ 69 | char *query_string = NULL; 70 | 71 | //读http 请求的第一行数据(request line),把请求方法存进 method 中 72 | numchars = get_line(client, buf, sizeof(buf)); 73 | i = 0; j = 0; 74 | while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 75 | { 76 | method[i] = buf[j]; 77 | i++; j++; 78 | } 79 | method[i] = '\0'; 80 | 81 | //如果请求的方法不是 GET 或 POST 任意一个的话就直接发送 response 告诉客户端没实现该方法 82 | if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 83 | { 84 | unimplemented(client); 85 | return; 86 | } 87 | 88 | //如果是 POST 方法就将 cgi 标志变量置一(true) 89 | if (strcasecmp(method, "POST") == 0) 90 | cgi = 1; 91 | 92 | i = 0; 93 | //跳过所有的空白字符(空格) 94 | while (ISspace(buf[j]) && (j < sizeof(buf))) 95 | j++; 96 | 97 | //然后把 URL 读出来放到 url 数组中 98 | while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 99 | { 100 | url[i] = buf[j]; 101 | i++; j++; 102 | } 103 | url[i] = '\0'; 104 | 105 | //如果这个请求是一个 GET 方法的话 106 | if (strcasecmp(method, "GET") == 0) 107 | { 108 | //用一个指针指向 url 109 | query_string = url; 110 | 111 | //去遍历这个 url,跳过字符 ?前面的所有字符,如果遍历完毕也没找到字符 ?则退出循环 112 | while ((*query_string != '?') && (*query_string != '\0')) 113 | query_string++; 114 | 115 | //退出循环后检查当前的字符是 ?还是字符串(url)的结尾 116 | if (*query_string == '?') 117 | { 118 | //如果是 ? 的话,证明这个请求需要调用 cgi,将 cgi 标志变量置一(true) 119 | cgi = 1; 120 | //从字符 ? 处把字符串 url 给分隔会两份 121 | *query_string = '\0'; 122 | //使指针指向字符 ?后面的那个字符 123 | query_string++; 124 | } 125 | } 126 | 127 | //将前面分隔两份的前面那份字符串,拼接在字符串htdocs的后面之后就输出存储到数组 path 中。相当于现在 path 中存储着一个字符串 128 | sprintf(path, "htdocs%s", url); 129 | 130 | //如果 path 数组中的这个字符串的最后一个字符是以字符 / 结尾的话,就拼接上一个"index.html"的字符串。首页的意思 131 | if (path[strlen(path) - 1] == '/') 132 | strcat(path, "index.html"); 133 | 134 | //在系统上去查询该文件是否存在 135 | if (stat(path, &st) == -1) { 136 | //如果不存在,那把这次 http 的请求后续的内容(head 和 body)全部读完并忽略 137 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 138 | numchars = get_line(client, buf, sizeof(buf)); 139 | //然后返回一个找不到文件的 response 给客户端 140 | not_found(client); 141 | } 142 | else 143 | { 144 | //文件存在,那去跟常量S_IFMT相与,相与之后的值可以用来判断该文件是什么类型的 145 | //S_IFMT参读《TLPI》P281,与下面的三个常量一样是包含在 146 | if ((st.st_mode & S_IFMT) == S_IFDIR) 147 | //如果这个文件是个目录,那就需要再在 path 后面拼接一个"/index.html"的字符串 148 | strcat(path, "/index.html"); 149 | 150 | //S_IXUSR, S_IXGRP, S_IXOTH三者可以参读《TLPI》P295 151 | if ((st.st_mode & S_IXUSR) || 152 | (st.st_mode & S_IXGRP) || 153 | (st.st_mode & S_IXOTH) ) 154 | //如果这个文件是一个可执行文件,不论是属于用户/组/其他这三者类型的,就将 cgi 标志变量置一 155 | cgi = 1; 156 | 157 | if (!cgi) 158 | //如果不需要 cgi 机制的话, 159 | serve_file(client, path); 160 | else 161 | //如果需要则调用 162 | execute_cgi(client, path, method, query_string); 163 | } 164 | 165 | close(client); 166 | } 167 | 168 | /**********************************************************************/ 169 | /* Inform the client that a request it has made has a problem. 170 | * Parameters: client socket */ 171 | /**********************************************************************/ 172 | void bad_request(int client) 173 | { 174 | char buf[1024]; 175 | 176 | sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 177 | send(client, buf, sizeof(buf), 0); 178 | sprintf(buf, "Content-type: text/html\r\n"); 179 | send(client, buf, sizeof(buf), 0); 180 | sprintf(buf, "\r\n"); 181 | send(client, buf, sizeof(buf), 0); 182 | sprintf(buf, "

Your browser sent a bad request, "); 183 | send(client, buf, sizeof(buf), 0); 184 | sprintf(buf, "such as a POST without a Content-Length.\r\n"); 185 | send(client, buf, sizeof(buf), 0); 186 | } 187 | 188 | /**********************************************************************/ 189 | /* Put the entire contents of a file out on a socket. This function 190 | * is named after the UNIX "cat" command, because it might have been 191 | * easier just to do something like pipe, fork, and exec("cat"). 192 | * Parameters: the client socket descriptor 193 | * FILE pointer for the file to cat */ 194 | /**********************************************************************/ 195 | void cat(int client, FILE *resource) 196 | { 197 | char buf[1024]; 198 | 199 | //从文件文件描述符中读取指定内容 200 | fgets(buf, sizeof(buf), resource); 201 | while (!feof(resource)) 202 | { 203 | send(client, buf, strlen(buf), 0); 204 | fgets(buf, sizeof(buf), resource); 205 | } 206 | } 207 | 208 | /**********************************************************************/ 209 | /* Inform the client that a CGI script could not be executed. 210 | * Parameter: the client socket descriptor. */ 211 | /**********************************************************************/ 212 | void cannot_execute(int client) 213 | { 214 | char buf[1024]; 215 | 216 | sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 217 | send(client, buf, strlen(buf), 0); 218 | sprintf(buf, "Content-type: text/html\r\n"); 219 | send(client, buf, strlen(buf), 0); 220 | sprintf(buf, "\r\n"); 221 | send(client, buf, strlen(buf), 0); 222 | sprintf(buf, "

Error prohibited CGI execution.\r\n"); 223 | send(client, buf, strlen(buf), 0); 224 | } 225 | 226 | /**********************************************************************/ 227 | /* Print out an error message with perror() (for system errors; based 228 | * on value of errno, which indicates system call errors) and exit the 229 | * program indicating an error. */ 230 | /**********************************************************************/ 231 | void error_die(const char *sc) 232 | { 233 | //包含于,基于当前的 errno 值,在标准错误上产生一条错误消息。参考《TLPI》P49 234 | perror(sc); 235 | exit(1); 236 | } 237 | 238 | /**********************************************************************/ 239 | /* Execute a CGI script. Will need to set environment variables as 240 | * appropriate. 241 | * Parameters: client socket descriptor 242 | * path to the CGI script */ 243 | /**********************************************************************/ 244 | void execute_cgi(int client, const char *path, 245 | const char *method, const char *query_string) 246 | { 247 | char buf[1024]; 248 | int cgi_output[2]; 249 | int cgi_input[2]; 250 | pid_t pid; 251 | int status; 252 | int i; 253 | char c; 254 | int numchars = 1; 255 | int content_length = -1; 256 | 257 | //往 buf 中填东西以保证能进入下面的 while 258 | buf[0] = 'A'; buf[1] = '\0'; 259 | //如果是 http 请求是 GET 方法的话读取并忽略请求剩下的内容 260 | if (strcasecmp(method, "GET") == 0) 261 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 262 | numchars = get_line(client, buf, sizeof(buf)); 263 | else /* POST */ 264 | { 265 | //只有 POST 方法才继续读内容 266 | numchars = get_line(client, buf, sizeof(buf)); 267 | //这个循环的目的是读出指示 body 长度大小的参数,并记录 body 的长度大小。其余的 header 里面的参数一律忽略 268 | //注意这里只读完 header 的内容,body 的内容没有读 269 | while ((numchars > 0) && strcmp("\n", buf)) 270 | { 271 | buf[15] = '\0'; 272 | if (strcasecmp(buf, "Content-Length:") == 0) 273 | content_length = atoi(&(buf[16])); //记录 body 的长度大小 274 | numchars = get_line(client, buf, sizeof(buf)); 275 | } 276 | 277 | //如果 http 请求的 header 没有指示 body 长度大小的参数,则报错返回 278 | if (content_length == -1) { 279 | bad_request(client); 280 | return; 281 | } 282 | } 283 | 284 | sprintf(buf, "HTTP/1.0 200 OK\r\n"); 285 | send(client, buf, strlen(buf), 0); 286 | 287 | //下面这里创建两个管道,用于两个进程间通信 288 | if (pipe(cgi_output) < 0) { 289 | cannot_execute(client); 290 | return; 291 | } 292 | if (pipe(cgi_input) < 0) { 293 | cannot_execute(client); 294 | return; 295 | } 296 | 297 | //创建一个子进程 298 | if ( (pid = fork()) < 0 ) { 299 | cannot_execute(client); 300 | return; 301 | } 302 | 303 | //子进程用来执行 cgi 脚本 304 | if (pid == 0) /* child: CGI script */ 305 | { 306 | char meth_env[255]; 307 | char query_env[255]; 308 | char length_env[255]; 309 | 310 | //dup2()包含中,参读《TLPI》P97 311 | //将子进程的输出由标准输出重定向到 cgi_ouput 的管道写端上 312 | dup2(cgi_output[1], 1); 313 | //将子进程的输出由标准输入重定向到 cgi_ouput 的管道读端上 314 | dup2(cgi_input[0], 0); 315 | //关闭 cgi_ouput 管道的读端与cgi_input 管道的写端 316 | close(cgi_output[0]); 317 | close(cgi_input[1]); 318 | 319 | //构造一个环境变量 320 | sprintf(meth_env, "REQUEST_METHOD=%s", method); 321 | //putenv()包含于中,参读《TLPI》P128 322 | //将这个环境变量加进子进程的运行环境中 323 | putenv(meth_env); 324 | 325 | //根据http 请求的不同方法,构造并存储不同的环境变量 326 | if (strcasecmp(method, "GET") == 0) { 327 | sprintf(query_env, "QUERY_STRING=%s", query_string); 328 | putenv(query_env); 329 | } 330 | else { /* POST */ 331 | sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 332 | putenv(length_env); 333 | } 334 | 335 | //execl()包含于中,参读《TLPI》P567 336 | //最后将子进程替换成另一个进程并执行 cgi 脚本 337 | execl(path, path, NULL); 338 | exit(0); 339 | 340 | } else { /* parent */ 341 | //父进程则关闭了 cgi_output管道的写端和 cgi_input 管道的读端 342 | close(cgi_output[1]); 343 | close(cgi_input[0]); 344 | 345 | //如果是 POST 方法的话就继续读 body 的内容,并写到 cgi_input 管道里让子进程去读 346 | if (strcasecmp(method, "POST") == 0) 347 | for (i = 0; i < content_length; i++) { 348 | recv(client, &c, 1, 0); 349 | write(cgi_input[1], &c, 1); 350 | } 351 | 352 | //然后从 cgi_output 管道中读子进程的输出,并发送到客户端去 353 | while (read(cgi_output[0], &c, 1) > 0) 354 | send(client, &c, 1, 0); 355 | 356 | //关闭管道 357 | close(cgi_output[0]); 358 | close(cgi_input[1]); 359 | //等待子进程的退出 360 | waitpid(pid, &status, 0); 361 | } 362 | } 363 | 364 | /**********************************************************************/ 365 | /* Get a line from a socket, whether the line ends in a newline, 366 | * carriage return, or a CRLF combination. Terminates the string read 367 | * with a null character. If no newline indicator is found before the 368 | * end of the buffer, the string is terminated with a null. If any of 369 | * the above three line terminators is read, the last character of the 370 | * string will be a linefeed and the string will be terminated with a 371 | * null character. 372 | * Parameters: the socket descriptor 373 | * the buffer to save the data in 374 | * the size of the buffer 375 | * Returns: the number of bytes stored (excluding null) */ 376 | /**********************************************************************/ 377 | int get_line(int sock, char *buf, int size) 378 | { 379 | int i = 0; 380 | char c = '\0'; 381 | int n; 382 | 383 | while ((i < size - 1) && (c != '\n')) 384 | { 385 | //recv()包含于,参读《TLPI》P1259, 386 | //读一个字节的数据存放在 c 中 387 | n = recv(sock, &c, 1, 0); 388 | /* DEBUG printf("%02X\n", c); */ 389 | if (n > 0) 390 | { 391 | if (c == '\r') 392 | { 393 | // 394 | n = recv(sock, &c, 1, MSG_PEEK); 395 | /* DEBUG printf("%02X\n", c); */ 396 | if ((n > 0) && (c == '\n')) 397 | recv(sock, &c, 1, 0); 398 | else 399 | c = '\n'; 400 | } 401 | buf[i] = c; 402 | i++; 403 | } 404 | else 405 | c = '\n'; 406 | } 407 | buf[i] = '\0'; 408 | 409 | return(i); 410 | } 411 | 412 | /**********************************************************************/ 413 | /* Return the informational HTTP headers about a file. */ 414 | /* Parameters: the socket to print the headers on 415 | * the name of the file */ 416 | /**********************************************************************/ 417 | void headers(int client, const char *filename) 418 | { 419 | char buf[1024]; 420 | (void)filename; /* could use filename to determine file type */ 421 | 422 | strcpy(buf, "HTTP/1.0 200 OK\r\n"); 423 | send(client, buf, strlen(buf), 0); 424 | strcpy(buf, SERVER_STRING); 425 | send(client, buf, strlen(buf), 0); 426 | sprintf(buf, "Content-Type: text/html\r\n"); 427 | send(client, buf, strlen(buf), 0); 428 | strcpy(buf, "\r\n"); 429 | send(client, buf, strlen(buf), 0); 430 | } 431 | 432 | /**********************************************************************/ 433 | /* Give a client a 404 not found status message. */ 434 | /**********************************************************************/ 435 | void not_found(int client) 436 | { 437 | char buf[1024]; 438 | 439 | sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 440 | send(client, buf, strlen(buf), 0); 441 | sprintf(buf, SERVER_STRING); 442 | send(client, buf, strlen(buf), 0); 443 | sprintf(buf, "Content-Type: text/html\r\n"); 444 | send(client, buf, strlen(buf), 0); 445 | sprintf(buf, "\r\n"); 446 | send(client, buf, strlen(buf), 0); 447 | sprintf(buf, "Not Found\r\n"); 448 | send(client, buf, strlen(buf), 0); 449 | sprintf(buf, "

The server could not fulfill\r\n"); 450 | send(client, buf, strlen(buf), 0); 451 | sprintf(buf, "your request because the resource specified\r\n"); 452 | send(client, buf, strlen(buf), 0); 453 | sprintf(buf, "is unavailable or nonexistent.\r\n"); 454 | send(client, buf, strlen(buf), 0); 455 | sprintf(buf, "\r\n"); 456 | send(client, buf, strlen(buf), 0); 457 | } 458 | 459 | /**********************************************************************/ 460 | /* Send a regular file to the client. Use headers, and report 461 | * errors to client if they occur. 462 | * Parameters: a pointer to a file structure produced from the socket 463 | * file descriptor 464 | * the name of the file to serve */ 465 | /**********************************************************************/ 466 | void serve_file(int client, const char *filename) 467 | { 468 | FILE *resource = NULL; 469 | int numchars = 1; 470 | char buf[1024]; 471 | 472 | //确保 buf 里面有东西,能进入下面的 while 循环 473 | buf[0] = 'A'; buf[1] = '\0'; 474 | //循环作用是读取并忽略掉这个 http 请求后面的所有内容 475 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 476 | numchars = get_line(client, buf, sizeof(buf)); 477 | 478 | //打开这个传进来的这个路径所指的文件 479 | resource = fopen(filename, "r"); 480 | if (resource == NULL) 481 | not_found(client); 482 | else 483 | { 484 | //打开成功后,将这个文件的基本信息封装成 response 的头部(header)并返回 485 | headers(client, filename); 486 | //接着把这个文件的内容读出来作为 response 的 body 发送到客户端 487 | cat(client, resource); 488 | } 489 | 490 | fclose(resource); 491 | } 492 | 493 | /**********************************************************************/ 494 | /* This function starts the process of listening for web connections 495 | * on a specified port. If the port is 0, then dynamically allocate a 496 | * port and modify the original port variable to reflect the actual 497 | * port. 498 | * Parameters: pointer to variable containing the port to connect on 499 | * Returns: the socket */ 500 | /**********************************************************************/ 501 | int startup(u_short *port) 502 | { 503 | int httpd = 0; 504 | //sockaddr_in 是 IPV4的套接字地址结构。定义在,参读《TLPI》P1202 505 | struct sockaddr_in name; 506 | 507 | //socket()用于创建一个用于 socket 的描述符,函数包含于。参读《TLPI》P1153 508 | //这里的PF_INET其实是与 AF_INET同义,具体可以参读《TLPI》P946 509 | httpd = socket(PF_INET, SOCK_STREAM, 0); 510 | if (httpd == -1) 511 | error_die("socket"); 512 | 513 | memset(&name, 0, sizeof(name)); 514 | name.sin_family = AF_INET; 515 | //htons(),ntohs() 和 htonl()包含于, 参读《TLPI》P1199 516 | //将*port 转换成以网络字节序表示的16位整数 517 | name.sin_port = htons(*port); 518 | //INADDR_ANY是一个 IPV4通配地址的常量,包含于 519 | //大多实现都将其定义成了0.0.0.0 参读《TLPI》P1187 520 | name.sin_addr.s_addr = htonl(INADDR_ANY); 521 | 522 | //bind()用于绑定地址与 socket。参读《TLPI》P1153 523 | //如果传进去的sockaddr结构中的 sin_port 指定为0,这时系统会选择一个临时的端口号 524 | if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 525 | error_die("bind"); 526 | 527 | //如果调用 bind 后端口号仍然是0,则手动调用getsockname()获取端口号 528 | if (*port == 0) /* if dynamically allocating a port */ 529 | { 530 | int namelen = sizeof(name); 531 | //getsockname()包含于中,参读《TLPI》P1263 532 | //调用getsockname()获取系统给 httpd 这个 socket 随机分配的端口号 533 | if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 534 | error_die("getsockname"); 535 | *port = ntohs(name.sin_port); 536 | } 537 | 538 | //最初的 BSD socket 实现中,backlog 的上限是5.参读《TLPI》P1156 539 | if (listen(httpd, 5) < 0) 540 | error_die("listen"); 541 | return(httpd); 542 | } 543 | 544 | /**********************************************************************/ 545 | /* Inform the client that the requested web method has not been 546 | * implemented. 547 | * Parameter: the client socket */ 548 | /**********************************************************************/ 549 | void unimplemented(int client) 550 | { 551 | char buf[1024]; 552 | 553 | sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 554 | send(client, buf, strlen(buf), 0); 555 | sprintf(buf, SERVER_STRING); 556 | send(client, buf, strlen(buf), 0); 557 | sprintf(buf, "Content-Type: text/html\r\n"); 558 | send(client, buf, strlen(buf), 0); 559 | sprintf(buf, "\r\n"); 560 | send(client, buf, strlen(buf), 0); 561 | sprintf(buf, "Method Not Implemented\r\n"); 562 | send(client, buf, strlen(buf), 0); 563 | sprintf(buf, "\r\n"); 564 | send(client, buf, strlen(buf), 0); 565 | sprintf(buf, "

HTTP request method not supported.\r\n"); 566 | send(client, buf, strlen(buf), 0); 567 | sprintf(buf, "\r\n"); 568 | send(client, buf, strlen(buf), 0); 569 | } 570 | 571 | /**********************************************************************/ 572 | 573 | int main(void) 574 | { 575 | int server_sock = -1; 576 | u_short port = 0; 577 | int client_sock = -1; 578 | //sockaddr_in 是 IPV4的套接字地址结构。定义在,参读《TLPI》P1202 579 | struct sockaddr_in client_name; 580 | int client_name_len = sizeof(client_name); 581 | //pthread_t newthread; 582 | 583 | server_sock = startup(&port); 584 | printf("httpd running on port %d\n", port); 585 | 586 | while (1) 587 | { 588 | //阻塞等待客户端的连接,参读《TLPI》P1157 589 | client_sock = accept(server_sock, 590 | (struct sockaddr *)&client_name, 591 | &client_name_len); 592 | if (client_sock == -1) 593 | error_die("accept"); 594 | accept_request(client_sock); 595 | /*if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0) 596 | perror("pthread_create");*/ 597 | } 598 | 599 | close(server_sock); 600 | 601 | return(0); 602 | } 603 | -------------------------------------------------------------------------------- /simpleclient.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | int sockfd; 11 | int len; 12 | struct sockaddr_in address; 13 | int result; 14 | char ch = 'A'; 15 | 16 | //申请一个流 socket 17 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 18 | //填充地址结构,指定服务器的 IP 和 端口 19 | address.sin_family = AF_INET; 20 | //inet_addr 可以参考 man inet_addr 21 | //可以用现代的inet_pton()替代inet_addr(), example 中有参考例子 22 | address.sin_addr.s_addr = inet_addr("127.0.0.1"); 23 | address.sin_port = htons(9734); 24 | len = sizeof(address); 25 | 26 | //下面的语句可以输出连接的 IP 地址 27 | //但是inet_ntoa()是过时的方法,应该改用 inet_ntop(可参考 example)。但很多代码仍然遗留着inet_ntoa. 28 | //printf("%s\n", inet_ntoa( address.sin_addr)); 29 | 30 | result = connect(sockfd, (struct sockaddr *)&address, len); 31 | 32 | if (result == -1) 33 | { 34 | perror("oops: client1"); 35 | exit(1); 36 | } 37 | 38 | //往服务端写一个字节 39 | write(sockfd, &ch, 1); 40 | //从服务端读一个字符 41 | read(sockfd, &ch, 1); 42 | printf("char from server = %c\n", ch); 43 | close(sockfd); 44 | exit(0); 45 | } 46 | --------------------------------------------------------------------------------