C++学习记录011——Linux网络编程下篇

系列文章目录

第一章 C语言基础知识
第二章 C语言高级编程
第三章 C语言数据结构
第四章 C语言高级数据结构
第五章 C++核心编程
第六章 C++STL
第七章 Qt桌面应用开发
第八章 Linux系统编程上篇
第九章 Linux系统编程下篇
第十章 Linux网络编程上篇
第十一章 Linux网络编程下篇



一、libevent

  1. libevent简介和安装。Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库。

    • 事件驱动, 高性能, 轻量级, 专注于网络。
      源代码精炼, 易读。
      跨平台。
      支持多种I/O多路复用技术, 如epoll select poll等。
      支持I/O和信号等事件。
    • 安装:从官网http://libevent.org上下载安装文件之后, 将安装文件上传到linux系统上,解压libevent-2.0.22-stable.tar.gz,cd到libevent-2.0.22-stable目录下, 查看README文件, 该文件里描述了安装的详细步骤, 可参照这个文件进行安装。
    • 进入源码目录:执行配置./configure, 检测安装环境, 生成makefile,默认会安装到系统默认的路径下, 编译的时候可以不指定头文件和库文件所在的路径.;执行make命令编译整个项目文件;执行sudo make install进行安装,可以将刚刚编译成的库文件和可执行文件以及一些头文件拷贝到/usr/local目录下:头文件拷贝到了/usr/local/include目录下,库文件拷贝到了/usr/local/lib目录下。
    • libevent库的使用:进入到libevent-2.0.22-stable/sample下, 可以查看一些示例源代码文件,使用libevent库编写代码在编译程序的时候需要指定库名:-levent;安装文件的libevent库文件所在路径:libevent-2.0.22-stable/.libs;编写代码的时候用到event.h头文件, 或者直接参考sample目录下的源代码文件也可以#include <event2/event.h>
    • 编译示例代码hello-world.c:gcc -o hello-world hello-world.c -levent。由于安装的时候已经将头文件和库文件拷贝到了系统头文件所在路径/usr/local/include和系统库文件所在路径/usr/local/lib, 所以这里编译的时候可以不用指定-I和-L。
    • 测试: 在另一个终端窗口进行测试, 输入: nc 127.1 9995, 然后回车立刻显示Hello, World!字符串。
  2. libevent的核心实现:在linux上, 其实质就是epoll反应堆。libevent是事件驱动, epoll反应堆也是事件驱动, 当要监测的事件发生的时候, 就会调用事件对应的回调函数, 执行相应操作。事件回调函数是由用户开发的, 但是不是由用户显示去调用的, 而是由libevent去调用的。

  3. libevent的地基-event_base:使用libevent 函数之前需要分配一个或者多个 event_base 结构体, 每个event_base结构体持有一个事件集合, 可以检测以确定哪个事件是激活的, event_base结构相当于epoll红黑树的树根节点, 每个event_base都有一种用于检测某种事件已经就绪的 “方法”(回调函数)。通常情况下可以通过event_base_new函数获得event_base结构。

    1 struct event_base *event_base_new(void);    
    //event.h的L:337
    函数说明: 获得event_base结构
    参数说明: 无
    返回值: 
    成功返回event_base结构体指针;
    失败返回NULL;
    2 void event_base_free(struct event_base *);   //event.h的L:561
    函数说明: 释放event_base指针
    3 int event_reinit(struct event_base *base);  
    //event.h的L:349
    函数说明: 如果有子进程, 且子进程也要使用base, 则子进程需要对event_base重新初始化, 此时需要调用event_reinit函数.
    函数参数: 由event_base_new返回的执行event_base结构的指针
    返回值: 成功返回0, 失败返回-1
    
    查看libevent支持的后端的方法有哪些:
    const char **event_get_supported_methods(void);
    函数说明: 获得当前系统(或者称为平台)支持的方法有哪些
    参数: 无
    返回值: 返回二维数组, 类似与main函数的第二个参数**argv.
    const char * event_base_get_method(const struct event_base *base);
    函数说明: 获得当前base节点使用的多路io方法
    函数参数: event_base结构的base指针.
    返回值: 获得当前base节点使用的多路io方法的指针
    
    • 对于不同系统而言, event_base就是调用不同的多路IO接口去判断事件是否已经被激活, 对于linux系统而言, 核心调用的就是epoll, 同时支持poll和select。
  4. 等待事件产生-循环等待event_loop:libevent在地基打好之后, 需要等待事件的产生, 也就是等待事件被激活, 所以程序不能退出, 对于epoll来说, 我们需要自己控制循环, 而在libevent中也给我们提供了API接口, 类似while(1)的功能。

    int event_base_dispatch(struct event_base *base);   //event.h的L:364
    函数说明: 进入循环等待事件
    参数说明:由event_base_new函数返回的指向event_base结构的指针
    调用该函数, 相当于没有设置标志位的event_base_loop。程序将会一直运行, 直到没有需要检测的事件了, 或者被结束循环的API终止
    
    int event_base_loopexit(struct event_base *base, const struct timeval *tv);
    int event_base_loopbreak(struct event_base *base);
    struct timeval {
           long    tv_sec;                   
           long    tv_usec;           
    };
    
    • 两个函数的区别是如果正在执行激活事件的回调函数, 那么event_base_loopexit将在事件回调执行结束后终止循环(如果tv时间非NULL, 那么将等待tv设置的时间后立即结束循环), 而event_base_loopbreak会立即终止循环。
  5. 使用libevent库的步骤。

    1 创建根节点--event_base_new
    
    2 设置监听事件和数据可读可写的事件的回调函数.(上树)
    设置了事件对应的回调函数以后, 当事件产生的时候会自动调用回调函数
    
    3 事件循环--event_base_dispatch
    相当于while(1), 在循环内部等待事件的发生,  若有事件发生则会触发事件对应的回调函数。
    
    4 释放根节点--event_base_free
    释放由event_base_new和event_new创建的资源, 分别调用event_base_free和event_free函数.
    
  6. 事件驱动-event:事件驱动实际上是libevent的核心思想。
    在这里插入图片描述

  • 主要的状态变化:无效的指针,此时仅仅是定义了 struct event *ptr;
    非未决:相当于创建了事件, 但是事件还没有处于被监听状态, 类似于我们使用epoll的时候定义了struct epoll_event ev并且对ev的两个字段进行了赋值, 但是此时尚未调用epoll_ctl对事件上树。
    未决:就是对事件开始监听, 暂时未有事件产生。相当于调用epoll_ctl对要监听的事件上树, 但是没有事件产生。
    激活:代表监听的事件已经产生, 这时需要处理, 相当于调用epoll_wait函数有返回, 当事件被激活以后, libevent会调用该事件对应的回调函数。
  • libevent的事件驱动对应的结构体为struct event,下面介绍一下主要的函数。
    回调函数, 原型如下:
    typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
    回调函数的参数就对应于event_new函数的fd, event和arg
    
    struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *arg);
    函数说明: event_new负责创建event结构指针, 同时指定对应的地基base, 还有对应的文件描述符, 事件, 以及回调函数和回调函数的参数。
    参数说明:
    base: 对应的根节点--地基
    fd: 要监听的文件描述符
    events:要监听的事件
    	  	#define  EV_TIMEOUT    0x01   //超时事件
    		#define  EV_READ       0x02    //读事件
    		#define  EV_WRITE      0x04    //写事件
    		#define  EV_SIGNAL     0x08    //信号事件
    		#define  EV_PERSIST     0x10    //周期性触发
    		#define  EV_ET         0x20    //边缘触发, 如果底层模型支持设置										 则有效, 若不支持则无效.
    	若要想设置持续的读事件则: EV_READ | EV_PERSIST
    event_new函数, 只是对应一个信号事件而已, 处理机制略有不同。涉及到信号的函数实际上就是一个宏定义.
    #define evsignal_new(b, x, cb, arg)	\
    event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
    
    int event_add(struct event *ev, const struct timeval *timeout);
    函数说明: 将非未决态事件转为未决态, 相当于调用epoll_ctl函数(EPOLL_CTL_ADD), 开始监听事件是否产生, 相当于epoll的上树操作
    参数说明:
    	ev: 调用event_new创建的事件
    	timeout: 限时等待事件的产生, 也可以设置为NULL, 没有限时
    
    int event_del(struct event *ev);
    函数说明: 将事件从未决态变为非未决态, 相当于epoll的下树(epoll_ctl调用EPOLL_CTL_DEL操作)操作
    参数说明: ev指的是由event_new创建的事件
    
    void event_free(struct event *ev);
    函数说明: 释放由event_new申请的event节点
    
    
    
  1. 编写一个基于event实现的tcp服务器。

    1 创建socket---socket()
    2 设置端口复用---setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int))
    3 绑定--bind()
    4 设置监听--listen()
    5 创建地基
    struct event_base *base = event_base_new()
    6 (创建lfd对应的事件)创建要监听的事件event, 主要就是监听事件和读数据的事件
    //struct event *ev = event_new(base, lfd, EV_READ|EV_PERSIST, conncb, NULL);
    struct event *ev = event_new(base, lfd, EV_READ|EV_PERSIST, conncb, base);
    设置好监听事件的回调函数,然后event_add上树---->有新的连接, 则调用accept接受新的连接---->
    将这个新的连接设置好回调函数(一般是设置读事件), 然后继续event_add上树, 若有客户端关闭连接则从树上摘除该事件节点.
    7 上event_base地基
    event_add(ev, NULL);
    8 进入事件循环
    event_base_dispatch(base);
    9 释放资源
    event_base_free(base);
    event_free(ev);
    
    • 问题:当使用多个客户端(nc命令模拟客户端程序)进行测试的时候, 特别是当关闭所有客户端程序的时候, 若再次开启nc命令, 会发现异常。
    • 原因:由于代码中connev是一个全局变量, 所以connev只能保留最后一次所赋的值, 当客户端退出后, 服务端会调用event_del(connev)从根节点上摘除该事件, 此时其实从base节点上摘掉的是最后一个event事件节点, 所以最后一个客户端会出现异常, 其实只要是开启了多个客户端, 而且关闭客户端的时候只要不是关闭最后一个客户端, 都会出现这种异常情况。
    • 解决:可以将对应事件的文件描述符和事件做一个映射, 说的通俗一点就是可以将fd和event定义在一个结构体当中, 然后定义一个结构体数组, 这样可以使fd和event形成一个一对一的映射关系, 通过fd就可以找到event。
  2. 自带buffer的事件-bufferevent:bufferevent实际上也是一个event, 只不过比普通的event高级一些, 它的内部有两个缓冲区, 以及一个文件描述符(网络套接字)。一个网络套接字有读和写两个缓冲区, bufferevent同样也带有两个缓冲区, 还有就是libevent事件驱动的核心回调函数, 那么四个缓冲区以及触发回调的关系如下,从图中可以得知, 一个bufferevent对应两个缓冲区, 三个回调函数, 分别是写回调, 读回调和事件回调。
    在这里插入图片描述

    • bufferevent有三个回调函数:
      读回调 – 当bufferevent将底层读缓冲区的数据读到自身的读缓冲区时触发读事件回调.需要注意的是: 数据有内核到bufferevent的过程不是用户程序执行的, 是由bufferevent内部操作的。
      写回调 – 当bufferevent将自身写缓冲的数据写到底层写缓冲区的时候触发写事件回调, 由于数据最终是写入了内核的写缓冲区中, 应用程序已经无法控制, 这个事件对于应用程序来说基本没什么用, 只是通知功能。
      事件回调 – 当bufferevent绑定的socket连接, 断开或者异常的时候触发事件回调。

    • 回调函数的原型:

      //读写回调
      typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
      	
      //事件回调
      typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);
      	What 代表 对应的事件
      		BEV_EVENT_EOF--遇到文件结束指示
      		BEV_EVENT_ERROR--发生错误
      		BEV_EVENT_TIMEOUT--发生超时
      		BEV_EVENT_CONNECTED--请求的过程中连接已经完成
      
      int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);//常用
      bufferevent_write是将data的数据写到bufferevent的写缓冲区
      
      int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
      bufferevent_write_buffer 是将数据写到写缓冲区另外一个写法, 实际上bufferevent的内部的两个缓冲区结构就是struct evbuffersize_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);//常用
      bufferevent_read 是将bufferevent的读缓冲区数据读到data中, 同时将读到的数据从bufferevent的读缓冲清除。
      
      int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);
      bufferevent_read_buffer 将bufferevent读缓冲数据读到buf中, 接口的另外一种。
      
      int bufferevent_enable(struct bufferevent *bufev, short event);
      int bufferevent_disable(struct bufferevent *bufev, short event);
      bufferevent_enable与bufferevent_disable是设置事件是否生效, 如果设置为disable, 事件回调将不会被触发。
      
      
    • 主要使用的函数如下:

      struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);
      函数说明: bufferevent_socket_new 对已经存在socket创建bufferevent事件, 可用于后面讲到的连接监听器的回调函数中.
      参数说明:
      	base :对应根节点
      	fd   :文件描述符
      	options : bufferevent的选项
      		BEV_OPT_CLOSE_ON_FREE  -- 释放bufferevent自动关闭底层接口(当bufferevent被释放以后, 文件描述符也随之被close)    
      		BEV_OPT_THREADSAFE  -- 使bufferevent能够在多线程下是安全的
      
      int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *serv, int socklen);
      函数说明: 该函数封装了底层的socket与connect接口, 通过调用此函数, 可以将bufferevent事件与通信的socket进行绑定, 
      参数如下:
      	bev – 需要提前初始化的bufferevent事件
      	serv – 对端(一般指服务端)的ip地址, 端口, 协议的结构指针
      	socklen – 描述serv的长度
      说明: 调用此函数以后, 通信的socket与bufferevent缓冲区做了绑定, 后面调用了bufferevent_setcb函数以后, 
      会对bufferevent缓冲区的读写操作的事件设置回调函数, 当往缓冲区中写数据的时候会触发写回调函数, 
      当数据从socket的内核缓冲区读到bufferevent读缓冲区中的时候会触发读回调函数.
      
      void bufferevent_free(struct bufferevent *bufev);
      函数说明: 释放bufferevent
      
      void bufferevent_setcb(struct bufferevent *bufev,
          		bufferevent_data_cb readcb, bufferevent_data_cb writecb,
      			bufferevent_event_cb eventcb, void *cbarg);
      函数说明: bufferevent_setcb用于设置bufferevent的回调函数, readcb, writecb, 
      eventcb分别对应了读回调, 写回调, 事件回调, cbarg代表回调函数的参数。
      
      
  3. 链接监听器-evconnlistener:链接监听器封装了底层的socket通信相关函数, 比如socket, bind, listen, accept这几个函数。链接监听器创建后实际上相当于调用了socket, bind, listen, 此时等待新的客户端连接到来, 如果有新的客户端连接, 那么内部先进行调用accept处理, 然后调用用户指定的回调函数。函数声明所在的头文件: event2/listener.h

    struct evconnlistener *evconnlistener_new_bind(
    	struct event_base *base,
        evconnlistener_cb cb, 
        void *ptr, //回调函数参数
        unsigned flags, 
        int backlog,
    	const struct sockaddr *sa, 
    	int socklen
    	);
    flags:
    	LEV_OPT_LEAVE_SOCKETS_BLOCKING   文件描述符为阻塞的
    	LEV_OPT_CLOSE_ON_FREE            关闭时自动释放
    	LEV_OPT_REUSEABLE                端口复用
    	LEV_OPT_THREADSAFE               分配锁, 线程安全
    
    • 函数说明:是在当前没有套接字的情况下对链接监听器进行初始化, 看最后2个参数实际上就是bind使用的关键参数, backlog是listen函数的关键参数(略有不同的是, 如果backlog是-1, 那么监听器会自动选择一个合适的值, 如果填0, 那么监听器会认为listen函数已经被调用过了), ptr是回调函数的参数, cb是有新连接之后的回调函数, 但是注意这个回调函数触发的时候, 链接器已经处理好新连接了, 并将与新连接通信的描述符交给回调函数。
    struct evconnlistener *evconnlistener_new(
    	struct event_base *base,
    	evconnlistener_cb cb, 
    	void *ptr, 
    	unsigned flags, 
    	int backlog,
    	evutil_socket_t fd
    );
    两个函数的回调函数
    typedef void (*evconnlistener_cb)(struct evconnlistener *evl, 
    			evutil_socket_t fd, struct sockaddr *cliaddr, int socklen, void *ptr);
    
    
    • evconnlistener_new函数与前一个函数不同的地方在与后2个参数, 使用本函数时, 认为socket已经初始化好, 并且bind完成, 甚至也可以做完listen, 所以大多数时候, 我们都可以使用第一个函数。
    • 回调函数fd参数是与客户端通信的描述符, 并非是等待连接的监听的那个描述符, 所以cliaddr对应的也是新连接的对端地址信息, 已经是accept处理好的。struct sockaddr *cliaddr, int socklen是新连接的对端地址信息。
    • 其余函数。
      void evconnlistener_free(struct evconnlistener *lev);
      函数说明: 释放链接监听器
      
      int evconnlistener_enable(struct evconnlistener *lev);
      函数说明: 使链接监听器生效
      
      int evconnlistener_disable(struct evconnlistener *lev);
      函数说明: 使链接监听器失效
      
  4. libevent代码分析。

    • 开发流程
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

