文章目录

  • 总体介绍
    • 使用的知识总结:
    • http协议请求报文格式:
    • http协议响应消息格式:
    • 使用epoll模型作为web服务器:

总体介绍

使用浏览器作为客户端访问web服务器;

使用的知识总结:

socktet编程:
    socket -> setsockopt -> bind -> listen -> read -> write -> send -> recv -> close ;
  常用网络服务器模型:
    多进程版本;
    多线程版本;
    多路IO复用;
    第三方库libevent;
  TCP/IP四层模型:
     应用层 -> 传输层 -> 网络层 -> 数据链路层
  使用的协议:
    TCP/IP、http协议
  熟悉http协议的请求和应答协议

http协议请求报文格式:

1 请求行  GET /test.txt HTTP/1.1
2 请求行  健值对
3 空行  \r\n
4 数据

http协议响应消息格式:

1 状态行  200 表示成功, 404 表示请求的资源不存在
2 消息报头 健值对
3 空行 \r\nz
4 响应正文

使用epoll模型作为web服务器:

<webserver.c>

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>#include "pub.h"
#include "wrap.h"
//处理客户端请求
int http_request(int cfd, int epfd);int main(){//若web服务器给浏览器发送数据时,浏览器已经关闭连接,//则web服务器会受到SIGPIPE信号,会直接将进程终止,我们将该信号屏蔽struct sigaction act;act.sa_handler = SIG_IGN;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGPIPE, &act, NULL);//改变当前的工作目录,专门设置一个目录作为根目录char path[255] = {0};//getenv是函数名,从环境中取字符串,获取环境变量的值sprintf(path, "%s/%s", getenv("HOME"), "webpath");chdir(path);//创建socket--设置端口复用 -- bind//该函数 是自己写的 在 "wrap.c"中int lfd = tcp4bind(9999, NULL);//设置监听 "wrap.c"Listen(lfd, 128);//创建epoll树int epfd = epoll_create(1024);if(epfd < 0){perror("epoll_create error");close(lfd);return -1;}//将监听文件描述符上树struct epoll_event ev;ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);int i;int cfd;int sockfd;int nready;struct epoll_event events[1024];while(1){//等待事件发生nready = epoll_wait(epfd, events, 1024, -1);if(nready < 0){if(error == EINTR){//阻塞函数,可以被信号打断, 不应该退出进程continue;}break;}for(i = 0; i < nready; i++){sockfd = events[i].data.fd;//有客户端连接请求if(sockfd == lfd){//接受新的客户端连接 "wrap.c"cfd = Accept(lfd, NULL, NULL);//设置cfd为非阻塞int flags = fcntl(cfd, f_GETFL);flags |= O_NONBLOCK;fcntl(cfd, F_SETFL, flags);//将新的cfd上树ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);}else{//有客户端数据发来http_request(sockfd, epfd);}}}
}
int send_header(int cfd, char *code, char *msg, char *fileType, int len){char buf[1024] = {0};sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);sprintf(buf + strlen(buf), "Content-Length:%d\r\n", fileType);strcat(buf, "\r\n");Write(cfd, buf, strlen(buf));return 0;}
int send_file(int cfd, char *fileName){//打开文件int fd = open(fileName, O_RDONLY);if(fd < 0){perror("open error");return -1;}int n;char buf[1024];while(1){memset(buf, 0x00, sizeof(buf));n = read(fd, buf, sizeof(buf));if(n <= 0){break;}else{Write(cfd, buf, n);}}return 0;
}
int http_request(int cfd, int epfd){int n;char buf[1024];//读取请求行数据,分析出要请求的资源文件名memset(buf, 0x00, sizeof(buf));//整行读 "wrap.c"n = Readline(cfd, buf, sizeof(buf));if(n <= 0){printf("read error or client closed, n == [%d]\n", n);close(cfd);//将文件描述符下树epoll_ctl(epfd, EPOLL_CTL_DEL, cfd,  NULL);return -1;}printf("buf == [%s]\n", buf);char reqTtpe[16] = {0};char fileName[255] = {0};char protocal[16] = {0};sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);printf("[%s]\n", fileName);char *pFile = fileName;if(strlen(fileName) <= 1){strcpy(pFile, "./");}else{pFile = fileName + 1;}//转换汉字编码 "pub.c" strdecode(pFile, pFile);printf("[%s]\n",pFile);//循环读取玩剩余的数据,避免产生粘包while((n = Readline(cfd, buf, sizeof(buf)) > 0);//判断问价是否存在struct stat st;if(stat(pFile, &st) < 0){printf("file not exist\n");//发送头部信息send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);//发送文件内容send_file(cfd, "error.html");}//若文件存在else{//判断文件类型//普通文件if(S_ISREG(st.st_mode)){printf("file exist\n");send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);//发送文件内容send_file(pFile);}else if(S_ISDIR(st.st_mode)){printf("目录文件\n");char buffer[1024];//发送文件头部信息send_header(cfd, "200", "OK", get_mime_type(".html"), 0);//发送文件信息,如果是目录用html文件将该目录中所有文件名 以超链接形式显示在//发送文件头部send_file(cfd, "html/dir_header.html");//文件列表信息 "<dirent.h>"struct dirent **namelist;int num;num = scandir(pFile, &namelist, NULL, alphasort);if(num < 0){perror("scandir error");close(cfd);epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1;}else{while(num--){printf("%s/n", namelist[num]->d_name);memset(buffer, 0x00, sizeof(buffer));if(namelist[num]->d_type == DT_DIR){sprintf(buffer, "<li><a href= %s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);}else{sprintf(buffer, "<li><a href = %s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);}free(namelist[num]);// "wrap.c"Write(cfd, buffer, sizeof(buffer));}free(namelist);}send_file(cfd, "html/dir_tail.html");}} return 0;
}

