【Linux C编程】第二十一章Linux高并发WEB服务器开发之版本2
【摘要】 Linux高并发WEB服务器开发之版本2
版本2(使用libevent实现):
libevent_http.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <fcntl.h>
5 #include <sys/types.h>
6 #include <sys/socket.h>
7 #include <arpa/inet.h>
8 #include <sys/stat.h>
9 #include <string.h>
10 #include <dirent.h>
11 #include <time.h>
12 #include <signal.h>
13 #include <ctype.h>
14 #include <errno.h>
15 #include <event2/bufferevent.h>
16 #include <event2/buffer.h>
17 #include <event2/listener.h>
18 #include "libevent_http.h"
19
20 #define _HTTP_CLOSE_ "Connection: close\r\n"
21
22 int response_http(struct bufferevent *bev, const char *method, char *path)
23 {
24 if(strcasecmp("GET", method) == 0){
25 //get method ...
26 strdecode(path, path);
27 char *pf = &path[1];
28
29 if(strcmp(path, "/") == 0 || strcmp(path, "/.") == 0)
30 {
31 pf="./";
32 }
33
34 printf("***** http Request Resource Path = %s, pf = %s\n", path, pf);
35
36 struct stat sb;
37 if(stat(pf,&sb) < 0)
38 {
39 perror("open file err:");
40 send_error(bev);
41 return -1;
42 }
43
44 if(S_ISDIR(sb.st_mode))//处理目录
45 {
46 //应该显示目录列表
47 send_header(bev, 200, "OK", get_file_type(".html"), -1);
48 send_dir(bev, pf);
49 }
50 else //处理文件
51 {
52 send_header(bev, 200, "OK", get_file_type(pf), sb.st_size);
53 send_file_to_http(pf, bev);
54 }
55 }
56
57 return 0;
58 }
59
60 /*
61 *charset=iso-8859-1 西欧的编码,说明网站采用的编码是英文;
62 *charset=gb2312 说明网站采用的编码是简体中文;
63 *charset=utf-8 代表世界通用的语言编码;
64 * 可以用到中文、韩文、日文等世界上所有语言编码上
65 *charset=euc-kr 说明网站采用的编码是韩文;
66 *charset=big5 说明网站采用的编码是繁体中文;
67 *
68 *以下是依据传递进来的文件名,使用后缀判断是何种文件类型
69 *将对应的文件类型按照http定义的关键字发送回去
70 */
71 const char *get_file_type(char *name)
72 {
73 char* dot;
74
75 dot = strrchr(name, '.'); //自右向左查找‘.’字符;如不存在返回NULL
76
77 if (dot == (char*)0)
78 return "text/plain; charset=utf-8";
79 if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
80 return "text/html; charset=utf-8";
81 if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
82 return "image/jpeg";
83 if (strcmp(dot, ".gif") == 0)
84 return "image/gif";
85 if (strcmp(dot, ".png") == 0)
86 return "image/png";
87 if (strcmp(dot, ".css") == 0)
88 return "text/css";
89 if (strcmp(dot, ".au") == 0)
90 return "audio/basic";
91 if (strcmp( dot, ".wav") == 0)
92 return "audio/wav";
93 if (strcmp(dot, ".avi") == 0)
94 return "video/x-msvideo";
95 if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
96 return "video/quicktime";
97 if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
98 return "video/mpeg";
99 if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
100 return "model/vrml";
101 if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
102 return "audio/midi";
103 if (strcmp(dot, ".mp3") == 0)
104 return "audio/mpeg";
105 if (strcmp(dot, ".ogg") == 0)
106 return "application/ogg";
107 if (strcmp(dot, ".pac") == 0)
108 return "application/x-ns-proxy-autoconfig";
109
110 return "text/plain; charset=utf-8";
111 }
112
113 int send_file_to_http(const char *filename, struct bufferevent *bev)
114 {
115 int fd = open(filename, O_RDONLY);
116 int ret = 0;
117 char buf[4096] = {0};
118
119 while( (ret = read(fd, buf, sizeof(buf)) ) )
120 {
121 bufferevent_write(bev, buf, ret);
122 memset(buf, 0, ret);
123 }
124 close(fd);
125 return 0;
126 }
127
128 int send_header(struct bufferevent *bev, int no, const char* desp, const char *type, long len)
129 {
130 char buf[256]={0};
131
132 sprintf(buf, "HTTP/1.1 %d %s\r\n", no, desp);
133 //HTTP/1.1 200 OK\r\n
134 bufferevent_write(bev, buf, strlen(buf));
135 // 文件类型
136 sprintf(buf, "Content-Type:%s\r\n", type);
137 bufferevent_write(bev, buf, strlen(buf));
138 // 文件大小
139 sprintf(buf, "Content-Length:%ld\r\n", len);
140 bufferevent_write(bev, buf, strlen(buf));
141 // Connection: close
142 bufferevent_write(bev, _HTTP_CLOSE_, strlen(_HTTP_CLOSE_));
143 //send \r\n
144 bufferevent_write(bev, "\r\n", 2);
145
146 return 0;
147 }
148
149 int send_error(struct bufferevent *bev)
150 {
151 send_header(bev,404, "File Not Found", "text/html", -1);
152 send_file_to_http("404.html", bev);
153 return 0;
154 }
155
156 int send_dir(struct bufferevent *bev,const char *dirname)
157 {
158 char encoded_name[1024];
159 char path[1024];
160 char timestr[64];
161 struct stat sb;
162 struct dirent **dirinfo;
163
164 char buf[4096] = {0};
165 sprintf(buf, "<html><head><meta charset=\"utf-8\"><title>%s</title></head>", dirname);
166 sprintf(buf+strlen(buf), "<body><h1>当前目录:%s</h1><table>", dirname);
167 //添加目录内容
168 int num = scandir(dirname, &dirinfo, NULL, alphasort);
169 for(int i=0; i<num; ++i)
170 {
171 // 编码
172 strencode(encoded_name, sizeof(encoded_name), dirinfo[i]->d_name);
173
174 sprintf(path, "%s%s", dirname, dirinfo[i]->d_name);
175 printf("############# path = %s\n", path);
176 if (lstat(path, &sb) < 0)
177 {
178 sprintf(buf+strlen(buf),
179 "<tr><td><a href=\"%s\">%s</a></td></tr>\n",
180 encoded_name, dirinfo[i]->d_name);
181 }
182 else
183 {
184 strftime(timestr, sizeof(timestr),
185 " %d %b %Y %H:%M", localtime(&sb.st_mtime));
186 if(S_ISDIR(sb.st_mode))
187 {
188 sprintf(buf+strlen(buf),
189 "<tr><td><a href=\"%s/\">%s/</a></td><td>%s</td><td>%ld</td></tr>\n",
190 encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);
191 }
192 else
193 {
194 sprintf(buf+strlen(buf),
195 "<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%ld</td></tr>\n",
196 encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);
197 }
198 }
199 bufferevent_write(bev, buf, strlen(buf));
200 memset(buf, 0, sizeof(buf));
201 }
202 sprintf(buf+strlen(buf), "</table></body></html>");
203 bufferevent_write(bev, buf, strlen(buf));
204 printf("################# Dir Read OK !!!!!!!!!!!!!!\n");
205
206 return 0;
207 }
208
209 void conn_readcb(struct bufferevent *bev, void *user_data)
210 {
211 printf("******************** begin call %s.........\n",__FUNCTION__);
212 char buf[4096]={0};
213 char method[50], path[4096], protocol[32];
214 bufferevent_read(bev, buf, sizeof(buf));
215 printf("buf[%s]\n", buf);
216 sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", method, path, protocol);
217 printf("method[%s], path[%s], protocol[%s]\n", method, path, protocol);
218 if(strcasecmp(method, "GET") == 0)
219 {
220 response_http(bev, method, path);
221 }
222 printf("******************** end call %s.........\n", __FUNCTION__);
223 }
224
225 void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
226 {
227 printf("******************** begin call %s.........\n", __FUNCTION__);
228 if (events & BEV_EVENT_EOF)
229 {
230 printf("Connection closed.\n");
231 }
232 else if (events & BEV_EVENT_ERROR)
233 {
234 printf("Got an error on the connection: %s\n",
235 strerror(errno));
236 }
237
238 bufferevent_free(bev);
239 printf("******************** end call %s.........\n", __FUNCTION__);
240 }
241
242 void signal_cb(evutil_socket_t sig, short events, void *user_data)
243 {
244 struct event_base *base = user_data;
245 struct timeval delay = { 1, 0 };
246
247 printf("Caught an interrupt signal; exiting cleanly in one seconds.\n");
248 event_base_loopexit(base, &delay);
249 }
250
251 void listener_cb(struct evconnlistener *listener,
252 evutil_socket_t fd,struct sockaddr *sa, int socklen, void *user_data)
253 {
254 printf("******************** begin call-------%s\n",__FUNCTION__);
255 struct event_base *base = user_data;
256 struct bufferevent *bev;
257 printf("fd is %d\n",fd);
258 bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
259 if (!bev)
260 {
261 fprintf(stderr, "Error constructing bufferevent!");
262 event_base_loopbreak(base);
263 return;
264 }
265 bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_NORMAL);
266 bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, NULL);
267 bufferevent_enable(bev, EV_READ | EV_WRITE);
268
269 printf("******************** end call-------%s\n",__FUNCTION__);
270 }
271
272 /*
273 * 这里的内容是处理%20之类的东西!是"解码"过程。
274 * %20 URL编码中的‘ ’(space)
275 * %21 '!' %22 '"' %23 '#' %24 '$'
276 * %25 '%' %26 '&' %27 ''' %28 '('......
277 * 相关知识html中的‘ ’(space)是 
278 */
279 void strdecode(char *to, char *from)
280 {
281 for ( ; *from != '\0'; ++to, ++from)
282 {
283 if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
284 {
285 // 依次判断from中 %20 三个字符
286 *to = hexit(from[1])*16 + hexit(from[2]);
287 // 移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
288 from += 2;
289 }
290 else
291 {
292 *to = *from;
293 }
294 }
295 *to = '\0';
296 }
297
298 //16进制数转化为10进制, return 0不会出现
299 int hexit(char c)
300 {
301 if (c >= '0' && c <= '9')
302 return c - '0';
303 if (c >= 'a' && c <= 'f')
304 return c - 'a' + 10;
305 if (c >= 'A' && c <= 'F')
306 return c - 'A' + 10;
307
308 return 0;
309 }
310
311 // "编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
312 // strencode(encoded_name, sizeof(encoded_name), name);
313 void strencode(char* to, size_t tosize, const char* from)
314 {
315 int tolen;
316
317 for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from)
318 {
319 if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0)
320 {
321 *to = *from;
322 ++to;
323 ++tolen;
324 }
325 else
326 {
327 sprintf(to, "%%%02x", (int) *from & 0xff);
328 to += 3;
329 tolen += 3;
330 }
331 }
332 *to = '\0';
333 }
libevent_http.h
1 #ifndef _LIBEVENT_HTTP_H
2 #define _LIBEVENT_HTTP_H
3
4 #include <event2/event.h>
5
6 void conn_eventcb(struct bufferevent *bev, short events, void *user_data);
7
8 void conn_readcb(struct bufferevent *bev, void *user_data);
9
10 const char *get_file_type(char *name);
11
12 int hexit(char c);
13
14 void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
15 struct sockaddr *sa, int socklen, void *user_data);
16
17 int response_http(struct bufferevent *bev, const char *method, char *path);
18
19 int send_dir(struct bufferevent *bev,const char *dirname);
20
21 int send_error(struct bufferevent *bev);
22
23 int send_file_to_http(const char *filename, struct bufferevent *bev);
24
25 int send_header(struct bufferevent *bev, int no, const char* desp, const char *type, long len);
26
27 void signal_cb(evutil_socket_t sig, short events, void *user_data);
28
29 void strdecode(char *to, char *from);
30
31 void strencode(char* to, size_t tosize, const char* from);
32
33 #endif
main.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <arpa/inet.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <signal.h>
7 #include <event2/bufferevent.h>
8 #include <event2/listener.h>
9 #include <event2/event.h>
10 #include "libevent_http.h"
11
12 int main(int argc, char **argv)
13 {
14 if(argc < 3)
15 {
16 printf("./event_http port path\n");
17 return -1;
18 }
19 if(chdir(argv[2]) < 0) {
20 printf("dir is not exists: %s\n", argv[2]);
21 perror("chdir err:");
22 return -1;
23 }
24
25 struct event_base *base;
26 struct evconnlistener *listener;
27 struct event *signal_event;
28
29 struct sockaddr_in sin;
30 base = event_base_new();
31 if (!base)
32 {
33 fprintf(stderr, "Could not initialize libevent!\n");
34 return 1;
35 }
36
37 memset(&sin, 0, sizeof(sin));
38 sin.sin_family = AF_INET;
39 sin.sin_port = htons(atoi(argv[1]));
40
41 // 创建监听的套接字,绑定,监听,接受连接请求
42 listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
43 LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
44 (struct sockaddr*)&sin, sizeof(sin));
45 if (!listener)
46 {
47 fprintf(stderr, "Could not create a listener!\n");
48 return 1;
49 }
50
51 // 创建信号事件, 捕捉并处理
52 signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
53 if (!signal_event || event_add(signal_event, NULL)<0)
54 {
55 fprintf(stderr, "Could not create/add a signal event!\n");
56 return 1;
57 }
58
59 // 事件循环
60 event_base_dispatch(base);
61
62 evconnlistener_free(listener);
63 event_free(signal_event);
64 event_base_free(base);
65
66 printf("done\n");
67
68 return 0;
69 }
补充:
(1)recv的flag
n = recv(sock, &c, 1, MSG_PEEK);
- flag == MSG_PEEK
- recv从缓冲区总读数据 - 拷贝的方式
1234567890
recv(fd, buf, size, 0);
- 没数据了
recv(fd, buf, size, MSG_PEEK);
- 有, 1234567890
(2)sscanf 函数
函数描述: 读取格式化的字符串中的数据。
函数原型:
int sscanf(
const char *buffer,
const char *format, [ argument ] ...
);
1. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。
sscanf("123456 abcdedf", "%[^ ]", buf);
printf("%s\n", buf);
结果为:123456
2. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。
sscanf("123456abcdedfBCDEF", "%[1-9a-z]", buf);
printf("%s\n", buf);
结果为:123456abcdedf
3. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。
sscanf("123456abcdedfBCDEF", "%[^A-Z]", buf);
printf("%s\n", buf);
结果为:123456abcdedf
(3)strftime 函数
头文件: time.h
函数功能: 将时间格式化,或者说格式化一个时间字符串。
函数原型:
size_t strftime(
char *strDest,
size_t maxsize,
const char *format,
const struct tm *timeptr
);
- format
%a 星期几的简写
%A 星期几的全称
%b 月份的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的前两位数字
%d 十进制表示的每月的第几天
%D 月/天/年
%e 在两字符域中,十进制表示的每月的第几天
%F 年-月-日
%g 年份的后两位数字,使用基于周的年
%G 年份,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从1到7,星期一为1)
%U 第年的第几周,把星期日作为第一天(值从0到53)
%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0)
%W 每年的第几周,把星期一做为第一天(值从0到53)
%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从0到99)
%Y 带世纪部分的十制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
(4)数据转码
url在数据传输过程中不支持中文,需要转码。
- 汉字
- 特殊字符
查看manpage
man ascii
要处理可见字符
从space开始 - 32
前0-31个不可见
不需要转换的特殊字符
.
_
*
/
~
0-9
a-z
A-Z
需要转换的字符使用其16进制的值前加%表示
可以在shell下通过unicode工具查看
安装unicode
- sudo apt-get install unicode
(5)正则表达式
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)