Zirconi

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

算是对之前学习的东西的一个阶段总结,东西虽然简陋,应该也有值得思考的地方。

文件如下:

rio                   之前弄的rio库

Makefile            Makefile

headerc.h         本意是所谓的“HeaderCollection”,包含必须用到的各种头文件

index.html        测试用的一个小页面,默认页面是当前目录的index.html文件

main.c              似乎这么做能够叫做Daemon,那么就试试了

whd.c               核心部分

whd.h               函数声明

大致流程如下(部分):

 

main.c:

 1 #include "headerc.h"
 2 #include "whd.h"
 3 
 4 int main(void)
 5 {
 6     pid_t pid;
 7     if((pid = fork()) == 0)
 8     {
 9         close(STDOUT_FILENO);
10         close(STDIN_FILENO);
11         close(STDERR_FILENO);
12         fake_main();
13     }
14     else if(pid > 0)
15         exit(0);
16     else
17         FATAL("MAIN.FORK");
18 
19     exit(0);
20 }

(似乎这么做就能达到daemon的效果,事实也是这样)

仅仅fork一次,父进程退出,子进程执行fake_main函数。FATAL是定义在headerc.h中的一个错误宏(宏中一用到syslog就编译错误,所以暂时没有记录功能)。

 

main.c:

fake_main()

 1 void fake_main(void)
 2 {
 3     signal(SIGPIPE, SIG_IGN);
 4     int listenfd, err, i;
 5     pid_t pid;//, contact[WORKER];
 6     struct sockaddr_in servaddr;
 7 
 8     memset(&servaddr, 0, sizeof(servaddr));
 9     servaddr.sin_family = AF_INET;
10     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
11     servaddr.sin_port = htons((getuid() == 0) ? 80 : 8080);
12 
13     if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
14         FATAL("FAKE_MAIN.SOCKET");
15 
16     if((err = bind(listenfd, (SA *)&servaddr, sizeof(servaddr))) < 0)
17         FATAL("FAKE_MAIN.BIND");
18 
19     if((err = listen(listenfd, 1024)) < 0)
20         FATAL("FAKE_MAIN.LISTEN");
21 
22     for(i = 0; i < WORKER; ++i)
23     {
24         if((pid = fork()) == 0)
25             start_worker(listenfd);
26         else if(pid < 0)
27             //contact[i] = pid;
28             FATAL("FAKE_MAIN.FORK");
29     }
30 
31     for(;;)
32     {
33         //TODO
34         pause();
35     }
36 }

该函数工作很简单,首先处理SIGPIPE信号(忽略)。然后socket,bind, listen来创建一个监听套接字,然后fork生成相应个worker并进入死循环。

 

start_worker(int listenfd):

 1 void start_worker(int listenfd)
 2 {
 3     struct sockaddr_in cliaddr;
 4     struct epoll_event ev, events[MAXLEN];
 5     socklen_t clilen;
 6     int connfd, efd, nfds, n;
 7     char buf[MAXLEN], content[MAXLEN];
 8 
 9     if((efd = epoll_create(MAXLEN)) < 0)
10         FATAL("WORKER.EPOLL_CREATE");
11 
12     ev.data.fd = listenfd;
13     ev.events = EPOLLIN; //EPOLLET
14     if(epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev) < 0)
15         FATAL("WORKER.EPOLL_CTL");
16 
17 
18     for(;;)
19     {
20         clilen = sizeof(cliaddr);
21 
22         //epoll_wait Thundering Herd?
23         //EINTR?
24     again:
25         if((nfds = epoll_wait(efd, events, MAXLEN, -1)) < 0)
26         {
27             if(errno == EINTR)
28                 goto again;
29             else
30                 FATAL("WORKER.EPOLL_WAIT");
31         }
32 
33         for(n = 0; n < nfds; ++n)
34         {
35             if(events[n].data.fd == listenfd) 
36             {
37                 pthread_mutex_lock(&ALOCK);
38                 if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
39                     FATAL("WORKER.ACCEPT");
40                 pthread_mutex_unlock(&ALOCK);
41 
42                 snprintf(content, MAXLEN, "Client : %s\n", inet_ntop(AF_INET, &clilen, buf, clilen));
43                 //syslog(LOG_INFO, content);
44 
45                 ev.data.fd = connfd;
46                 ev.events = EPOLLIN;
47 
48                 if(epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev) < 0)
49                     FATAL("WORKER.EPOLL_CTL");
50             }
51             else
52             {
53                 process(events[n].data.fd, efd);              
54             }
55         }
56     }
57 }

对于每个工作子进程,该函数首先创建一个epollfd,并将传入的listenfd加入epoll,并等待EPOLLIN可读事件发生,如果可读事件是之前的listenfd,说明有新的连接,则调用accept接受连接并也加入epoll的监听。如果是其他fd的可读事件说明有请求到来,则交给process函数处理请求。(这里尝试了一下accept锁,不过似乎epoll_wait也会造成惊群?)。

 