<wrap.h>

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

“wrap.c”

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
//绑定错误显示和退出
void perr_exit(const char *s)
{perror(s);exit(-1);
}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ((n = accept(fd, sa, salenptr)) < 0) {if ((errno == ECONNABORTED) || (errno == EINTR))//ECONNABORTED 代表连接失败 ETINTR 代表被信号打断goto again;elseperr_exit("accept error");}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = bind(fd, sa, salen)) < 0)perr_exit("bind error");return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = connect(fd, sa, salen)) < 0)perr_exit("connect error");return n;
}int Listen(int fd, int backlog)
{int n;if ((n = listen(fd, backlog)) < 0)perr_exit("listen error");return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n = socket(family, type, protocol)) < 0)perr_exit("socket error");return n;
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = read(fd, ptr, nbytes)) == -1) {if (errno == EINTR)//被信号打断应该继续读goto again;elsereturn -1;}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = write(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t  nleft;              //usigned int 剩余未读取的字节数ssize_t nread;              //int 实际读到的字节数char   *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ((nread = read(fd, ptr, nleft)) < 0) {if (errno == EINTR)nread = 0;elsereturn -1;} else if (nread == 0)break;nleft -= nread;//防止一次数据没有读完ptr += nread;//指针需要向后移动}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {if (nwritten < 0 && errno == EINTR)nwritten = 0;elsereturn -1;}nleft -= nwritten;ptr += nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];//定义了100的缓冲区if (read_cnt <= 0) {again://使用缓冲区可以避免多次从底层缓冲读取数据--为了提高效率if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {if (errno == EINTR)goto again;return -1;} else if (read_cnt == 0)return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;//从缓冲区取数据return 1;
}
//读取一行
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = my_read(fd, &c)) == 1) {*ptr++ = c;if (c  == '\n')//代表任务完成break;} else if (rc == 0) {//对端关闭*ptr = 0;//0 = '\0'return n - 1;} elsereturn -1;}*ptr  = 0;return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()if(IP == NULL){//如果这样使用 0.0.0.0,任意ip将可以连接serv_addr.sin_addr.s_addr = INADDR_ANY;}else{if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){perror(IP);//转换失败exit(1);}}serv_addr.sin_family = AF_INET;serv_addr.sin_port   = htons(port);int opt = 1;setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));return lfd;
}

“pub.c”

//该函数主要处理文件类型,以及返回HTTP文件类型格式

