C 实现一个简易的Http服务器 (二)
正文 - 直接搞起
很久以前写过一个简易的http服务器, 后面和一个朋友交流, 反思后发现问题不少.在这里简单搞一下.
让其更加简单去表现httpd本质, 弱化协议业务. 方便当httpd入手学习的demo. ok, 那直接代码走起 ~
Makefile - 编译部分
all:httpd.exe client.exe httpd.exe : httpd.c
gcc -g -Wno-unused-result -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast -Wall -O2 -o $@ $^ -lpthread client.exe : client.c
gcc -g -Wall -o $@ $^ clean:
rm -rf *.exe
client.c - 简单的测试客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h> #define CERR(fmt, ...) \
fprintf(stderr, "[%s:%s:%d][errno %d:%s]" fmt "\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #define CERR_EXIT(fmt,...) \
CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE) #define CERR_IF(code) \
if((code) < ) \
CERR_EXIT(#code) //待拼接的字符串
#define _STR_HTTPBEG "GET /index.html HTTP/1.0\r\nUser-Agent: Happy is good.\r\nHost: 127.0.0.1:"
#define _STR_HTTPEND "\r\nConnection: close\r\n\r\n" // 简单请求一下
int main(int argc, char * argv[]) {
int sfd;
int len, port;
char buf[BUFSIZ];
struct sockaddr_in saddr = { AF_INET }; // argc 默认为1 第一个参数 就是 执行程序串
if((argc != ) || (port = atoi(argv[])) <= )
CERR_EXIT("Usage: %s [port]", argv[]); // 开始了,就这样了
CERR_IF(sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
saddr.sin_port = htons(port);
CERR_IF(connect(sfd, (struct sockaddr *)&saddr, sizeof saddr)); //开始发送请求
strcpy(buf, _STR_HTTPBEG);
strcat(buf, argv[]);
strcat(buf, _STR_HTTPEND);
write(sfd, buf, strlen(buf)); //读取所哟内容
while((len = read(sfd, buf, sizeof buf - )) > 0) {
buf[len] = '\0';
printf("%s", buf);
}
putchar('\n'); close(sfd);
return EXIT_SUCCESS;
}
httpd.c - 简易http服务器主体
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <arpa/inet.h> #define CERR(fmt, ...) \
fprintf(stderr, "[%s:%s:%d][errno %d:%s]" fmt "\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #define CERR_EXIT(fmt,...) \
CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE) #define CERR_IF(code) \
if((code) < ) \
CERR_EXIT(#code) //
// getfdline - 读取文件描述符 fd 一行的内容,保存在buf中,返回读取内容长度
// fd : 文件描述符
// buf : 保存的内容
// sz : buf 的大小
// return : 返回读取的长度
//
int getfdline(int fd, char buf[], int sz); // 返回400 请求解析失败, 客户端代码错误
void response_400(int cfd);
// 返回404 文件内容, 请求文件没有找见
void response_404(int cfd);
// 返回501 错误, 不支持的请求
void response_501(int cfd);
// 服务器内部错误,无法处理等
void response_500(int cfd);
// 返回200 请求成功 内容, 后面可以加上其它参数,处理文件输出
void response_200(int cfd);
// 服务器返回请求的文件内容
void response_file(int cfd, const char * path); //
// request_start - 启动一个httpd监听端口, 使用随机端口
// pport : 输出参数和输出参数, 如果传入NULL, 将不返回自动分配的端口
// return : 返回启动的文件描述符
//
int request_start(uint16_t * pport); // 在客户端链接过来, pthread 多线程处理的函数
void * request_accept(void * arg); //
// request_cgi - 处理客户端的http请求.
// cfd : 客户端文件描述符
// path : 请求的文件路径
// type : 请求类型,默认是POST,其它是GET
// query : 请求发送的过来的数据, url ? 后面那些数据
// return : void
//
void request_cgi(int cfd, const char * path, const char * type, const char * query); //
// 主逻辑,启动服务,可以做成守护进程.
// 具体的实现逻辑, 启动小型玩乐级别的httpd 服务
//
int main(int argc, char * argv[]) {
uint16_t port = ;
pthread_attr_t attr;
int sfd = request_start(&port); printf("httpd running on port %u.\n", port);
// 初始化线程属性
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
for(;;){
pthread_t tid;
int cfd = accept(sfd, NULL, NULL);
if(cfd < ){
CERR("accept sfd = %d is error!", sfd);
break;
}
if(pthread_create(&tid, &attr, request_accept, (void *)cfd) < )
CERR("pthread_create run is error!");
}
// 销毁吧, 一切都结束了
pthread_attr_destroy(&attr);
close(sfd);
return EXIT_SUCCESS;
} int
getfdline(int fd, char buf[], int sz) {
char c, * tp = buf; --sz;
while((tp-buf)<sz){
if(read(fd, &c, ) <= ) //伪造结束条件
break;
if(c == '\r'){ //全部以\r分割
if(recv(fd, &c, , MSG_PEEK)> && c == '\n')
read(fd, &c, );
else //意外的结束,填充 \n 结束读取
*tp++ = '\n';
break;
}
*tp++ = c;
}
*tp = '\0';
return tp - buf;
} inline void
response_400(int cfd) {
const char * estr =
"HTTP/1.0 400 BAD REQUEST\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<p>你的请求有问题,请检查语法!</p>\r\n"; write(cfd, estr, strlen(estr));
} inline void
response_404(int cfd) {
const char * estr =
"HTTP/1.0 404 NOT FOUND\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html>"
"<head><title>你请求的界面被查水表了!</title></head>\r\n"
"<body><p>404: 估计是回不来了</p></body>"
"</html>"; write(cfd, estr, strlen(estr));
} inline void
response_501(int cfd) {
const char * estr =
"HTTP/1.0 501 Method Not Implemented\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html>"
"<head><title>小伙子不要乱请求</title></head>\r\n"
"<body><p>too young too simple, 年轻人别总想弄出个大新闻.</p></body>"
"</html>"; write(cfd, estr, strlen(estr));
} inline void
response_500(int cfd) {
const char * estr =
"HTTP/1.0 500 Internal Server Error\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html>"
"<head><title>Sorry </title></head>\r\n"
"<body><p>最近有点方了!</p></body>"
"</html>"; write(cfd, estr, strlen(estr));
} inline void
response_200(int cfd) {
const char * estr =
"HTTP/1.0 200 OK\r\n"
"Server: wz simple httpd 1.0\r\n"
"Content-Type: text/html\r\n"
"\r\n"; write(cfd, estr, strlen(estr));
} void
response_file(int cfd, const char * path) {
char buf[BUFSIZ];
FILE * txt = fopen(path, "r"); // 如果文件解析错误, 给它个404
if(NULL == txt)
response_404(cfd);
else{
//发送给200的报文头过去, 并发送文件内容过去
response_200(cfd);
while(!feof(txt) && fgets(buf, sizeof buf, txt))
write(cfd, buf, strlen(buf));
fclose(txt);
}
} int
request_start(uint16_t * pport) {
int sfd;
struct sockaddr_in saddr = { AF_INET }; CERR_IF(sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
// 监测一下是否要更换端口, 并绑定一下端口信息
saddr.sin_port = pport && *pport ? htons(*pport) : ;
CERR_IF(bind(sfd, (struct sockaddr *)&saddr, sizeof saddr)); if(pport && !*pport) {
socklen_t clen = sizeof saddr;
CERR_IF(getsockname(sfd, (struct sockaddr*)&saddr, &clen));
*pport = ntohs(saddr.sin_port);
} // 开启监听任务
CERR_IF(listen(sfd, SOMAXCONN)); return sfd;
} void *
request_accept(void * arg) {
char buf[BUFSIZ], path[BUFSIZ >> ], type[BUFSIZ >> ];
char * lt, * rt, * query = NULL, * nb = buf;
int iscgi, cfd = (int)arg;
struct stat st; // 请求错误, 直接返回结果
if(getfdline(cfd, buf, sizeof buf) <= ) {
response_501(cfd);
close(cfd);
return NULL;
} // 合法请求处理
for(lt = type, rt = nb; !isspace(*rt) && (lt - type) < sizeof type - ; *lt++ = *rt++)
;
*lt = '\0'; //同样处理合法与否判断, 出错了直接返回错误结果
if((iscgi = strcasecmp(type, "POST")) && strcasecmp(type, "GET")) {
response_501(cfd);
close(cfd);
return NULL;
} // 在buf中 去掉空字符
while(*rt && isspace(*rt))
++rt; // 这里得到路径信息, query url路径拼接
*path = '.';
for(lt = path + ; (lt - path) < sizeof path - && !isspace(*rt); *lt++ = *rt++)
;
*lt = '\0'; // 单独处理 get 获取 ? 后面数据, 不是 POST那就是 GET
if(iscgi != ) {
for(query = path; *query && *query != '?'; ++query)
;
if(*query == '?'){
iscgi = ;
*query++ = '\0';
}
} // type , path 和 query 已经构建好了
if(stat(path, &st) < ) {
response_404(cfd);
close(cfd);
return NULL;
} // 合法情况, 执行, 写入, 读取权限. 监测是否是 CGI程序
if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
iscgi = ;
if(!iscgi)
response_file(cfd, path);
else
request_cgi(cfd, path, type, query); close(cfd);
return NULL;
} void
request_cgi(int cfd, const char * path, const char * type, const char * query) {
pid_t pid;
char c, buf[BUFSIZ];
int pocgi[], picgi[];
int i, contlen = -; // 报文长度 if(strcasecmp(type, "POST") == ){
while(getfdline(cfd, buf, sizeof buf) > && strcmp("\n", buf)){
buf[] = '\0';
if(!strcasecmp(buf, "Content-Length:"))
contlen = atoi(buf + );
}
if(contlen == -) { //错误的报文,直接返回错误结果
response_400(cfd);
return;
}
}
else {
// 读取报文头,就是过滤, 后面就假定是 GET
while(getfdline(cfd, buf, sizeof buf) > && strcmp("\n", buf))
;
} //这里处理请求内容, 先处理错误信息
if(pipe(pocgi) < ) {
response_500(cfd);
return;
}
// 管道 是 0读取, 1写入
if(pipe(picgi) < ) {
close(pocgi[]), close(pocgi[]);
response_500(cfd);
return;
}
if((pid = fork()) < ){
close(pocgi[]), close(pocgi[]);
close(picgi[]), close(picgi[]);
response_500(cfd);
return;
} // 这里就是多进程处理了, 先处理子进程
if(pid == ) {
// dup2 让前者共享后者同样的文件表
dup2(pocgi[], STDOUT_FILENO); // 标准输出算作 pocgi管道的写入端
dup2(picgi[], STDIN_FILENO); // 标准输入做为 picgi管道的读取端
close(pocgi[]);
close(picgi[]); // 添加环境变量, 用于当前会话中
sprintf(buf, "REQUEST_METHOD=%s", type);
putenv(buf);
// 继续凑环境变量串,放到当前会话种
if(strcasecmp(buf, "POST") == )
sprintf(buf, "CONTENT_LENGTH=%d", contlen);
else
sprintf(buf, "QUERY_STRING=%s", query);
putenv(buf);
// 成功的话调到 新的执行体上
execl(path, path, NULL); // 这行代码原本是不用的, 但是防止 execl执行失败, 子进程没有退出.妙招
exit(EXIT_SUCCESS);
}
// 父进程, 随便搞了, 先发送个OK
write(cfd, "HTTP/1.0 200 OK\r\n", );
close(pocgi[]);
close(picgi[]); if(strcasecmp(type, "POST") == ){
// 将数据都写入到 picgi 管道中, 让子进程在 picgi[0]中读取 => STDIN_FILENO
for(i = ; i < contlen; ++i){
read(cfd, &c, );
write(picgi[], &c, );
}
}
//从子进程中 读取数据 发送给客户端, 多线程跨进程阻塞模型
while(read(pocgi[], &c, ) > )
write(cfd, &c, ); close(pocgi[]);
close(picgi[]);
//等待子进程结束
waitpid(pid, NULL, );
}
代码精简部分, 方便温故. http 是 tcp 的上层协议. 本质是HTTP文本协议加网络IO处理. 简单点同上面那些.
说复杂那全是没边. 系统开发层面 libcurl 库可以解决http常用客户端请求. 服务器端可以nginx加代理.
后记 - 未来展望
错误是难免的欢迎指正, 加打脸.
最近做了很多跨平台的工作, 线程, 管道, socket ... 哎总感觉 被风吹乱了头发~(¬_¬)
千默 http://music.163.com/#/song?id=35447148
C 实现一个简易的Http服务器 (二)的更多相关文章
- C 实现一个简易的Http服务器
引言 做一个老实人挺好的,至少还觉得自己挺老实的. 再分享一首 自己喜欢的诗人的一首 情景诗. 每个人总会有问题,至少喜欢就好, 本文 参照 http 协议 http://www.cnblogs. ...
- Tinywebserver:一个简易的web服务器
这是学习网络编程后写的一个练手的小程序,可以帮助复习I/O模型,epoll使用,线程池,HTTP协议等内容. 程序代码是基于<Linux高性能服务器编程>一书编写的. 首先回顾程序中的核心 ...
- day-1 用python编写一个简易的FTP服务器
从某宝上购买了一份<Python神经网络深度学习>课程,按照视频教程,用python语言,写了一个简易的FTP服务端和客户端程序,以前也用C++写过聊天程序,编程思路差不多,但是pytho ...
- 在Android中实现一个简易的Http服务器
最近遇到一个需求需要在App中创建一个Http服务器供供浏览器调用,用了下开源的微型Htpp服务器框架:NanoHttpd,项目地址:https://github.com/NanoHttpd/nano ...
- python -m http.server 搭建一个简易web下载服务器
在打vulnhub靶场的时候遇到的一个问题 目录 一.进到需要发送的安装包目录 二.开启http服务 三.访问服务器 一.进到需要发送的安装包目录 比如设置一个专门发送,传输的文件的文件夹,cmd命令 ...
- 局域网 FTP建立,搭建一个简易的局域网服务器
1.创建用户名以及密码: 右键我的电脑 -> 管理->本地用户和组->右键用户->新用户----设置用户名密码: 2.安装IIS 和FTP :控制面板->程序->打 ...
- python3 使用http.server模块 搭建一个简易的http服务器
from http.server import HTTPServer, BaseHTTPRequestHandler import json data = {'result': 'this is a ...
- 使用Python创建一个简易的Web Server
Python 2.x中自带了SimpleHTTPServer模块,到Python3.x中,该模块被合并到了http.server模块中.使用该模块,可以快速创建一个简易的Web服务器. 我们在C:\U ...
- go 语言实现一个简单的 web 服务器
学习Go语言的一些感受,不一定准确. 假如发生战争,JAVA一般都是充当航母战斗群的角色.一旦出动,就是护卫舰.巡洋舰.航母舰载机.预警机.电子战飞机.潜艇等等浩浩荡荡,杀将过去.(JVM,数十个JA ...
随机推荐
- Python 日志输出中添加上下文信息
Python日志输出中添加上下文信息 除了传递给日志记录函数的参数(如msg)外,有时候我们还想在日志输出中包含一些额外的上下文信息.比如,在一个网络应用中,可能希望在日志中记录客户端的特定信息,如: ...
- Python面向对象—类的继承
一个子类可以继承父类的所有属性,不管是父类的数据属性还是方法. class Father(object): num = 0 def __init__(self): print "I'm Pa ...
- Oracle DB_LINK如何使用
语句,或可通过可视化操作 -- Create database link create database link DBL_TESTconnect to UID identified by PSWus ...
- 【hackerrank】Week of Code 26
在jxzz上发现的一个做题网站,每周都有训练题,题目质量……前三题比较水,后面好神啊,而且类型差不多,这周似乎是计数专题…… Army Game 然后给出n*m,问需要多少个小红点能全部占领 解法:乘 ...
- BZOJ 1342: [Baltic2007]Sound静音问题 | 单调队列维护的好题
题目: 给n个数字,一段合法区间[l,l+m-1]要求max-min<=c 输出所有合法区间的左端点,如果没有输出NONE 题解: 单调队列同时维护最大值和最小值 #include<cst ...
- React获取组件实例
1. 直接new Component() 组件本身也是class,可以new,这样的组件实例意义不大 componentInstance = new Component(); 2. ReactDOM. ...
- finetune on caffe
官方例程:http://caffe.berkeleyvision.org/gathered/examples/finetune_flickr_style.html 相应的中文说明:http://blo ...
- Codeforces Round #337 (Div. 2)B
B. Vika and Squares time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- 题解【luogu1073 最优贸易】
Solution 考虑原图是 DAG 时怎么做. 拓扑排序 + dp ,令 dp[i] 表示 \(1\) 到 \(i\) 的路径上最小的卖出价格.转移方程就是对每一个可以到达这个点的 dp 取个 mi ...
- 分析一个贴图社交app的失败原因:FORK(相机)
FORK(相机)是一个通过分享图片来建立社交的app,它有着鲜明的配色,还算不错的贴图创新,细腻的产品设计,但是由于产品定位不清晰.设计亮点不多以及推广不利,从2014年5月第一版开始就没有火过.所以 ...