一、配置
源码包安装:参考READIE、readme
./configure 检查安装环境生成makefile
make 生成.o和可执行文件
sudomake install 将必要的资源置系统指定目录。
进入sample目录,运行demo验证库安装使用情况。
编译使用库的.c时,需要加-levent选项。
gcc hello-world.c -o hello -levent
二、libevent框架
libevent框架:
1.创建event_base (乐高底座)struct event_base *event_base_new(void);struct event_base *base = event_base_new():2. 创建事件evnet常规事件event -->event_new():bufferevent --> bufferevent_socket_new();3.将事件添加到base上int event_add(struct event *ev, const struct timeval *tv);4.循环监听事件满足int event_base_dispatch(struct event_base *base);event_base_dispatch(base);5.释放event_baseevent_base_free(base):
三、创建事件对象
struct event *ev;
struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event callback_fn cb, void *arg);base: event_base_new()返回值。fd:绑定到event上的文件描述符what:对应的事件EV_READ 一次读事件EV_WRTIE 一次写事件EV_PERSIST 持续触发。结合event_base_dispatch函数使用,生效。cb:一旦事件满足监听条件,回调的函数。typedef void (*event _callback_fn)(evutil_socket_t fd, short, void*)arg:回调的函数的参数。返回值:成功创建的event
四、操作事件对象
添加事件到event_base:
int event_add(struct event *ev, const struct timeval *tv):ev:event_new()的返回值。tv:NULL从event_base上摘下事件:
int event_del(struct event *ev);ev:event_new()的返回值。销毀事件:
int event_free(struct event *ev);ev:event_new()的返回值。
五、用事件实现fifo读写
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#include<event2/event.h>//对操作处理函数
void read_cb(evutil_socket_t fd, short what, void* arg) {//读管道char buf[1024] = { 0 };int len = read(fd, buf, sizeof(buf));printf("read event: %s\n", what & EV_READ ? "Yes" : "No");printf("data len = %d, buf = %s\n", len, buf);sleep(1);
}int main(int argc, const char* argv[]) {unlink("myfifo");//创建有名管道mkfifo("myfifo", 0664);// open fileint fd = open("myfifo", O_RDONLY | O_NONBLOCK);if (fd == -1) {perror("open error");exit(1);}//创建个event_basestruct event_base* base = NULL;base = event_base_new();//创建事件struct event* ev = NULL;ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);//添加事件event_add(ev, NULL);//事件循环event_base_dispatch(base); // while (1) { epoll(); }//释放资源event_free(ev);event_base_free(base);close(fd);return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#include<event2/event.h>//对操作处理函数
void write_cb(evutil_socket_t fd, short what, void* arg) {//write管道char buf[1024] = { 0 };static int num = 0;sprintf(buf, "hello,world-%d\n", num++);write(fd, buf, strlen(buf) + 1);sleep(1);
}//写管道
int main(int argc, const char* argv[]) {//open fileint fd = open("myfifo", O_WRONLY | O_NONBLOCK);if (fd == -1) {perror("open error");exit(1);}struct event_base* base = NULL;base = event_base_new();//创建事件struct event* ev = NULL;//检测的写缓冲区是否有空间写//ev = event_new(base, fd, EV_WRITE, write_cb, NULL); //不能循环写入了ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL);//添加事件event_add(ev, NULL);//事件循环event_base_dispatch(base);//释放资源event_free(ev);event_base_free(base);close(fd);return 0;
}
六、未决和非未决

七、bufferevent

八、创建、销毁bufferevent
创建:
struct bufferevent *ev;
struct bufferevent *bufferevent_socket_new(struct event_base *base,
evutil_socket_t fd, enum bufferevent_options options);base: event_basefd:封装到bufferevent内的fdoptions: BEV_OPT_CLOSE_ON_FREE返回:成功创建的bufferevent事件对象。销毁:
void bufferevent_socket_free(struct bufferevent *ev);
九、bufferevent设置回调
bufferevent_socket_new(fd); bufferevent_setcb(callback)
void bufferevent_setcb(struct bufferevent* bufev, bufferevent_data_cb readcb,bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);参数:bufev:bufferevent_socket_new()返回值readcb:设置bufferevent读缓冲,对应回调read_cb{ bufferevent_read(); }writecb:设置bufferevent写缓冲,对应回调write_cb{}--给调用者,发送写成功通知。传NULLeventcb:设置事件回调。传NULLtypedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);void event_cb(struct bufferevent *bev, short events, void *ctx){}events: BEV_EVENT_CONNECTEDcbarg:上述回调函数使用的参数。read回调函数类型:typedef void(*bufferevent_data_cb)(struct bufferevent *bev, void* ctx);void read_cb(struct bufferevent *bev, void *cbarg){bufferevent_read();}bufferevent_readO函数的原型:size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize);write回调函数类型:int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
十、开启缓冲区
void bufferevent_enable(struct bufferevent *bufev,short events);events: EV_READ、EV_WRITE、EV_READ|EV_WRITE默认、write缓冲是enable、read缓冲是disablebufferevent_enable(evev, EV_READ); -—开启读缓冲。
十一、客户端服务器连接和监听
客户端:
socket(); connect();
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);bev:bufferevent 事件对象(封装了fd)address、len:等同于connect() 参2/3服务器:
socket(); bind(); listen(); accept();
struct evconnlistener * listener
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,evconnlistener_cb cb,void *ptr,unsigned flags,int backlog,const structsockaddr *sa,int socklen
);base: event_basecb:回调函数。一旦被回调,说明在其内部与客户端完成,数据读写操作,进行通信。ptr:回调函数的参数flags: LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLEbacklog:listen()的2参。-1表示最大值sa:服务器自己的地址结构体socklen:服务器自己的地址结构体大小。返回值:成功创建的监听器。
十二、libevent实现TCP通信
//客户端
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<event2/event.h>
#include<event2/listener.h>
#include<event2/bufferevent.h>//读缓冲区回调
void read_cb(struct bufferevent* bev, void* arg) {char buf[1024] = { 0 };bufferevent_read(bev, buf, sizeof(buf));printf("client say: &s\n", buf);char* p = "我是服务器,已经成功收到你发送的数据!";//写数据给客户端bufferevent_write(bev, p, strlen(p) + 1);sleep(1);
}//写缓冲区回调
void write_cb(struct bufferevent* bev, void* arg) {printf("I'm 服务器,成功写数据给客户端,写缓冲区回调函数被回调...\n");
}//事件回调
void event_cb(struct bufferevent* bev, short events, void* arg) {if (events & BEV_EVENT_EOF) {printf("connection closed\n");}else if (events & BEV_EVENT_ERROR) {printf("some other error\n");}bufferevent_free(bev);printf("buffevent资源已经被释放...\n");
}//监听回调
void cb_listener(struct evconnlistener* listener, evutil_socket_t fd,struct sockaddr* addr, int len, void* ptr) {printf("connect new client\n");struct event_base* base = (struct event_base*)ptr;//添加新事件struct bufferevent* bev;bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);//给bufferevent缓冲区设置回调bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);//启用bufferevent的读缓冲。默认是disable的bufferevent_enable(bev, EV_READ);
}int main(int argc, const char* argv[]) {// init serverstruct sockaddr_in serv;memset(&serv, 0, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(9876);serv.sin_addr.s_addr = htonl(INADDR_ANY);//创建event_basestruct event_base* base;base = event_base_new();//创建套接字//绑定//接收连接请求struct evconnlistener* listener; //监听器listener = evconnlistener_new_bind(base, cb_listener, base,LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,36, (struct sockaddr*)&serv, sizeof(serv));//启动循环监听event_baseispatch(base);evconnlistener_free(listener);event_base_free(base);return 0;
}
//客户端
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<event2/bufferevent.h>
#include<event2/event.h>
#include<arpa/inet.h>//读缓冲区回调
void read_cb(struct bufferevent* bev, void* arg) {char buf[1024] = { 0 };bufferevent_read(bev, buf, sizeof(buf));printf("fwq say: &s\n", buf);bufferevent_write(bev, buf, strlen(buf) + 1);sleep(1);
}//写缓冲区回调
void write_cb(struct bufferevent* bev, void* arg) {printf("--------客户端写回调函数,没啥用\n");
}//事件回调
void event_cb(struct bufferevent* bev, short events, void* arg) {if (events & BEV_EVENT_EOF) {printf("connection closed\n");}else if (events & BEV_EVENT_ERROR) {printf("some other error\n");}else if (events & BEV_EVENT_CONNECTED) {printf("已经连接服务器...\n");return;}bufferevent_free(bev);
}//客户端与用户交互,从终端读取数据写给服务器
void read_terminal(evutil_socket_t fd, short what, void* arg) {//读数据char buf[1024] = { 0 };int len = read(fd, buf, sizeof(buf));struct bufferevent* bev = (struct bufferevent*)arg;// 发送数据bufferevent_write(bev, buf, len + 1);
}int main(int argc, const char* argv[]) {struct event_base* base = NULL;base = event_base_new();int fd = socket(AF_INET, SOCK_STREAM, 0);//通信的fd放到bufferevent中struct bufferevent* bev = NULL;bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);// init server infostruct sockaddr_in serv;memset(&serv, 0, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(9876);inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);//连接服务器bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));//设置回调bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);//设置读回调生效bufferevent_enable(bev, EV_READ);//创建事件struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, read_terminal, bev);//添加事件event_add(ev, NULL);event_base_dispatch(base);event_free(ev);event_base_free(base);return 0;
}
十三、http协议
客户端请求消息(Request)(GET:只拿)(POST:带数据发给服务器)
浏览器-->发给-->服务器。主旨内容包含4部分:请求行:说明请求类型,要访问的资源,以及使用的http版本。请求头:说明服务器要使用的附加信息。空行:必须!,即使没有请求数据。(用来区分头和数据)请求数据:也叫主体,可以添加任意的其他数据。以下是浏览器发送给服务器的http协议头内容举例,注意:9行的空行(\r\n)也是协议头的一部分:

