参考:“深入理解计算机系统” 第663页

web简介:

web服务器其实就是用来响应浏览器(客户端)的请求,他们之间的通讯都遵循着HTTP协议。HTTP请求格式是 <方法><统一资源标识符><HTTP协议版本>,<方法>有GET,POST,PUT等等,其中用得最为广泛得是GET请求,占全世界所有HTTP请求的99%;<统一资源标识符>说白了就是文件的路径,比如index.html。如果你用浏览器输入http://127.0.0.1/index.html,它就会用TCP协议连接上127.0.0.1:80的服务器,发送GET /index.html HTTP/1.1的数据到服务器;服务器收到请求后,就会返回index.html的文件内容,然后断开对浏览器的连接;最后浏览器收到index.html的文件内容后把它显示在屏幕上。既然HTTP协议是建基於TCP的,那我们当然就可以用我们在c语言中熟悉的socket来制作一个属于自己的浏览器。

我们的微型web服务器简介:

不要看它叫微型,就小看它,麻雀虽小但五脏俱全,有错误处理,有文字内容,有图片内容。看到这里一定会有朋友问:你这些都是静态服务,像登入操作的那种动态服务就没了吧?那你就错了,这个小服务器连动态服务都有,你能用c语言或python写动态响应程序/脚本,也就是我们常说的CGI程序,牛逼了吧?事不宜迟,我们马上开始实现一个属于自己的web服务器。

效果图:

要准备的东西:

线程池:下面我们会用到这个线程池模块,是我之前写的,你可以直接复制来用,不用也可以,就把线程池的函数去掉就可以了。不过用法很简单,三个函数init,add_event,destroy。http://blog.csdn.net/sumkee911/article/details/50231891

重定位标准输出:CGI程序是用printf来返回数据的,所以我们必须在调用之前把它的stdout重新定位到客户端socket的描述符上,我之前又写下过这个代码,你们可以参考这里。http://blog.csdn.net/sumkee911/article/details/50238169

我的源码:这个就是我写的微型web服务器,里面有makefile,make一下就能用;还有测试用的html和cgi,都在bin文件夹里。http://download.csdn.net/detail/sumkee911/9367809

这里也放源码(你们从main函数跟着看下去就可以明白。我打了很多注释,而且代码很浅显易明的,请放心。如果实在有函数不懂的话就上百度,我这里就不再详细说明了):

