缘由
在编 写纯C语言版socket.io服务器 时,选择了libev作为网络基础层代码,可以离epoll模型远一些,再说还可以避免单独使用Epoll,写出不易维护的多层嵌套代码,听说,有时Epoll出现一些“伪信号”小问题,没有那么空闲精力,绕过之,选择成熟度非常高的libev好了。
有关libev的文章,中文资料不多,英文资料也不多。这里推荐三篇:
- libev 设计分析
- libev ev_io源码分析
- 官方文档http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod,更为全面一些,阅读时可以获得较总体认知。
这里把在编写c_socket.io_server
程中使用libev的一些地方做些笔记,记录下来,也方便以后查阅。
预备知识
所有代码的编写、编译、测试和运行等,都在Ubuntu下进行,另外实例严重依赖libev和http-parser HTTP解析库。
其它依赖,可以从https://github.com/yongboy/csocket.ioserver处下载。
处理静态文件
这里设计一个静态文件WEB服务器,非常简单,仅仅满足socket.io服务器最基本的需求,因此别苛求太多。但比网上很多大把类似文章多了一点写入管道时缓冲区已满问题的处理。
这里简单说一下处理静态文件的思路。
计算静态文件路径以及扩展名和内容类型
char file_path[200];
sprintf(file_path, "%s%s", static_folder, url_str);
获取文件内容和以及优先输出响应头部
char file_path[200];
sprintf(file_path, "%s%s", static_folder, url_str);
int file = open(file_path, O_RDONLY);
struct stat info;
if (fstat(file, &info) == -1) {
fprintf(stderr, "the file %s is NULL\n", file_path);
write(client->fd, RESPONSE_404, strlen(RESPONSE_404));
close(file);
free_res(loop, client);
return 0;
}
char file_ext[50];
get_extension(file_path, file_ext);
char content_type[50];
get_content_type(file_ext, content_type);
int file_len = info.st_size;
char head_msg[200] = "";
sprintf(head_msg, RESPONSE_TEMPLATE, content_type, file_len);
write(client->fd, head_msg, strlen(head_msg));
很显然,这里引入fcntl.h头部文件,调用fstat初始化stat结构,可判断文件是否存在,以及文件大小等。
读取文件内容到缓冲区,循环写入
int read_count;
int buf_size = 8 * 1024;//8096;
char buffer[buf_size + 1];
while ((read_count = read(file, buffer, buf_size)) > 0) {
int bytes_left = read_count;
char *ptr = buffer;
int need_break = 0;
while (bytes_left > 0) {
ssize_t write_len = write(client->fd, ptr, bytes_left);
if (write_len == -1) {
fprintf(stderr, "write failed(errno = %d): %s\n", errno, strerror(errno));
switch (errno) {
case EAGAIN:
case EINTR:
case EINPROGRESS:
fprintf(stderr, "now sleep 0.2s\n");
ev_sleep(0.2);
break;
default:
need_break = 1;
break;
}
} else if (write_len == 0) {
need_break = 1;
fprintf(stderr, "write_len is zero, and break now\n");
break;
} else if (write_len < bytes_left) {
bytes_left -= write_len;
ptr += write_len;
fprintf(stderr, "write client with something wrong wtih bytes_left = %d & write_len = %d and write the left data !\n", (int)bytes_left, (int)write_len);
} else {
break;
}
}
if (need_break) {
break;
}
}
close(file);
需要注意,构造的一个大约8K+1的缓冲区buffer,不是每次都可以正常完整输出到socket对端,write输出不完整,会返回-1,系统返回errno值,在errno = EAGAIN,EINTR,EINPROGRESS时,需要再次将缓冲区中尚未写入的剩下数据再次写入到请求端。这样可以避免常见的半包、包不完整问题。
关闭socket描述符,当前请求结束
free_res(loop, client);
请求完成,一定要记得关闭socket描述符,释放相应资源等。
这样一个较为完整的HTTP请求,静态文件就处理完毕了。
编译运行
先编译:
gcc staticserver.c -o staticserver ../include/libev.a ../include/http-parser/http_parser.o -lm
运行之:
./static_server ../static
命令输入错误,如输入静态路径为空,会报错的,哈哈:
Error: invald path parmeter
Usage:
./static_server
Example:
./staticserver ../static
./staticserver /home/yongboy/yourstaticfolder
Enjoy it~
测试一下吧
curl -i http://192.168.190.150:8000/index.html
在浏览器内,测试一下,支持图片样式等,完好显示。
需要注意,要传入静态文件目录路径,相对的路径,或绝对的路径,都是可以接受的。
最后,附上完整代码: