【Linux】libevent、vcpkg(linux环境变量)

  • Post author:
  • Post category:linux




0 Linux环境变量


Linux环境变量配置方法一:


export PATH

export PATH=/home/uusama/mysql/bin:$PATH

# 或者把PATH放在前面
export PATH=$PATH:/home/uusama/mysql/bin
  • 生效时间:立即生效
  • 生效期限:当前终端有效,窗口关闭后无效
  • 生效范围:仅对当前用户有效
  • 配置的环境变量中不要忘了加上原来的配置,即

    $PATH

    部分,避免覆盖原来配置


Linux环境变量配置方法二:


vim ~/.bashrc

vim ~/.bashrc

# 在最后一行加上
export PATH=$PATH:/home/uusama/mysql/bin
  • 生效时间:使用相同的用户打开新的终端时生效,或者手动

    source ~/.bashrc

    生效
  • 生效期限:永久有效
  • 生效范围:仅对当前用户有效
  • 如果有后续的环境变量加载文件覆盖了PATH定义,则可能不生效


Linux环境变量配置方法三:


vim ~/.bash_profile

  • 生效时间:使用相同的用户打开新的终端时生效,或者手动

    source ~/.bash_profile

    生效
  • 生效期限:永久有效
  • 生效范围:仅对当前用户有效
  • 如果没有

    .bash_profile

    文件,则可以编辑

    .profile

    文件或者新建一个


Linux环境变量配置方法四:


vim /etc/bashrc

该方法是修改系统配置,需要管理员权限(如root)或者对该文件的写入权限

# 如果/etc/bashrc文件不可编辑,需要修改为可编辑
chmod -v u+w /etc/bashrc
vim /etc/bashrc

# 在最后一行加上
export PATH=$PATH:/home/uusama/mysql/bin
  • 生效时间:新开终端生效,或者手动

    source /etc/bashrc

    生效
  • 生效期限:永久有效
  • 生效范围:对所有用户有效


Linux环境变量配置方法五:


vim /etc/profile

# 如果/etc/profile文件不可编辑,需要修改为可编辑
chmod -v u+w /etc/profile
vim /etc/profile

# 在最后一行加上
export PATH=$PATH:/home/uusama/mysql/bin
  • 生效时间:新开终端生效,或者手动

    source /etc/profile

    生效
  • 生效期限:永久有效
  • 生效范围:对所有用户有效


Linux环境变量配置方法六:


vim /etc/environment

# 如果/etc/bashrc文件不可编辑,需要修改为可编辑
chmod -v u+w /etc/environment

vim /etc/profile

# 在最后一行加上
export PATH=$PATH:/home/uusama/mysql/bin
  • 生效时间:新开终端生效,或者手动

    source /etc/environment

    生效
  • 生效期限:永久有效
  • 生效范围:对所有用户有效



1 libevent 简介


简介


Libevent是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:

事件驱动( event-driven),高性能轻量级,专注于网络,不如ACE那么臃肿庞大:源代码相当精炼、易读:跨平台,支持 Windows、 Linux、BSD和 Mac Os:支持多种IO多路复用技术, epoll, poll、 dev/poll, select和 kqueue等。支持I/O,定时器和信号等事件、注册事件优先级。

Chromium、Memcached、NTP、HTTPSQS等著名的开源程序都使用libevent库。


libevent核心


Reactor(反应堆)模式是 libevent的核心框架, libevent以事件驱动,自动触发回调功能。



2 libevent (vcpkg)安装

git https://github.com/libevent/libevent.git



2.1 安装vcpkg

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install


提示下面的信息

(后面放在CMakeLists中,CMAKE_TOOLCHAIN_FILE用于获取到安装库的路径)

-CMAKE_TOOLCHAIN_FILE=/home/xhh/Downloads/app/vcpkg/scripts/buildsystems/vcpkg.cmake



2.2 安装libevent

./vcpkg install libevent


提示下面的信息

(后面放在CMakeLists中用)

find_package(Libevent CONFIG REQUIRED)
target_link_libraries(main PRIVATE libevent::core libevent::extra libevent::pthreads)



2.3 测试libevent

#include <event.h>
#include <stdio.h>

int main(){
    const char **methods = event_get_supported_methods();
    for(int i = 0; methods[i] != nullptr; ++i){
        printf("%s is support .\n", methods[i]);
    }
    struct event_base *base = event_base_new();
    printf("curr os supprot [%s] \n", event_base_get_method(base));
    return 0;
}



2.4 编写CMakeLists.txt


V1版本

cmake_minimum_required(VERSION 2.8)

# [2.1] vcpkg.camke---> setting vcpkg path [NICE!]
set(CMAKE_TOOLCHAIN_FILE /home/xhh/Downloads/app/vcpkg/scripts/buildsystems/vcpkg.cmake)

project(TESTPOOL)
add_compile_options(-std=c++11)       # C++11
set(CMAKE_BUILD_TYPE "DEBUG")         # debug mode

# [2.2] 找event包
find_package(Libevent CONFIG REQUIRED)

add_executable(main main.cpp)
# [2.2] 找event的库   -levent
target_link_libraries(main PRIVATE libevent::core libevent::extra libevent::pthreads)   


V2版本-利用${}

cmake_minimum_required(VERSION 2.8)
# setting vcpkg path [NICE!]
set(CMAKE_TOOLCHAIN_FILE "/home/xhh/Downloads/app/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")

project(TESTPOOL)
add_compile_options(-std=c++11)       # C++11
set(CMAKE_BUILD_TYPE "DEBUG")         # debug mode

find_package(Libevent CONFIG REQUIRED)

add_executable(${PROJECT_NAME} main.cpp)
# -levent
target_link_libraries(${PROJECT_NAME} PRIVATE libevent::core libevent::extra libevent::pthreads)   


V3版本-把vcpkg.cmake添加至系统变量

  • 添加系统变量
vim ~/.bashrc
export VCPKG="/home/xhh/Downloads/app/vcpkg/scripts/buildsystems/vcpkg.cmake"
vim ~/.bashrc
export VCPKGHOME="/home/xhh/Downloads/app/vcpkg"
  • 使用系统变量地址
# 修改这一行 "$ENV{VCPKG}" 获取环境变量中设置的VCPKG
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG}" CACHE STRING "Vcpkg toolchain file")



2.5 Clion中使用vcpkg(PASS)




2.6 VsCode中使用vcpkg


【Linux】Vscode & CMake实战开发:用于配置Vscode


CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
# setting vcpkg path [NICE!]
# set(CMAKE_TOOLCHAIN_FILE "/home/xhh/Downloads/app/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
// set(CMAKE_TOOLCHAIN_FILE $ENV{VCPKG} CACHE STRING "Vcpkg toolchain file")
set(CMAKE_TOOLCHAIN_FILE $ENV{VCPKGHOME}/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")


project(MAINCXX)
add_compile_options(-std=c++11)       # C++11
set(CMAKE_BUILD_TYPE "DEBUG")           # debug mode

find_package(Libevent CONFIG REQUIRED)
add_executable(${PROJECT_NAME} main.cpp)
# -levent
target_link_libraries(${PROJECT_NAME} PRIVATE libevent::core libevent::extra libevent::pthreads)   


c_cpp_properties.json

用于包含vcpkg的第三方头文件include(声明,no定义)

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                // [xhh] add vcpkg all lib-include
                // "/home/xhh/Downloads/app/vcpkg/installed/x64-linux/include",
                "${VCPKGHOME}/installed/x64-linux/include"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}


settings.json

包含编译链工具 CMAKE_TOOLCHAIN_FILE

{
    "files.associations": {
        "iostream": "cpp"
    },
    // [xhh] add cmake 
    "cmake.configureSettings": {
        // "CMAKE_TOOLCHAIN_FILE": "/home/xhh/Downloads/app/vcpkg/scripts/buildsystems/vcpkg.cmake",
        "CMAKE_TOOLCHAIN_FILE": "${VCPKGHOME}/scripts/buildsystems/vcpkg.cmake"
    }
}



3 libevent 使用

在这里插入图片描述



3.1 基本API

对于不同系统而言, event_ base就是调用不同的多路IO接口去判断事件是否已经被激活。

核心调用的就是epoll,同时支持poll和 select。

  • 创建与销毁(父根)
// [1] create
struct event_base *event_base_new(void); 
// [2] free
void event_base_free(struct event_base *base);
  • 等待事件
// 循环监听,封装epoll_wait
int event_base_dispatch(struct event_base *base);
  • 退出监听
// 退出循环监听
int event_base_loopexit(struct event_base *base, const struct timeval *timeout);
int event_base_loopbreak(struct event_base *base);
  • 初始化节点
// 根节点 上树文件描述符fd  监听事件  回调函数cb  回调函数参数
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *arg);


// 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

// cb
typedef void (*event_callback_fn)(evutil_socket_t fd, short, void *arg);
  • 添加节点
int event_add(struct event *ev, const struct timeval *timeout);
  • 删除节点
int event_del(struct event *ev);
  • 释放节点
void event_free(struct event *ev);



3.2 libevent(event事件)开发TCP服务器

#include <iostream>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include "event.h"

using namespace std;

void cfdcb(evutil_socket_t cfd, short cfd_event, void *arg){
    // [-> 1] read msg
    // char buf[128] = "";
    char buf[4] = "";
    int len = read(cfd, buf, sizeof(buf));
    if(len <= 0){
        // error
        // [->] del node and free node
        
        // [-> 1] get event_base
        event_base *base = static_cast<event_base *>(arg);
        // [-> 2] get curr ev
        event *currev = event_base_get_running_event(base);
        // [-> 3] del and free currev
        event_del(currev);
        event_free(currev);
        // [-> 4] close fd
        close(cfd);
        printf("[close connect] fd:%d  SUCCESS.\n", cfd);
    }else{
        printf("server rev msg: %s\n", buf);
        write(cfd, buf, strlen(buf));
    }
}

// typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
void lfdcb(evutil_socket_t lfd, short lfd_event, void *arg){
    sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    char ip[24] = {0};
    // accept
    int cfd = accept(lfd, (sockaddr *)&cliaddr, &len);
    printf("[accept] client ip:%s port:%u \n", \
            inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), \
            ntohs(cliaddr.sin_port));
    
    // [-> 1] init cfd node
    event_base *base = static_cast<event_base *>(arg);
    // event *ev = event_new(base, cfd, EV_READ | EV_PERSIST, cfdcb, NULL);
    event *ev = event_new(base, cfd, EV_READ | EV_PERSIST, cfdcb, static_cast<void *>(base));

    // [-> 2] add cfd to tree
    event_add(ev, NULL);
}

void m_libevent_Server(){

    // 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket error");
        exit(1);
    }

    // 绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8888);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 本地多有的IP
    // inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
    
    // 设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定端口
    int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1){
        perror("bind error");
        exit(1);
    }
    // 监听
    ret = listen(lfd, 128);
    if(ret == -1){
        perror("listen error");
        exit(1);
    }

    // [1] create
    event_base *base = event_base_new();

    // [2] init root_ev
    event *ev = event_new(base, lfd, EV_READ | EV_PERSIST, lfdcb, static_cast<void *>(base));

    // [3] add root_ev
    event_add(ev, NULL);

    // [4] loop for listen
    event_base_dispatch(base); // this is blocked

    // [x] free
    close(lfd);
    event_base_free(base);
}


int main(){

    m_libevent_Server();

    return 0;
}



3.3 libevent(bufferevent事件)相关API

  • 一个文件描述符
  • 两个缓冲区
  • 三个回调函数

在这里插入图片描述


  • 创建节点
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);

enum bufferevent_options {
	BEV_OPT_CLOSE_ON_FREE = (1<<0),		// use1 释放bufferevent自动关闭底层接口(自动关闭fd)
	BEV_OPT_THREADSAFE = (1<<1),		// use2 使用bufferevent线程安全
	BEV_OPT_DEFER_CALLBACKS = (1<<2),
	BEV_OPT_UNLOCK_CALLBACKS = (1<<3)
};
  • 设置节点回调函数(自动上树)
void bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg);

// readcb and writecb
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
// enent cb  异常回调
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);

// what such as:
#define BEV_EVENT_READING	0x01	/**< error encountered while reading */
#define BEV_EVENT_WRITING	0x02	/**< error encountered while writing */
#define BEV_EVENT_EOF		0x10	/**< eof file reached */
#define BEV_EVENT_ERROR		0x20	/**< unrecoverable error encountered */
#define BEV_EVENT_TIMEOUT	0x40	/**< user-specified timeout reached */
#define BEV_EVENT_CONNECTED	0x80	/**< connect operation finished. */
  • 事件使能(事件是否生效)
int bufferevent_enable(struct bufferevent *bufev, short event);
int bufferevent_disenable(struct bufferevent *bufev, short event);

// event
EV_READ
EV_WRITE
  • 发送和接收数据
// 发送
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
// 接收
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
  • 链接帧听器(合并了:创建套接字-绑定-监听-提取)


#include <event2/listener.h>

// 根节点base
// 回调cb
// 传入回调的参数 ptr(把base传入到cb中)
// flags    
// backlgog=-1自动调整数量   cliaddr   sizeof(cliaddr)
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
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING	(1u<<0)
#define LEV_OPT_CLOSE_ON_FREE		(1u<<1)			// use 关闭自动释放close(fd)
#define LEV_OPT_CLOSE_ON_EXEC		(1u<<2)
#define LEV_OPT_REUSEABLE		(1u<<3)				// use 端口复用
#define LEV_OPT_THREADSAFE		(1u<<4)
#define LEV_OPT_DISABLED		(1u<<5)
#define LEV_OPT_DEFERRED_ACCEPT		(1u<<6)
#define LEV_OPT_REUSEABLE_PORT		(1u<<7)
#define LEV_OPT_BIND_IPV6ONLY		(1u<<8)


// 回调函数cb
// evl链接帧听器地址
// fd:cfd 客户端
// 客户端地址cliaddr  
// 传入回调的参数 ptr
typedef void (*evconnlistener_cb)(struct evconnlistener *evl, evutil_socket_t fd, struct sockaddr *cliaddr, int socklen, void *ptr);
  • 释放节点
void bufferevent_free(struct bufferevent *bufev);
  • 释放侦听器
void evconnlistener_free(struct evconnlistener *lev);



3.4 libevent(bufferevent事件)开发TCP服务器

#include <iostream>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

#include <signal.h>

#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>

using namespace std;

static char c_upper(char c){
    return (c >= 'a' && c <= 'z') ? (c - ('a' - 'A')) : c;
}

static void conn_readcb(struct bufferevent *bev, void *user_data){
	char buf[4] = {0};
    // [1] read
    // size_t n = bufferevent_read(bev, buf, sizeof(buf));
    // if(n > 0){
    //     printf("recv msg: %s\n", buf);

    //     // to upper and write
    //     for(size_t i = 0; i < n; i++){
    //         buf[i] = c_upper(buf[i]);
    //     }
        
    //     // [2] write
    //     bufferevent_write(bev, buf, strlen(buf));
    // }

    // [1] loop for read
    size_t n = 0;
    while(n = bufferevent_read(bev, buf, sizeof(buf))){
        if(n > 0){
            printf("recv msg: %s\n", buf);
            // to upper and write
            for(size_t i = 0; i < n; i++){
                buf[i] = c_upper(buf[i]);
            }
            
            // [2] write
            printf("send buf: %s  strlen(buf)=%d  n=%d\n", buf, strlen(buf), n);  // "xhhc"  strlen(buf)=5  n=4
            bufferevent_write(bev, buf, n); // strlen(n)
        }
    }
}

static void conn_writecb(struct bufferevent *bev, void *user_data){
    // 获取爰冲区类型
	// struct evbuffer *output = bufferevent_get_output(bev);
	// if (evbuffer_get_length(output) == 0) {
	// 	printf("flushed answer\n");
	// 	bufferevent_free(bev);
	// }
    printf("xhh go to [conn_writecb]\n");
}

static void conn_eventcb(struct bufferevent *bev, short events, void *user_data){
	if (events & BEV_EVENT_EOF) {
		printf("Connection closed.\n");
	} else if (events & BEV_EVENT_ERROR) {
		printf("Got an error on the connection: %s\n",
		    strerror(errno));/*XXX win32*/
	}
	/* None of the other events can happen here, since we haven't enabled
	 * timeouts */
    // [1] some event (error or close) -> free bev
	bufferevent_free(bev);
}

static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *sa, int socklen, void *user_data){
	event_base *base = static_cast<event_base *>(user_data);

	bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	if (!bev) {
		fprintf(stderr, "Error constructing bufferevent!");
		event_base_loopbreak(base);
		return;
	}
	bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL);
	bufferevent_enable(bev, EV_WRITE | EV_READ);

	// bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

static void signal_cb(evutil_socket_t sig, short events, void *user_data){
	event_base *base = static_cast<event_base *>(user_data);
	timeval delay = { 2, 0 }; // 2 seconds

	printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
    // after 2s exit base_root 退出循环监听
	event_base_loopexit(base, &delay);
}