二、网络编程阶段项目

  1. 项目简介:

    • 项目目标:实现一个web服务器,可以在浏览器页面请求资源页面。
    • Web服务器开发准备:1 开发网络服务器:多路IO复用:epoll select poll; 多进程或多线程;第三方库:libevent库。2 熟悉http协议:请求协议;应答协议。3 使用的协议有http协议+TCP协议。TCP协议:建立连接的三次握手,连接建立完成后接着是数据传输。
    • Web服务器:首先解析浏览器发来的请求数据,得到请求的文件名;若文件存在判断文件类型:若是普通文件,则发生文件内容给浏览器;若是目录文件,则发送文件列表。若文件不存在,则发送一个错误页给浏览器;
  2. Html语言:Html(Hyper Texture Markup Language)是超文本标记语言,在计算机中以 .html或者.htm作为扩展名,可以被浏览器识别,就是经常见到的网页。

    1.	<!doctype html> 声明文档类型,可以不写
    2.	<html>开始 和 </html>结束,属于html的根标签
    3.	<head></head>头部标签,头部标签内一般有 <title></title>
    4.	<body></body>主体标签,一般用于显示内容
    
    • 题目标签,文本标签,列表标签,图片标签,超链接标签。
    题目标签: 共有6种,<h1>,<h2>,…<h6>,其中<h1>最大,<h6>最小
    文本标签: 
    	<font>标签,可以设置颜色和字体大小属性
    	size属性,大小范围为1-7,其中7最大,1最小
    	换行标签 <br/> 水平线标签 <hr/>
    列表标签:
    	无序列表和有序列表,分别对应<ul><ol>
    	无序列表可以设置type属性:
    		实心圆圈:type=disc
    		空心圆圈:type=circle
    		小方块:  type=square
    	有序列表同样可以设置type属性
    		数字:type=1,也是默认方式
    		英文字母:type=a或type=A
    		罗马数字:type=i或type=I
    图片标签:
    	1.	src=”3.gif” 图片来源,必写
    	2.	alt=”小岳岳” 图片不显示时,显示的内容
    	3.	title=”我的天呐” 鼠标移动到图片上时显示的文字
    	4.	width=”600” 图片显示的宽度
    	5.	height=”400” 图片显示的高度
    
    超链接标签
    	使用<a>,同样需要设置属性表明要链接到哪里
    	1.	href=”http://www.itcast.cn”,前往地址,必填,注意要写http://
    	2.	title=”前往传智” 鼠标移动到链接上时显示的文字
    	3.	target=”_self”或者”_blank”,_self是默认值,在自身页面打开,_blank是新开页面前往连接地址
    	
    
  3. http超文本传输协议:http协议和html前面的ht都是超文本的意思,所以http与html是配合非常紧密的一对,我们可以认为http就是为了传输html这样的文件,http位于应用层,侧重于解释。http协议对消息区分可以分为请求消息和响应消息。

    • 我们要开发的服务器与浏览器通信采用的就是http协议,在浏览器想访问一个资源的时候,在浏览器输入访问地址(例如http://127.0.0.1:8000),地址输入完成后当敲击回车键的时候,浏览器就将请求消息发送给服务器。
    • 请求消息分为四部分内容:
    1. 请求行 说明请求类型,要访问的资源,以及使用的http版本。
    2. 请求头 说明服务器使用的附加信息,都是键值对,比如表明浏览器类型。
    3. 空行 不能省略-而且是\r\n,包括请求行和请求头都是以\r\n结尾。
    4. 请求数据 表明请求的特定数据内容,可以省略-如登陆时,会将用户名和密码内容作为请求数据。
      在这里插入图片描述
    • http协议有很多种请求类型,对我们来说常见的用的最多的是get和post请求。常见的请求类型如下:
      1. Get 请求指定的页面信息,并返回实体主体
      2. Post 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
      3. get 和 post 请求都是请求资源,而且都会提交数据,如果提交密码信息用get请求,就会明文显示,而post则不会显示出涉密信息。
    • 响应消息是代表服务器收到请求消息后,给浏览器做的反馈,所以响应消息是服务器发送给浏览器的,响应消息也分为四部分:
      1. 状态行 包括http版本号,状态码,状态信息
      2. 消息报头 说明客户端要使用的一些附加信息,也是键值对
      3. 空行 \r\n 同样不能省略
      4. 响应正文 服务器返回给客户端的文本信息

在这里插入图片描述

  • http常见状态码:
    1xx  指示信息--表示请求已接收,继续处理
    2xx  成功--表示请求已被成功接收、理解、接受
    3xx  重定向--要完成请求必须进行更进一步的操作
    4xx  客户端错误--请求有语法错误或请求无法实现
    5xx  服务器端错误--服务器未能实现合法的请求
    200 OK  客户端请求成功
    200 OK  客户端请求成功
    403 Forbidden        服务器收到请求,但是拒绝提供服务
    404 Not Found        请求资源不存在,eg:输入了错误的URL
    
    
  1. web服务器开发:使用http协议传送html文件,那么我们如何搭建我们的服务器呢?注意http只是应用层协议,我们仍然需要选择一个传输层的协议来完成我们的传输数据工作,所以开发协议选择是TCP+HTTP,也就是说服务器搭建浏览依照TCP,对数据进行解析和响应工作遵循HTTP的原则。思路很清晰,编写一个TCP并发服务器,只不过收发消息的格式采用的是HTTP协议。
    在这里插入图片描述

    • 为了支持并发服务器,我们可以有多个选择,比如多进程服务器,多线程服务器,select,poll,epoll等多路IO工具都可以,甚至也可以使用libevent进行开发。
  2. 基于epoll的web服务器:epoll在大量并发少量活跃的情况下效率很高,所以本文以epoll为例,介绍epoll开发的主体流程。

选择使用epoll模型作为web服务器:
	1 创建socket,得到监听文件描述符lfd-----socket();
	2 设置端口复用--- setsockopt()
	3 绑定---bind()
	4 监听---listen()
	5 创建epoll树,得到树根文件描述符epfd--epoll_create()
	6 将监听文件描述符上epoll树--epoll_ctl(epfd,EPOLL_CTL_ADD...)
	7 while(1)
	  {
	   //等待事件的发生
	  	nready = epoll_wait();
	  	if(nready<0)
	  	{
		  	if(errnp==EINTR)
		  	{
			  	continue;
		  	}
			  break;
	  	}
	  	//有事件发生,循环处理每一个文件描述符
	  	for(i=0; i<nready; i++)
	  	{
	  	//若是有新的客户端连接到来,则接受新的客户端连接, 然后上epoll树
	  	sockfd=events[i].data.fd;
	  	if(sockfd==lfd)
	  	{
		  	//接受新的客户端连接
	  		cfd = accept();
	  		//将新的cfd上epoll树
	  		epoll_ctl(epfd, cfd, EPOLL_CTL_ADD, &ev);
	  	}
	  	//有数据发来的情况
	  	else
	  	{ 	
		  	//接受数据并进行处理
		  	http_request(cfd);
	  	}
	  	}	
	  }	
	 //处理客户端的请求
	 int http_request(int cfd)
	 {
	 	//读取数据
	 	//先读取一行, 可以获得浏览器想请求的文件file
	 	//读取请求行
	 	Readline();
	 	//分析请求行,得到要请求的资源文件名file    如:GET /hanzi.c /HTTP1.1
	 	//然后循环将剩余的内核缓冲区数据读完
	 	while((n=Readline())>0);
	 	//查看本地是否有这个文件
	 	//判断文件是否存在
	 	stat();
 		---若没有这个文件, 则组织应答信息并且将404.jpg文件内容发送给浏览器
	 		http响应格式消息+错误页正文内容
 		---若有个这个文件, 则判断文件类型
 			----若是普通文件, 则组织应答消息并且将文件内容发送给浏览器
	 			http响应格式消息+消息正文
 			----若是目录, 则组织应答消息, 响应正文部分将目录下的所有文件组织成文件列表的形式生成html文件(每个文件都是一个超连接), 然后发送给浏览器 
	 			http响应格式消息+html格式文件内容
	 }

三、完善Web服务器

  1. buf修复和完善:在 Web 服务器中,每个响应消息通常分为以下四个部分:状态行、响应头部、空行、和响应体。为了更好地管理和发送响应消息,可以将这些部分封装为多个函数。

  2. 遍历目录,添加目录访问,访问目录中文件:在 Web 服务器中,当处理目录请求时,需要遍历该目录下的所有内容(如文件和子目录),并将其信息以 HTML 格式返回给客户端。

  3. 添加默认访问路径。

  4. 优化buf:可以一次拼接10个buffer写入,提高效率,但出现bug:连接关闭后未通知。解决:回退一次,关闭原有连接,新建一个连接
    第二次点关闭。

  5. 中文文件访问: unicode 中文 看查看中文编码,1个汉字占3个字节。
    在这里插入图片描述

  6. 解决SIGPIPE信号问题:浏览器关闭读端后,而服务端仍继续往一个关闭读端的连接写数据时,会收到SIGPIPE信号。这个信号的默认处理动作是使进程终止。文件还没发完就关闭,就会死掉。解决:忽略或阻塞。

  7. 日志相关函数:可以自定义日志输出函数系列,可以按普通信息及告警信息分类输出程序日志 。

  8. 实现bufferevent版本的web服务器。

  9. char型转换为数值形式。
    在这里插入图片描述

  10. 完整流程图。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述


总结

笔记来源:黑马程序员C语言课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值