tiny_web_server.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "threadpool/threadpool.h"// 服务器配置
#define PORT            80
#define MAX_LISTEN      128
#define MAX_EPOLL       MAX_LISTEN+1
#define THREAD_COUNT    10
#define DEFAULT_PAGE    "index.html"
#define SEND_BUF        2048
#define RECV_BUF        2048
#define SERVE_STATIC    0               // 处理静态请求
#define SERVE_DYNAMIC   1               // 处理动态请求// 服务器根目录
static char g_root[256]="";
// 线程池
static ThreadPool g_pool;
// 多路复用io接口(epoll)
static int g_epoll_fd;
static void del_from_epoll(int efd, int fd);void get_file_type(char *type, char *filepath) {if(strstr(filepath,".html")) strcpy(type, "text/html");else if(strstr(filepath, ".gif")) strcpy(type, "image/gif");else if(strstr(filepath, ".jpg")) strcpy(type, "image/jpeg");else if(strstr(filepath, ".png")) strcpy(type, "image/png");else if(strstr(filepath, ".css")) strcpy(type, "text/css");else if(strstr(filepath, ".js")) strcpy(type, "application/x-javascript");else strcpy(type, "text/plain");
}void get_absolute_path(char *abfilepath,  char *rt, char *filepath) {sprintf(abfilepath, "%s%s", rt, filepath);
}void serve_error(int fd,  char *filepath,const char *errnum, const char *shortmsg, const char *longmsg) {char head[SEND_BUF], body[SEND_BUF];memset(body, 0, sizeof(body));memset(head, 0 ,sizeof(head));// 网页错误页面sprintf(body, "<html><head><title>Tiny Web Server Error</title></head>");sprintf(body, "%s<body><h1><font color='red'>Tiny Web Server Error</font></h1>", body);sprintf(body, "%s<p>Error code: %s</p>",body, errnum);sprintf(body, "%s<p>Cause: %s %s</p></body></html>", body,longmsg, filepath);// http 头sprintf(head, "HTTP/1.1 %s %s\r\n", errnum, shortmsg);sprintf(head, "%sContent-type: text/html\r\n", head);sprintf(head, "%sContent-length: %d\r\n\r\n", head,(int)strlen(body));// 发送send(fd, head, strlen(head), MSG_NOSIGNAL);send(fd, body, strlen(body), MSG_NOSIGNAL);
}void serve_static(int fd, char *filepath, long filesize) { int filefd;char buf[SEND_BUF], filetype[128],*filemap; memset(filetype, 0, sizeof(filetype));memset(buf, 0, sizeof(buf));// 发送http头get_file_type(filetype, filepath);   // 获取文件类型sprintf(buf, "HTTP/1.1 200 OK\r\n");sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);sprintf(buf, "%sContent-length: %ld\r\n",buf,  filesize);sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);send(fd, buf, strlen(buf), MSG_NOSIGNAL);// 发送文件内容filefd = open(filepath, O_RDONLY);filemap = (char *)mmap(0, filesize, PROT_READ, MAP_PRIVATE, filefd, 0);close(filefd);send(fd, filemap, filesize, MSG_NOSIGNAL);munmap(filemap,filesize);
}void serve_dynamic(int fd, char *filepath, char *args) {// 创建一条进程;设置环境变量QUERY_STRING,将args设置进去; 最后将子进程的标准输出重新定位到客户端的socket描述符中,// 只要子程序printf,就能把printf得数据传送到客户端。pid_t pid;char head[SEND_BUF];// 发送http头memset(head, 0, sizeof(head));sprintf(head, "HTTP/1.1 200 OK\r\n");sprintf(head, "%sServer: Tiny Web Server\r\n", head);send(fd,  head, strlen(head), MSG_NOSIGNAL);if((pid=fork()) == 0) {// 子进程// 设置环境变量setenv("QUERY_STRING", args, 1);// 重定向标准输出, 默认输出fd是1dup2(fd, 1);// 启动CGIexecl(filepath, "" , (char*)0);} else {// 等待子进程结束waitpid(pid, 0, 0);}
} int serve_type(const char *filepath) {const char str[] = "/cgi-bin/\0";if(strncmp(filepath, str, strlen(str)) == 0) {return SERVE_DYNAMIC;} return SERVE_STATIC;
}void parse_url(char *filepath, char *args, char *url) {char *file_start, *args_start;file_start = index(url, '/');args_start = index(url, '?');if(args_start != 0) {memcpy(filepath, file_start, args_start-url);memcpy(args, args_start+1, strlen(args_start)-1);} else if(file_start != 0) {memcpy(filepath, file_start, strlen(file_start));}
}void process_command(void *tp_args) {char data[RECV_BUF], request[16], filepath[256], \new_abfilepath[256],args[256], url[512];struct stat filestat;int res, type;int fd = *(int*)tp_args;free(tp_args);memset(data, 0, sizeof(data));memset(request, 0, sizeof(request));memset(filepath, 0, sizeof(filepath));memset(url, 0, sizeof(url));memset(args, 0, sizeof(args));memset(new_abfilepath, 0, sizeof(new_abfilepath));// 获取请求 res = recv(fd, data, sizeof(data), MSG_NOSIGNAL);if(res == 0 || res == -1) {// 删除连接goto __end;}printf("%s\n", data);// 解析请求sscanf(data, "%s %s", request, url);if(strcasecmp("GET", request) != 0) {// 无法识别请求serve_error(fd, request, "501", "Not implememted","Tiny does not implement this method");goto __end;}// 解析urlparse_url(filepath, args, url);// 如果文件位置为'/', 就把它设置为默认页面if(strlen(filepath) == 1) {// 默认页面strcat(filepath, DEFAULT_PAGE);}get_absolute_path(new_abfilepath, g_root, filepath); // 设置文件的绝对路径// 获取文件属性res = stat(new_abfilepath, &filestat);if(res == -1) {// 找不到相关文件serve_error(fd, filepath,"404", "Not found","Tiny couldn't find this file");goto __end;}// 判断是静态请求还是动态请求,动态请求也就是我们常说的CGI程序type = serve_type(filepath); if(type == SERVE_STATIC) {if(!(S_ISREG(filestat.st_mode)) || !(S_IRUSR & filestat.st_mode)) {// 错误,不能读取这文件   serve_error(fd, filepath, "403", "Forbidden", "Tiny couldn't read this file");goto __end;}// 开始回复静态请求serve_static(fd, new_abfilepath, filestat.st_size);} else {if(!(S_ISREG(filestat.st_mode)) || !(S_IXUSR & filestat.st_mode)) {// 错误,不能运行这文件serve_error(fd, filepath, "403", "Forbidden","Tiny couldn't run this cgi file");goto __end;}// 开始回复动态请求serve_dynamic(fd, new_abfilepath, args);}// 删除连接
__end:close(fd);
}void add_to_epoll(int efd, int fd) {int res;struct epoll_event epe;epe.data.fd = fd;epe.events = EPOLLIN | EPOLLET;res = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &epe);if(res == -1) {perror("epoll_ctl");}
}void del_from_epoll(int efd, int fd) {int res = epoll_ctl(efd, EPOLL_CTL_DEL, fd, 0);if(res == -1) {perror("epoll_ctl");}
}void set_nonblock(int fd) {int tmp = 1;if(ioctl(fd, FIONBIO, &tmp) == -1) {perror("ioctl");}
}void set_root(char *rt, char *filepath) {char *end = rindex(filepath, '/');memcpy(rt, filepath, end-filepath);strcat(rt, "\0");
}int main(int, char *argv[]) {    int server,res,  blreuse;struct sockaddr_in addr;bool blres;// 设置根目录, 也就是该服务器程序的主目录set_root(g_root, argv[0]);// 创建服务器server = socket(AF_INET, SOCK_STREAM, 0);if(server == -1) {perror("socket");return -1;}addr.sin_family = AF_INET;addr.sin_port = htons(PORT);addr.sin_addr.s_addr = htonl(INADDR_ANY);// 将bind地址设置为可重用blreuse = 1;res = setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &blreuse, sizeof(blreuse));if(res == -1) {perror("setsockopt");return -1;}res = bind(server, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));if(res == -1) {perror("bind");return -1;}listen(server, MAX_LISTEN);if(res == -1) {perror("listen");return -1;}// 将服务器设置为非阻塞set_nonblock(server);// 创建epollg_epoll_fd = epoll_create(MAX_LISTEN);if(g_epoll_fd == -1) {perror("epoll_create");return -1;}// 将服务器加入epolladd_to_epoll(g_epoll_fd, server);// 初始化线程池, 用来处理url请求blres = g_pool.init(THREAD_COUNT);if(blres == false) {return -1;}// 进入接收事件循环while(1) {struct epoll_event epes[MAX_EPOLL];int i, n;n = epoll_wait(g_epoll_fd, epes, MAX_EPOLL, -1);for(i=0; i<n; ++i) {if(epes[i].events & EPOLLERR || epes[i].events & EPOLLHUP ||!(epes[i].events & EPOLLIN)) {del_from_epoll(g_epoll_fd,epes[i].data.fd);continue;} else if(epes[i].data.fd == server) {// 服务器接收到请求struct sockaddr_in addr_client;socklen_t len; int fd;len = sizeof(struct sockaddr_in);while(true) {fd = accept(server, (struct sockaddr*)&addr_client, &len);if(fd == -1) {break;}// 将新来得客户加入到epolladd_to_epoll(g_epoll_fd, fd);}} else {// 接收到来自客户端的数据int *fd = (int*)malloc(sizeof(int));*fd = epes[i].data.fd;// 将客户从epoll中删除del_from_epoll(g_epoll_fd, epes[i].data.fd);// 处理url请求g_pool.add_event(process_command, (void*)fd);}}}g_pool.destroy();close(g_epoll_fd);close(server);return 0;
}