int m_buffer_event_Server(){
    // [1] create base root
	event_base *base = event_base_new();
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

	sockaddr_in sin = {0};
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8888);
    sin.sin_addr.s_addr = INADDR_ANY;

    // [2] crete_fd -> bind -> listen -> accept
	evconnlistener *listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
	    LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
	    (struct sockaddr*)&sin,
	    sizeof(sin));

	if (!listener) {
		fprintf(stderr, "Could not create a listener!\n");
		return 1;
	}

    // [3] ctrl+c SIGINT add this event to base root
	struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);

	if (!signal_event || event_add(signal_event, NULL)<0) {
		fprintf(stderr, "Could not create/add a signal event!\n");
		return 1;
	}

    // [4] listen this is blocked
	event_base_dispatch(base);

    // [5] free resource  listen -- event -- base_root
	evconnlistener_free(listener);
	event_free(signal_event);
	event_base_free(base);
	return 0;
}

int main(){

    // m_libevent_Server();
    m_buffer_event_Server();
    return 0;
}



4 webserver(接收HTTP请求)



4.1 简介

默认端口:

80



www.baidu.com

====

220.181.112.244:80/index.html

在这里插入图片描述



4.2 抓包工具 Wireshark(TCP三/四)

ip.addr == 192.168.137.1 and tcp.port == 4508


mac报头(6+6+2=14byte)

数据都是大端

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



IP报头(4*5=20byte)

在这里插入图片描述



TCP报头(20byte)

在这里插入图片描述




4.3 HTTP协议

在这里插入图片描述

  • 请求
// 明文  [请求  文件  版本  \r\n]
GET /demo.html HTTP/1.1\r\n
// 不显示
POST /demo.html HTTP/1.1\r\n
  • 应答请求

    在这里插入图片描述
// 状态行  [版本  状态码  \r\n]
HTTP/1.1 200 OK\r\n

// 消息报头
// 文件类型(必填)  编码格式
Content-Type:text/html; charset=utf-8\r\n

// 文件长度(可选项)
Content-length:950\r\n

在这里插入图片描述



4.4 HTTP服务器解析头部

#include <iostream>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

#include <signal.h>

#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>

using namespace std;

void send_header(int cfd, int code, char *info, char *flieType){
    // send stat_row     HTTP/1.1 200 OK\r\n
    char buf[1024] = {0};
    int len = sprintf(buf, "HTTP/1.1 %d %s\r\n", code, info);
    write(cfd, buf, len); // don`t use strlen(buf);
    // msg_head     Content-Type:text/html; charset=utf-8
    len = sprintf(buf, "Content-Type:%s\r\n", \
                        flieType);
    write(cfd, buf, len);
    // sent null row
    write(cfd, "\r\n", len);
}

void send_file(char *fileName, int epfd, epoll_event *ev){
    int fd = open(fileName, O_RDONLY);
    if(fd < 0){
        perror("open");
        return;
    }
    char buf[1024] = {0};
    int len = 0;
    while((len = read(fd, buf, sizeof(buf)) > 0)){
        write(ev->data.fd, buf, len);
    }
    close(fd);
    // del ev
    close(ev->data.fd);
    epoll_ctl(epfd, EPOLL_CTL_DEL, ev->data.fd, ev);
}

void read_client_request(int epfd, epoll_event *ev){
    // readline 1
    char buf[32] = {0}; // read head
    char tmp[1024] = {0}; // other

    int headLen = read(ev->data.fd, buf, sizeof(buf));
    if(headLen <= 0){
        // exit
        epoll_ctl(epfd, EPOLL_CTL_DEL, ev->data.fd, ev);
        close(ev->data.fd);
        printf("curr connect close..!\n");
        return;
    }
    // web:192.168.137.234:8888/index.html
    // head: [GET /index.html HTTP/1.1\r\nHost:.....]
    // parse to get /index.html
    printf("head: [%s]\n", buf);

    int len = 0;
    while((len = read(ev->data.fd, tmp, sizeof(tmp))) > 0);
    
    printf("read head and all other OKKK\n");

    // GET /index.html HTTP/1.1\r\nHost:.....
    // [1] parser buf to get /index.html
    char method[256] = {0};
    char content[256] = {0};
    char protocol[256] = {0};
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", method, content, protocol);
    // sscanf get method:GET  content:/index.html  protocol:HTTP/1.1
    printf("sscanf get method:%s  content:%s  protocol:%s\n", \
            method, content, protocol);

    // estimate method is GET ?
    // strcmp   strcasecmp
    // 若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数
    if(strcasecmp(method, "get") == 0){
        char *fileName = content+1; // index.html

        if(*fileName == 0){
            // if not file find curr dir
            fileName = "./";
        }
        // estimate file is exist ?
        struct stat s;
        if(stat(fileName, &s) < 0){
            printf("fileName is not exist.\n");
            // send[1] stat msg_head  \r\n
            send_header(ev->data.fd, 404, "NOT FOUND", "------");
        }else{
            // is a file
            if(S_ISREG(s.st_mode)){
                printf("fileName is a file\n");
                // send[1] stat msg_head  \r\n
                send_header(ev->data.fd, 200, "OK", "Content-Type:text/html; charset=utf-8");
                // send[2] file
                send_file(fileName, epfd, ev);
            }
            // is a dir
            if(S_ISDIR(s.st_mode)){
                printf("fileName is a dir.\n");
            }
        }
    }
}

