引言
做一个老实人挺好的,至少还觉得自己挺老实的.
再分享一首 自己喜欢的诗人的一首 情景诗. 每个人总会有问题,至少喜欢就好,
本文 参照
http 协议 http://www.cnblogs.com/rayray/p/3729533.html
html格式 http://blog.csdn.net/allenjy123/article/details/7375029
tinyhttpd 源码 https://github.com/EZLippi/Tinyhttpd
附录 本文最后完稿的资源
httpd 源码打包 http://download.csdn.net/detail/wangzhione/9461441
通过本文练习, 至少会学会 Linux上fork用法, pipe管道用法0读1写, pthread用法等.
其它的都是业务解析内容.
前言
讲的不好望见谅, 因为很多东西需要自己去写一写就有感悟了. 看懂源码和会改源码是两码事. 和 会优化更不同了.
凡事多练习. 不懂也都懂了. 我们先说一下总的结构.
client.c 是一个简易的 测试 http请求的客户端
httpd.c 使我们重点要说的 小型简易的Linux上的http服务器
index.html 测试网页 是client.c 想请求的网页
Makefile 编译文件.
这里先总的展示一下 httpd.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <errno.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> // --------------------------------------- 辅助参数宏 ---------------------------------------------- /* * c 如果是空白字符返回 true, 否则返回false * c : 必须是 int 值,最好是 char 范围 */ #define sh_isspace(c) ((c==' ')||(c>=' '&&c<=' ')) //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏 #define CERR(fmt, ...) fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt " ", __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__) //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量 #define CERR_EXIT(fmt,...) CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) //4.3 if 的 代码检测 #define IF_CHECK(code) if((code) < 0) CERR_EXIT(#code) // --------------------------------------- 辅助变量宏 和 声明 ------------------------------------------ // char[]缓冲区大小 #define _INT_BUF (1024) // listen监听队列的大小 #define _INT_LIS (7) /* * 读取文件描述符 fd 一行的内容,保存在buf中,返回读取内容长度 * fd : 文件描述符 * buf : 保存的内容 * sz : buf 的大小 * : 返回读取的长度 */ int getfdline(int fd, char buf[], int sz); // 返回400 请求解析失败,客户端代码错误 extern inline void response_400(int cfd); // 返回404 文件内容, 请求文件没有找见 extern inline void response_404(int cfd); // 返回501 错误, 不支持的请求 extern inline void response_501(int cfd); // 服务器内部错误,无法处理等 extern inline void response_500(int cfd); // 返回200 请求成功 内容, 后面可以加上其它参数,处理文件输出 extern inline void response_200(int cfd); /* * 将文件 发送给客户端 * cfd : 客户端文件描述符 * path : 发送的文件路径 */ void response_file(int cfd, const char* path); /* * 返回启动的服务器描述符(句柄), 这里没有采用8080端口,防止冲突,用了随机端口 * pport : 输出参数和输出参数, 如果传入NULL,将不返回自动分配的端口 * : 返回 启动的文件描述符 */ int serstart(uint16_t* pport); /* * 在客户端链接过来,多线程处理的函数 * arg : 传入的参数, 客户端文件描述符 (int)arg * : 返回处理结果,这里默认返回 NULL */ void* request_accept(void* arg); /* * 处理客户端的http请求. * cfd : 客户端文件描述符 * path : 请求的文件路径 * type : 请求类型,默认是POST,其它是GET * query : 请求发送的过来的数据, url ? 后面那些数据 */ void request_cgi(int cfd, const char* path, const char* type, const char* query); /* * 主逻辑,启动服务,可以做成守护进程. * 具体的实现逻辑, 启动小型玩乐级别的httpd 服务 */ int main(int argc, char* argv[]) { pthread_attr_t attr; uint16_t port = 0; int sfd = serstart(&port); printf("httpd running on port %u. ", port); // 初始化线程属性 pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); for(;;){ pthread_t tid; struct sockaddr_in caddr; socklen_t clen = sizeof caddr; int cfd = accept(sfd, (struct sockaddr*)&caddr, &clen); if(cfd < 0){ CERR("accept sfd = %d is error!", sfd); break; } if(pthread_create(&tid, &attr, request_accept, (void*)cfd) < 0) CERR("pthread_create run is error!"); } // 销毁吧, 一切都结束了 pthread_attr_destroy(&attr); close(sfd); return 0; } // ----------------------------------------- 具体的函数实现过程 ------------------------------------------------ /* * 读取文件描述符 fd 一行的内容,保存在buf中,返回读取内容长度 * fd : 文件描述符 * buf : 保存的内容 * sz : buf 的大小 * : 返回读取的长度 */ int getfdline(int fd, char buf[], int sz) { char* tp = buf; char c; --sz; while((tp-buf)<sz){ if(read(fd, &c, 1) <= 0) //伪造结束条件 break; if(c == ' '){ //全部以 分割 if(recv(fd, &c, 1, MSG_PEEK)>0 && c == ' ') read(fd, &c, 1); else //意外的结束,填充 结束读取 *tp++ = ' '; break; } *tp++ = c; } *tp = '