服务器响应消息(Response)
服务器查看是否有对应文件。
服务器-->发给-->浏览器。主旨内容包含4部分:状态行:包括http协议版本号,状态码,状态信息。消息报头:说明客户端要使用的一些附加信息。空行:必须!响应正文:服务器返回给客户端的文本信息。以下是经服务器按照http协议,写回给浏览器的内容举例,1~9行是协议头部分。
注意:9行\八n的空行不可忽略。

HTTP常用状态码:
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:1xx:指示信息-表示请求已接收,继续处理。2xx:成功-表示请求已被成功接收、理解、接受。3xx:重定向-要完成请求必须进行更进一步的操作。4xx:客户端错误-请求有语法错误或请求无法实现。5xx:服务器端错误-服务器未能实现合法的请求。常见状态码:200 OK 客户端请求成功。400 Bad Request 客户端请求有语法错误,不能被服务器所理解。401 Unauthorized 请求未经授权。403 Forbidden 服务器收到请求,但是拒绝提供服务。404 Not Found 请求资源不存在,eg:输入了错误的URL500 Internal Server Error 服务器发生不可预期的错误。503 Server Unavailable 服务器当前不能处理客户端的请求。
常见网络文件类型:普通文件:text/plain; charset=iso-8859-1*.html:text/html; charset=iso-8859-1*.jpg: image/jpeg*.gif: image/gif*.png: image/png*.wav: audio/wav*.avi: video/x-msvideo*.mov: video/quicktime*.mp3: audio/mpegcharset=iso-8859-1 西欧的编码,说明网站采用英文编码。charset-gb2312 说明网站采用的编码是简体中文 charset-utf-8 代表世界通用的语言编码;可以用到中文、韩文、日文等世界上charset=euc-kr 说明网站采用的编码是韩文;charset=big5 说明网站采用的编码是繁体中文;
十四、http协议实现服务器
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<fcntl.h>#define MAXSIZE 2048void send_error(int cfd, int status, char* title, char* text) {char buf[4096] = { 0 };sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", status, title);sprintf(buf + strlen(buf), "Content-Type:%s\r\n", "text/html");sprintf(buf + strlen(buf), "Content-Length:%d\r\n", -1);sprintf(buf + strlen(buf), "Connection: close\r\n");send(cfd, buf, strlen(buf), 0);send(cfd, "\r\n", 2, 0);memset(buf, 0, sizeof(buf));sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);sprintf(buf + strlen(buf), "<body bgcolor=\"#cc99cc\"><h4 align=\"center\">%d %s</h4>\n", status, title);sprintf(buf + strlen(buf), "%s\n", text);sprintf(buf + strlen(buf), "<hr>\n</body>\n</html>\n");send(cfd, buf, strlen(buf), 0);return;
}//获取一行\r\n结尾的数据
int get_line(int cfd, char* buf, int size) {int i = 0;char c = '\0';int n;while ((i < size - 1) && (c != '\n')) {n = recv(cfd, &c, 1, 0);if (n > 0) {if (c == '\r') {n = recv(cfd, &c, 1, MSG_PEEK);if ((n > 0) && (c == '\n')) {recv(cfd, &c, 1, 0);}else {c = '\n';}}buf[i] = c;i++;}else {c = '\n';}}buf[i] = '\0';if (n == -1) {n = i;}return i;
}int init_listen_fd(int port, int epfd) {//创建监听的套接字lfdint lfd = socket(AF_INET, SOCK_STREAM, 0);if (lfd == -1) {perror("socket error");exit(1);}//创建服务器地址结构ip+portstruct sockaddr_in srv_addr;bzero(&srv_addr, sizeof(srv_addr));srv_addr.sin_family = AF_INET;srv_addr.sin_port = htons(port);srv_addr.sin addr.s_addr = htonl(INADDR ANY);//端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//给lfd绑定地址结构int ret = bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));if (ret == -1) {perror("bind error");exit(1);}//设置监听上限ret = listen(lfd, 128);if (ret == -1) {perror("histen error");exit(1);}//lfd添加到epoll树上struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = lfd;ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);if (ret == -1) {perror("epoll_ctl add lfd error");exit(1);}return lfd;
}void do_accept(int lfd, int epfd) {struct sockaddr_in clt_addr;socklen_t clt_addr_len = sizeof(clt_addr);int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);if (cfd == -1) {perror("accept error");exit(1);}//打印客户端ip+portchar client_ip[64] = { 0 };printf("New Client IP: %s, Port: %d, cfd = %d\n",inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),ntohs(clt_addr.sin_port), cfd);//设置cfd非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);//将新节点cfd挂到epoll监听树上struct epoll_event ev;ev.data.fd = cfd;//边沿非阻塞模式ev.events = EPOLLIN | EPOLLET;int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);if (ret == -1) {perror("epoll_ctl add cfd error");exit(1);}
}void disconnect(int cfd, int epfd) {int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);if (ret != 0) {perror("epoll_ctl error");exit(1);}close(cfd);
}//参数:客户端的fd,错误号,错误描述,回发文件描述,文件长度
void send_respond(int cfd, int no, char* disp, char* type, int len) {char buf[1024] = { 0 };sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);sprintf(buf + strlen(buf), "%s\r\n", type);sprintf(buf + strlen(buf), "Content-Length:%d\r\n", len);send(cfd, buf, strlen(buf), 0);send(cfd, "\r\n", 2, 0);
}//发送服务器本地文件给浏览器
void send_file(int cfd, const char* file) {int n = 0, ret;char buf[1024] = { 0 };//打开的服务器本地文件,cfd是访问客户端的socketint fd = open(file, O_RDONLY);if (fd == -1) {send_error(cfd, 404, "Not Found", "No such file or direntry");exit(1);}//所有涉及到IO的操作,都需要详细的进行错误处理,否则可能会导致程序无法正常推进while ((n = read(fd, buf, sizeof(buf))) > 0) {ret = send(cfd, buf, n, 0);if (ret == -1) {printf("errno = %d\n", errno);if (errno == EAGAIN) {printf("----------EAGAIN");continue;}else if (errno == EINTR) {printf("-----------EINTR");continue;}else {perror("send error");exit(1);}}if (ret < 4096) {printf("--------send ret: %d\n", ret);}}close(fd);
}//通过文件名获取文件的类型
const char* get_file_type(const char* name) {char* dot;//自右向左查找'.'字符,如不存在返回NULdot = strrchr(name, '.');if (dot == NULL) {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, ".mp3") == 0) {return "audio/mpeg";}return "text/plain; charset=utf-8";
}//处理http请求
void http_request(int cfd, const char* file) {struct stat sbuf;//判断文件是否存在int ret = stat(file, &sbuf);if (ret != 0) {send_error(cfd, 404, "Not Found", "No such file or direntry");return;}//是普通文件if (S_ISREG(sbuf.st_mode)) {//回发http协议应答//send_respond(cfd, 200, "OK", " Content-Type: Text/plain; charset=iso-8859-1", sbuf.st_size);//send_respond(cfd, 200, "OK", " Content-Type: audio/mpeg", -1);send_respond(cfd, 200, "OK", get_file_type(file), sbuf.st_size);//回发给客户端请求数据内容send_file(cfd, file);}
}void do_read(int cfd, int epfd) {//读取一行http协议,拆分,获取: GET 文件名 协议号char line[1024] = { 0 };//读http请求协议首行,GET /hello.c HTTP/1.1int len = get_line(cfd, line, sizeof(line));if (len == 0) {printf("服务器,检查到客户端关闭...\n");disconnect(cfd, epfd);}else {char method[16], path[256], protocol[16];sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);//客户端浏览器输入127.0.0.1:9876/hello.cprintf("method=%s, path=%s, protocol=%s\n", method, path, protocol);//把剩下的东西读走,避免拥塞网络while (1) {char buf[1024] = { 0 };len = get_line(cfd, buf, sizeof(buf));if (len == '\n') {break;}else if (len == -1) {break;}}if (strncasecmp(method, "GET", 3) == 0) {char* file = path + 1; //取出客户端要访问的文件名//如果没有指定访问的资源,默认显示资源目录中的内容if (strcmp(path, "/") == 0) {file = "./";}http_request(cfd, file);}}
}void epoll_run(int port) {int i = 0;struct epoll_event all_events[MAXSIZE];//创建一个epoll监听树根int epfd = epoll_create(MAXSIZE);if (epfd = -1) {perror("epoll_create error");exit(1);}//创建lfd,并添加至监听树int lfd = init_listen_fd(port, epfd);while (1) {//监听节点对应事件int ret = epoll_wait(epfd, all_events, MAXSIZE, -1);if (ret == -1) {perror("epoll_wait error");exit(1);}for (i = 0; i < ret; ++i) {//只处理读事件,其他事件默认不处理struct epoll_event* pev = &all_events[i];//不是读事件if (!(pev->events & EPOLLIN)) {continue;}if (pev->data.fd == lfd) {//接受连接请求do_accept(lfd, epfd);}else {//读数据do_read(pev->data.fd, epfd);}}}
}int main(int argc, char* argv[]) {//命令行参数获取端口和server提供的目录//服务器启动: ./myhttpd 9876 /home/happygame/dir if (argc < 3) {printf("./a.out port path\n");}//获取用户输入的端口int port = atoi(argv[1]);//改变进程工作目录int ret = chdir(argv[2]);if (ret != 0) {perror("chdir error");exit(1);}//启动epoll监听epoll_run(port);return 0;
}
十五、telnet调试
可使用telnet命令,借助IP和port,模拟浏览器行为,在终端中对访问的服务器进行调试,
方便查看服务器回发给浏览器的http协议数据。使用方式:
命令行键入:telnet 127.0.0.19999回车,手动写入http请求协议头,
如:GET /hello.c http/1.1回车
此时,终端中可查看到服务器回发给浏览器的http应答协议及数据内容。可根据该信息进行调试。
