文章目录
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..!
参考
[5]【Linux】Vscode & CMake实战开发:用于配置Vscode