process(int fd, int efd):

 1 void process(int fd, int efd)
 2 {
 3     char rstline[MAXLEN], method[MAXLEN], uri[MAXLEN], protocol[MAXLEN];
 4     char filename[MAXLEN], filetype[MAXLEN];
 5     char response[MAXLEN];
 6     rio_t rio;
 7     int err;
 8     struct stat sbuf;
 9     char *file;
10     rio_readinitb(&rio, fd);
11 
12     if((err = rio_readlineb(&rio, rstline, MAXLEN)) <= 0)
13     {
14         if((err = epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL)) < 0)
15             FATAL("PROCESS.EPOLL_CTL");
16         close(fd);
17     }
18     else
19     {
20         sscanf(rstline, "%s %s %s", method, uri, protocol);
21         if(strcasecmp(method, "GET"))
22             //TEST
23             ERR(fd, efd);
24         if(uri[0] != '/')
25             ERR(fd, efd);
26         process_rstheader(&rio);
27         parse_uri(uri, filename, filetype);
28         if(get_fileinfo(filename) < 0)
29             call_403(fd);
30         else
31         {
32             lstat(filename, &sbuf);
33             err = open(filename, O_RDONLY, 0);
34             file = mmap(0 ,sbuf.st_size, PROT_READ, MAP_PRIVATE, err, 0);
35             close(err);
36             sprintf(response, "HTTP/1.1 200 OK\r\n");
37             sprintf(response, "%sContent-length: %lu\r\n", response, sbuf.st_size);
38             sprintf(response, "%sContent-type: %s\r\n\r\n", response, filetype);
39             rio_writen(fd, response, strlen(response));
40             rio_writen(fd, file, sbuf.st_size);
41             munmap(file, sbuf.st_size);
42         }
43     }
44 }

处理请求的主要函数。首先获取一行,如果无内容可读,则认为对方发送了FIN,则从epoll监听中删除并关闭这个描述符。

对于读取到的一行,先按照HTTP规范分成方法,URI,协议分成三个部分存入相应数组。然后直接判断方法和URI是否合法(目前仅支持GET方法),若不合法直接调用ERR函数处理,ERR直接返回403错误和一个提示消息,并把当前描述符从监听列表中删除+关闭。

当请求合法时,首先调用process_rstheader处理请求头(简单地“吃掉”)。然后在prase_uri中生成对应的文件路径和文件类型。

然后调用get_fileinfo判断文件是否存在且能够读取到,并且是一个“文件”,否则调用call_403返回错误页面。

当一切就绪时,将文件打开并mmap映射至内存,并生成响应头返回。最后关闭文件和映射内存。

所用到的辅助函数如下:

 1 void ERR(int fd, int efd)
 2 {
 3     char re[] = "HTTP/1.1 403 Not Found\r\nContent-length: 14\r\nContent-type: text/html\r\n\r\n<p>ILLEGAL</p>";
 4     rio_writen(fd, re, strlen(re));
 5     if(epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL) < 0)
 6         FATAL("ERR.EPOLL_CTL");
 7     close(fd);
 8 }
 9 
10 void call_403(int fd)
11 {
12     char re[] = "HTTP/1.1 403 Not Found\r\nContent-length: 16\r\nContent-type: text/html\r\n\r\n<p>NOT FOUND</p>";
13     rio_writen(fd, re, strlen(re));
14 }
15 
16 void parse_uri(char *uri, char *filename, char *filetype)
17 {
18     strcpy(filename, ".");
19     strcat(filename, uri);
20     if(uri[strlen(uri) - 1] == '/')
21         strcat(filename, "index.html");
22 
23     get_filetype(filename, filetype);
24 }
25 
26 void get_filetype(char *filename, char *filetype)
27 {
28     if(strcasestr(filename, "html"))
29         strcpy(filetype, "text/html");
30     else if(strcasestr(filename, "jpg"))
31         strcpy(filetype, "image/jpeg");
32     else if(strcasestr(filename, "png"))
33         strcpy(filetype, "image/png");
34     else if(strcasestr(filename, "gif"))
35         strcpy(filetype, "image/gif");
36     else
37         strcpy(filetype, "texp/plain");
38 }
39 
40 void process_rstheader(rio_t *rp)
41 {
42     char buf[MAXLEN];
43     rio_readlineb(rp, buf, MAXLEN);
44     while(strcmp(buf, "\r\n"))
45         rio_readlineb(rp, buf, MAXLEN);
46 }
47 
48 int get_fileinfo(char *filename)
49 {
50     struct stat buf;
51     if(lstat(filename, &buf) < 0)
52         return -1;
53     if((S_ISREG(buf.st_mode)) && (S_IRUSR & buf.st_mode))
54         return 0;
55     else
56         return -1;
57 }

 

编译通过,经siege测试如下(应该没错?):

 1 zirconi@Zirx-Ubuntu:~$ siege -c 300 -r 30 http://localhost:8080
 2 ** SIEGE 2.70
 3 ** Preparing 300 concurrent users for battle.
 4 The server is now under siege..      done.
 5 Transactions:                9000 hits
 6 Availability:              100.00 %
 7 Elapsed time:               23.17 secs
 8 Data transferred:            0.96 MB
 9 Response time:                0.01 secs
10 Transaction rate:          388.43 trans/sec
11 Throughput:                0.04 MB/sec
12 Concurrency:                5.57
13 Successful transactions:        9000
14 Failed transactions:               0
15 Longest transaction:            1.07
16 Shortest transaction:            0.00

 

项目放在Github上:https://github.com/Zirconi/whd

posted on 2013-05-14 13:20  Zirconi  阅读(454)  评论(0编辑  收藏  举报