int epoll_http_web(){
    // change work dir
    char pwd_path[256];
    char *path = getenv("PWD");
    strcpy(pwd_path, path);
    strcat(pwd_path, "/web-http");
    chdir(pwd_path);


    // 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket error");
        exit(1);
    }

    // 绑定
    sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8888);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 本地多有的IP
    
    // 设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定端口
    int ret = bind(lfd, (sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }

    // 监听
    ret = listen(lfd, 128);
    if(ret == -1)
    {
        perror("listen error");
        exit(1);
    }

    // 现在只有监听的文件描述符
    // 所有的文件描述符对应读写缓冲区状态都是委托内核进行检测的epoll
    // 创建一个epoll模型
    int epfd = epoll_create(100);
    if(epfd == -1)
    {
        perror("epoll_create");
        exit(0);
    }

    // 往epoll实例中添加需要检测的节点, 现在只有监听的文件描述符
    epoll_event ev;
    ev.events = EPOLLIN;    // 检测lfd读读缓冲区是否有数据
    ev.data.fd = lfd;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if(ret == -1)
    {
        perror("epoll_ctl");
        exit(0);
    }

    epoll_event evs[1024];
    int size = sizeof(evs) / sizeof(evs[0]);
    // 持续检测
    while(1)
    {
        // 调用一次, 检测一次
        int num = epoll_wait(epfd, evs, size, -1);

        if(num < 0){
            perror("epoll_wait");
            break;
        }

        for(int i=0; i<num; ++i)
        {
            // 判断这个文件描述符是不是用于监听的
            if(evs[i].data.fd == lfd && evs[i].events & EPOLLIN)
            {
                sockaddr_in cliaddr;
                socklen_t cillen = sizeof(cliaddr);

                // 建立新的连接
                // int cfd = accept(evs[i].data.fd, static_cast<sockaddr *>(&cliaddr), &cillen);
                int cfd = accept(evs[i].data.fd, (sockaddr *)(&cliaddr), &cillen);
                // show cli info 
                char ip[24] = {0};
                printf("Client ip:%s   Port:%d\n", \
                        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), \
                        ntohs(cliaddr.sin_port));


                // set-NONBLOCK
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);

                // 新得到的文件描述符添加到epoll模型中, 下一轮循环的时候就可以被检测了
                ev.events = EPOLLIN;    // 读缓冲区是否有数据
                ev.data.fd = cfd;
                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
                if(ret == -1){
                    perror("epoll_ctl-accept");
                    exit(0);
                }
            }else if(evs[i].events & EPOLLIN){
                // cfd is read
                read_client_request(epfd, &evs[i]);
            }
        }
    }
}

int main(){

    // m_libevent_Server();
    // m_buffer_event_Server();
    epoll_http_web();
    return 0;
}


启动HTTP服务—浏览器访问—抓包工具

// 浏览器访问
web: 192.168.137.234:8888/index.html

[xhh@xhhCentOS build]$ ./LibEvent
Client ip:192.168.137.1   Port:14438
Client ip:192.168.137.1   Port:14888
head: [GET /index.html HTTP/1.1
Host:  ����]
read OKKK
curr connect close..!
curr connect close..!

在这里插入图片描述



参考


[1] Linux环境变量配置全攻略


[2] CMakeLists常用变量及规则查询笔记


[3] vcpkg国内镜像使用方法


[4] CentOS8完美升级gcc版本方法


[5]【Linux】Vscode & CMake实战开发:用于配置Vscode


[6] Linux高并发服务器开发视频



版权声明:本文为weixin_45292794原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。