liunx c语言制作 微型web服务器 300行代码相关推荐

  1. 如何用C语言写一个web服务器的基础功能

    我们都知道,学一门语言,只是单独看了就不写的话是很容易出现眼高手低的,所以,今天摩杜云要给大家分享的内容,就是如何用C语言写一个web服务器的基础功能,希望大家看完有所收获. 服务器架构 目标架构 以 ...

  2. go语言服务器代码,Go语言开发简单web服务器

    欢迎,来自IP地址为:182.103.254.107 的朋友 Go语言由于其方便的并发通信机制以及强大的网络支持,常常被用于服务器软件的开发.本文将示例使用Go语言来开发简单的Web服务器. HTTP ...

  3. c语言300行代码大作业,C语言300行代码

    <C语言300行代码>由会员分享,可在线阅读,更多相关<C语言300行代码(3页珍藏版)>请在人人文库网上搜索. 1.include #include #include #in ...

  4. 特斯拉AI总监用300行代码实现“迷你版GPT”,上线GitHub三天收获3.3k星

    晓查 发自 凹非寺  量子位 报道 | 公众号 QbitAI "GPT并不是一个复杂的模型." 前OpenAI科学家.现任特斯拉AI总监的Andrej Karpathy在自己的Gi ...

  5. 一天star量破千,300行代码,特斯拉AI总监Karpathy写了个GPT的Pytorch训练库

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 整理:公众号@机器之心 本文仅做学术分享,如有侵权,请联系删除. 如果说 GPT 模型是所向披靡的战舰 ...

  6. 通过Mesos、Docker和Go,使用300行代码创建一个分布式系统

    [摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...

  7. 三百行python代码的项目_使用300行代码创建一个分布式系统

    使用 300 行代码创建一个分布式系统 构建一个分布式系统是很困难的. 它需要可扩展性. 容错性. 高可用性. 一致性. 可伸缩以及高效.为了达到这些目的,分布式系统需要很多复杂的组件以一 种 复杂的 ...

  8. Weex 300行代码开发一款简易的跑步App

    通过Weex 300行代码开发一款简易的跑步App 2017-03-28 Weex正如它的目标, 一套构建高性能.可扩展的原生应用的跨平台开发方案 Weex 给大家带来的无疑是客户端开发效率的提升,我 ...

  9. 如何仅用 300行代码养活自己一年,并将公司卖出?

    编者按:创业需要超人的毅力,需要耐心,需要海盗般的勇气,并不是每个人都适合创业.但创业并不像你想象中的那么难,那么神秘,也不需要什么太伟大的创意.Felix Chan的亲身经历就是一个很好的例子.以下 ...

最新文章

  1. 2022-2028年中国演出市场深度调研与投资可行性报告
  2. 信息系统监理师题库_信息系统监理题库
  3. 2.1.5 编码与调制
  4. zookeeper web ui--gt;node-zk-browser安装
  5. JavaScript(四)——面向对象编程、BOM、DOM、表单验证、jQuery
  6. 您第一次上网的速度是多少?
  7. QTP的那些事 -– Visual Relation Identifier Feature: How to use in the real world
  8. IOS8 AutoLayout+SizeClasses 基础篇(1)
  9. Linux 命令(134)—— groupmod 命令
  10. web安全day13:简单深透测试流程
  11. 从无线安全到内网渗透[1]
  12. python点云数据处理_python处理点云数据并生成三维点云模型
  13. MPai数据科学平台
  14. 物理防火墙是什么?有什么作用?
  15. Centos 7.6 挂载硬盘
  16. 零线和地线的区别、示波器如何测量市电
  17. java 调用 yed 绘制 流程图_用 yEd Graph Editor 绘制流程图(2)
  18. ios开发原生的扫描二维码的实现以及限制扫描区域rectOfInterest遇到的一些坑
  19. 爱笑程序员-笑话10则
  20. 交叉熵损失函数以及softmax损失函数

热门文章

  1. 2022年物流地产行业研究报告
  2. python networkx.algorithms.approximation.steinertree 介绍
  3. matlab 按照概率生成数字_matlab生成的随机数是真正随机的吗?
  4. python拼图游戏_Python加pyGame实现的简单拼图游戏实例
  5. 记一次微信小程序申请定位权限的开发
  6. D-CAP模式和DCS-control模式
  7. 2021-08-14校网比赛A题
  8. 短信验证码获取步骤详情!
  9. java项目——发邮件之阿里云邮箱推送服务(一)
  10. Linux C/C++ 学习路线(已拿腾讯、百度 offer)