#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{char* dot;dot = strrchr(name, '.'); //自右向左查找‘.’字符;如不存在返回NULL/**charset=iso-8859-1  西欧的编码,说明网站采用的编码是英文;*charset=gb2312       说明网站采用的编码是简体中文;*charset=utf-8           代表世界通用的语言编码;*                        可以用到中文、韩文、日文等世界上所有语言编码上*charset=euc-kr     说明网站采用的编码是韩文;*charset=big5          说明网站采用的编码是繁体中文;**以下是依据传递进来的文件名,使用后缀判断是何种文件类型*将对应的文件类型按照http定义的关键字发送回去*/if (dot == (char*)0)return "text/plain; charset=utf-8";if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)return "text/html; charset=utf-8";if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)return "image/jpeg";if (strcmp(dot, ".gif") == 0)return "image/gif";if (strcmp(dot, ".png") == 0)return "image/png";if (strcmp(dot, ".css") == 0)return "text/css";if (strcmp(dot, ".au") == 0)return "audio/basic";if (strcmp( dot, ".wav") == 0)return "audio/wav";if (strcmp(dot, ".avi") == 0)return "video/x-msvideo";if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)return "video/quicktime";if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)return "video/mpeg";if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)return "model/vrml";if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)return "audio/midi";if (strcmp(dot, ".mp3") == 0)return "audio/mpeg";if (strcmp(dot, ".ogg") == 0)return "application/ogg";if (strcmp(dot, ".pac") == 0)return "application/x-ns-proxy-autoconfig";return "text/plain; charset=utf-8";
}
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,* carriage return, or a CRLF combination.  Terminates the string read* with a null character.  If no newline indicator is found before the* end of the buffer, the string is terminated with a null.  If any of* the above three line terminators is read, the last character of the* string will be a linefeed and the string will be terminated with a* null character.* Parameters: the socket descriptor*             the buffer to save the data in*             the size of the buffer* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//获得一行数据,每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{int i = 0;char c = '\0';int n;while ((i < size - 1) && (c != '\n')){n = recv(sock, &c, 1, 0);/* DEBUG printf("%02X\n", c); */if (n > 0){if (c == '\r'){n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除/* DEBUG printf("%02X\n", c); */if ((n > 0) && (c == '\n'))recv(sock, &c, 1, 0);elsec = '\n';}buf[i] = c;i++;}elsec = '\n';}buf[i] = '\0';return(i);
}//下面的函数第二天使用
/** 这里的内容是处理%20之类的东西!是"解码"过程。* %20 URL编码中的‘ ’(space)* %21 '!' %22 '"' %23 '#' %24 '$'* %25 '%' %26 '&' %27 ''' %28 '('......* 相关知识html中的‘ ’(space)是&nbsp*/
void strdecode(char *to, char *from)
{for ( ; *from != '\0'; ++to, ++from) {if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符*to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8from += 2;                      //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符} else*to = *from;}*to = '\0';
}//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{if (c >= '0' && c <= '9')return c - '0';if (c >= 'a' && c <= 'f')return c - 'a' + 10;if (c >= 'A' && c <= 'F')return c - 'A' + 10;return 0;
}//"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{int tolen;for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {*to = *from;++to;++tolen;} else {sprintf(to, "%%%02x", (int) *from & 0xff);to += 3;tolen += 3;}}*to = '\0';
}

“pub.h”

#ifndef _PUB_H
#define _PUB_H
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
char *get_mime_type(char *name);
int get_line(int sock, char *buf, int size);
int hexit(char c);//16进制转10进制
void strencode(char* to, size_t tosize, const char* from);//编码
void strdecode(char *to, char *from);//解码
#endif

效果


在进程工作目录下webpath 的文件

点击任意一个超链接都可以跳转到该 文件/目录 获取该文件的内容


后台DEBUG的结果

缺点

该小项目是我跟着视频做的,目前还有一些问题有待完善。
1.该程序在linux中运行,windows中 好像不可以。
2.该程序好像只能从根目录开始,才能完全好使,如果直接输入:192.168.64.129/html 不是从根目录开始,超链接现在还不好使,文件路径不对,不能打开文件,产生404 NO FOUND 错误。
3. 该进程现在只能在虚拟机中,用本机访问,不能用另外一台机器访问。
4. 博主水平有限,仅仅是跟着视频学习,对于如何让web服务器被互联网其他用户访问到,对于这一点,博主一点也不知道,希望有懂的各位,在评论或私信博主,让博主多了解一点知识,不胜感激。

如果有任何问题或者疑问,欢迎在评论区或者指出错误,共同学习

附录

网络编程部分整体回顾:
1 协议的概念
2 OSI7层模型: 物数网传会表应
3 TCP/IP四层模型: 应用层 传输层 网络层 数据链路层
4 几种常见的协议格式, TCP UDP ARP IP
5 socket API编程:
网络字节序 主机字节序
字节序常用到的函数: htons htonl ntohs ntohl
IP地址转换函数: inet_pton inet_ntop
INADDR_ANY
socket API常用的函数:
socket bind listen accept connect read | recv send|write close setsockopt
编写简单版本的服务端程序和客户端程序的流程:

5 三次握手和四次挥手 滑动窗口
TCP 状态转换图
半关闭: close和shutdown的区别
心跳包
同步和异步
阻塞和非阻塞
6 高并发服务器模型:
多进程版本
多线程版本
多路IO复用技术: select poll epoll (ET LT 边缘非阻塞模式)
epoll反应堆
线程池
UDP通信
本地socket通信
第三方库: libevent
事件驱动, 和epoll反应堆一样
普通事件
bufferevent
连接监听器
7 web服务端开发:
html语法
http协议: 请求消息格式 响应消息格式
web服务端开发流程:
libevent作为web服务框架的流程

web服务器的开发(简易版本)相关推荐

  1. 五、Web服务器——MVC开发模式 EL表达式 JSTL 学习笔记

    今日内容 1. JSP:1. 指令2. 注释3. 内置对象2. MVC开发模式 3. EL表达式 4. JSTL标签 5. 三层架构 JSP: 1. 指令* 作用:用于配置JSP页面,导入资源文件* ...

  2. Java Web基础入门第八讲 Java Web开发入门——初始WEB服务器

    WEB开发的相关知识 WEB,在英语中web即表示网页的意思,它用于表示Internet主机上供外界访问的资源.Internet上供外界访问的Web资源分为: 静态web资源(如html页面):指we ...

  3. linux下用c 开发web,用C一步步开发web服务器(2)

    顺着教程1往下走,这个章节我们需要开发支持并发的web服务器,并加入容错处理 首先加入容错处理,建议将socket函数封装在新的wrap_socket.c文件中,并创建他的.h文件,Server端in ...

  4. web服务器虚拟,虚拟web服务器

    虚拟web服务器 内容精选 换一换 本节操作介绍切换虚拟私有云的操作步骤.仅支持单网卡切换虚拟私有云.切换虚拟私有云会导致云服务器网络中断.切换虚拟私有云过程中,请勿操作云服务器的弹性公网IP,或对云 ...

  5. 服务器关闭重启后客户端socket能自动连接吗_用Python 撸一个 Web 服务器

    从一个 Hello World 程序说起 要编写 Web 服务器,需要用到一个 Python 内置库 socket.Socket 是一个比较抽象的概念,中文叫套接字,它代表一个网络连接.两台计算机之间 ...

  6. MyEclipse网站服务器,myeclipse配置web服务器

    myeclipse配置web服务器 内容精选 换一换 简要介绍uWSGI是一个Web服务器,它实现了WSGI协议.uwsgi.http等协议.Nginx中HttpUwsgiModule的作用是与uWS ...

  7. 简易的web全栈开发——服务器部分

    简易的web全栈开发--服务器部分 java sdk的安装 nginx的安装 mysql的安装 node的安装 连接数据库 第一步,买一个云服务器,过程略,买的是个2核4G的轻量应用服务器,预装的是c ...

  8. python开发web服务器——搭建简易网站

    转自:http://blog.csdn.net/baidu_35085676/article/details/69807145?%3E 目标 用已有的丰富图片资源建一个看图网站 条件 开发语言: py ...

  9. .NET5 开发手机提词应用,基于内嵌Web服务器及PowerPoint自动化

    项目说明 我使用电脑录制视频教程的时候,会展示PPT给观众,同时也有一些提示性的文字给我自己看.这就类似于很多电视节目录制现场的"提词器". 节目录制现场的提词器 在PC环境下,P ...

最新文章

  1. 80网口打印机ip固定工具_网络打印机端口用名称好是还是IP好?
  2. load、loads、dump、dumps的区别
  3. 轻松搭建基于 SpringBoot Vue 的 Web 商城应用
  4. 自然语言处理之AI深度学习顶级实战
  5. AQS源码阅读笔记(一)
  6. 转:c#委托事件实现窗体传值通信
  7. 以实际产品为例, 进行软件工程训练的作业
  8. 微信小程序:一起玩连线,一个算法来搞定
  9. linux查看内核分区,如何根据bootloader中MTD分区信息修改linux内核中的MTD分区
  10. java float.max value_java – Float.POSITIVE_INFINITY和Float.MAX_VALUE有什么区别?
  11. qt实现百度首页(仅界面,功能未实现)
  12. 天正网络版服务器填写位置,教你如何在天正里面输入坐标定位
  13. 怎样把mysql表转换为hbase表_导出table数据库表
  14. C语言小案例_微信小程序开发(教学大纲) | 附视频
  15. 6.11编写计算正方体、圆柱体、球体的表面积和体积的类。
  16. 天啦噜,小白后台的一波新功能,看完世界杯 看这里!(最后有福利 哦~~)
  17. Fragment already added 问题
  18. 任意斜率的中点画线算法
  19. 拼多多怎么调整后台数据|聚创卓跃
  20. iP地址计算子网掩码(附示例)

热门文章

  1. Revit二次开发之族库管理系统
  2. STM32F4的LED点灯
  3. Chrome网页翻译失效的解决方案
  4. video标签不能自动播放的原因
  5. 在linux终端下使用scp与远程windows传输文件
  6. CE实现植物大战僵尸之阳光篇
  7. linux下挂载新的磁盘
  8. 如何让word左边显示目录
  9. java继承1—上溯造型
  10. termux怎么修改php版本,玩转Termux