Linux网络学习与总结(6)

23 篇文章 0 订阅

libevent

网络的事件库
跨平台,支持多种多路io复用
核心: 以事件驱动(触发),回调

安装libevent
解压 : tar -xzvf libevent-2.0.22-stable.tar.gz -C ./install
执行configure ,检测环境生成makefile ./configure
编译 sudo make
安装 sudo make install
库的路径: /usr/local/lib
头文件目录: /usr/local/include'
编译时需要指定库名 -levent

libevent的使用
创建event_base根节点

struct event_base *event_base_new(void);
返回值:
	是event_base根节点地址

释放根节点

void event_base_free(struct event_base *);

循环监听

int event_base_dispatch(struct event_base *base);
相当于
while(1){epoll_wait()}
int event_base_loopexit(struct event_base *base, const struct timeval *tv); //等待固定时间之后退出
int event_base_loopbreak(struct event_base *base);//立即退出

事件驱动event
在这里插入图片描述
初始化上树节点

struct event *event_new(struct event_base *base, evutil_socket_t fd, 
						short events, event_callback_fn cb, void *arg);
参数:
	base: event_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 
	默认触发一次,所以需要 EV_READ EV_PERSIST 一起使用 
	cb: 回调函数
	typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
	arg: 传给回调函数的参数
返回值: 初始化好的节点的地址

节点上树

int event_add(struct event *ev, const struct timeval *timeout);
参数:
	ev: 上树节点的地址
	timeout: NULL  永久监听  固定时间 限时等待

下树

int event_del(struct event *ev);
ev: 下树节点的地址

释放节点

void event_free(struct event *ev);

使用libevent编写tcp服务器流程

  1. 创建套接字
  2. 绑定
  3. 监听
  4. 创建event_base根节点
  5. 初始化上树节点 lfd
  6. 上树
  7. 循环监听
  8. 收尾
#include <stdio.h>
#include "wrap.h"
#include <event.h>

void cfdcb(int cfd,short event,void *arg)
{
	char buf[1500] = "";
	
	int n = Read(cfd, buf, sizeof(buf));
	if(n <= 0)
	{
		perror("err or close\n");
		//event_del();//下树
	}
	else
	{
		printf("%s\n",buf);
		Write(cfd, buf, n);
	}
}
void lfdcb(int lfd, short event, void *arg)
{
	struct event_base *base = (struct event_base *)arg; 
	//提取新的cfd
	int cfd = Accept(lfd, NULL, NULL);
	//将cfd上树
	struct event *ev = event_new(base, cfd, EV_READ | EV_PERSIST, cfdcb, NULL);//初始化上树节点
	event_add(ev, NULL);
}
int main(int argc, char *argv[])
{
	//创建套接字
	//绑定
	int lfd = tcp4bind(8000,NULL);
	//监听
	Listen(lfd,128);
	//创建event_base根节点
	struct event_base * base = event_base_new();
	//初始化lfd上树节点
	struct event *ev = event_new(base, lfd, EV_READ | EV_PERSIST, lfdcb, base);
	//上树
	event_add(ev, NULL);
	//循环监听
	event_base_dispatch(base);//阻塞
	//收尾
	close(lfd);
	event_base_free(base);
	return 0;
}
#include <event.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

struct event *readev = NULL;

void readcb(evutil_socket_t fd, short event, void * arg)
{
	//处理读事件
	char buf[256] = { 0 };
	int ret = read(fd, buf, sizeof(buf));
	if (ret < 0){
		perror("read err");
		close(fd);
		event_del(readev);
	}
	else if (ret == 0){
		printf("client closed\n");
		close(fd);
		event_del(readev);
	}
	else{
		write(fd, buf, ret);//反射
	}
}
void conncb(evutil_socket_t fd, short event, void * arg)
{
	//处理连接
	struct event_base *base = (struct event_base*)arg;
	struct sockaddr_in client;
	socklen_t  len = sizeof(client);
	int cfd = accept(fd, (struct sockaddr*)&client, &len);
	if (cfd > 0){
		//连接成功
		//需要将新的文件描述符上树
		readev = event_new(base, cfd, EV_READ | EV_PERSIST, readcb, base);
		event_add(readev, NULL);
	}
}

int main()
{
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	serv.sin_port = htons(8888);
	serv.sin_family = AF_INET;

	bind(fd, (struct sockaddr*)&serv, sizeof(serv));

	listen(fd, 120);
	struct event_base *base = event_base_new();//创建根节点

	//struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
	struct event *connev = event_new(base, fd, EV_READ | EV_PERSIST, conncb, base);
	event_add(connev, NULL);//开始监听
	//循环
	event_base_dispatch(base);
	event_base_free(base);//释放
	event_free(connev);
	event_free(readev);
	return 0;
}

bufferevent事件

Bufferevent实际上也是一个event,只不过比普通的event高级一些,它的内部有两个缓冲区,以及一个文件描述符(网络套接字)。我们都知道一个网络套接字有读和写两个缓冲区,bufferevent同样也带有两个缓冲区,还有就是libevent事件驱动的核心回调函数。
在这里插入图片描述
有三个回调函数:

  1. 读回调 – 当bufferevent将底层读缓冲区的数据读到自身的读缓冲区时触发读事件回调
  2. 写回调 – 当bufferevent将自身写缓冲的数据写到底层写缓冲区的时候出发写事件回调
  3. 事件回调 – 当bufferevent绑定的socket连接,断开或者异常的时候触发事件回调

创建新的节点

struct bufferevent *bufferevent_socket_new(struct event_base *base, 
										evutil_socket_t fd, int options);

参数: 
    base  : event_base 根节点
    fd: 要初始化上树的文件描述符
    options : 
         BEV_OPT_CLOSE_ON_FREE   -- 释放bufferevent自动关闭底层接口   
         BEV_OPT_THREADSAFE      -- 使bufferevent能够在多线程下是安全的    
返回值:
   新建节点的地址

设置节点的回调

//释放bufferevent
void bufferevent_setcb(struct bufferevent *bufev,
    					bufferevent_data_cb readcb, bufferevent_data_cb writecb,
						bufferevent_event_cb eventcb, void *cbarg);

参数:
    bufev : 新建的节点的地址
    readcb : 读回调
    writecb : 写回调
    eventcb : 异常回调
    cbarg: 传给回调函数的参数
    
    typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);// 读写回调
    typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);//事件回调
	BEV_EVENT_EOF  对方关闭连接
	BEV_EVENT_ERROR  出错
	BEV_EVENT_TIMEOUT  超时
	BEV_EVENT_CONNECTED  建立连接成功

让事件使能
int bufferevent_enable(struct bufferevent *bufev, short event);//EV_READ  EV_WRITE
int bufferevent_disable(struct bufferevent *bufev, short event);//EV_READ  EV_WRITE

发送数据
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
bufferevent_write是将data的数据写到bufferevent的写缓冲区

接收数据
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
bufferevent_read 是将bufferevent的读缓冲区数据读到data中,同时将读到的数据从bufferevent的读缓冲清除。

连接侦听器
创建套接字->绑定->监听->提取

struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
    										   evconnlistener_cb cb, void *ptr,
    										   unsigned flags, int backlog,
											   const struct sockaddr *sa, int socklen);
参数: 
    base : base根节点
    cb : 提取cfd后调用的回调
    ptr : 传给回调的参数
    flags : 
    	LEV_OPT_LEAVE_SOCKETS_BLOCKING   文件描述符为阻塞的
        LEV_OPT_CLOSE_ON_FREE            关闭时自动释放
        LEV_OPT_REUSEABLE                端口复用
        LEV_OPT_THREADSAFE               分配锁,线程安全
	backlog : -1
	sa : 绑定的地址信息
	socklen : sa的大小
返回值: 
	连接侦听器的地址
回调:
typedef void (*evconnlistener_cb)(struct evconnlistener *evl, 
								  evutil_socket_t fd, struct sockaddr *cliaddr, 
								  int socklen, void *ptr);
参数:
    evl : 链接侦听器的地址
    fd : cfd
    cliaddr : 客户端的地址信息
    ptr : evconnlistener_new_bind传过来的参数

创建套接字 连接服务器

struct bufferevent *bufferevent_socket_new(struct event_base *base,-1, int options);
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *serv, int socklen);
	bev: 新建的节点
	serv: 服务器的地址信息
	socklen: serv长度

hello_world.c 看懂基本就掌握了

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef WIN32
#include <netinet/in.h>
#	ifdef _XOPEN_SOURCE_EXTENDED
#	include <arpa/inet.h>
#	endif
#include <sys/socket.h>
#endif

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

static const char MESSAGE[] = "Hello, World!\n";

static const int PORT = 9995;

static void conn_readcb(struct bufferevent *bev, void *user_data);
static void listener_cb(struct evconnlistener *, evutil_socket_t,
					    struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);

int
main(int argc, char **argv)
{
	struct event_base *base;
	struct evconnlistener *listener;
	struct event *signal_event;

	struct sockaddr_in sin;
#ifdef WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	base = event_base_new();//创建event_base根节点
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);

	//创建链接侦听器
	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;
	}
	//创建信触发的节点
	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;
	}

	event_base_dispatch(base);//循环监听

	evconnlistener_free(listener);//释放链接侦听器
	event_free(signal_event);//释放信号节点
	event_base_free(base);//释放event_base根节点

	printf("done\n");
	return 0;
}

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

	//将fd上树
	//新建一个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_disable(bev, EV_READ);//设置读事件非使能

	bufferevent_write(bev, MESSAGE, strlen(MESSAGE));//给cfd发送消息 helloworld
}
static void 
conn_readcb(struct bufferevent *bev, void *user_data)
{
	char buf[1500] = "";
	int n = bufferevent_read(bev,buf,sizeof(buf));
	printf("%s\n",buf);
	bufferevent_write(bev, buf, n);//给cfd发送消息 
}

static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
	struct evbuffer *output = bufferevent_get_output(bev);//获取缓冲区类型,地址
	if (evbuffer_get_length(output) == 0) {// 如果缓冲区为0
		;
	//	printf("flushed answer\n");
	//	bufferevent_free(bev);//释放节点 自动关闭
	}
}

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 */
	bufferevent_free(bev);
}

static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
	struct event_base *base = user_data;
	struct timeval delay = { 2, 0 };

	printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");

	event_base_loopexit(base, &delay);//退出循环监听
}

bufferevent实现tcp客户端

//bufferevent建立客户端的过程
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>

int tcp_connect_server(const char* server_ip, int port);
void cmd_msg_cb(int fd, short events, void* arg);
void server_msg_cb(struct bufferevent* bev, void* arg);
void event_cb(struct bufferevent *bev, short event, void *arg);
//./a.out  192.168.1.1  9995
int main(int argc, char** argv)
{
    if( argc < 3 )
    {
        //两个参数依次是服务器端的IP地址、端口号
        printf("please input 2 parameter\n");
        return -1;
    }
    //创建根节点
    struct event_base *base = event_base_new();
    //创建并且初始化buffer缓冲区
    struct bufferevent* bev = bufferevent_socket_new(base, -1,
                                                     BEV_OPT_CLOSE_ON_FREE);

    //监听终端输入事件,设置标准输入的监控,设置回调是 cmd_msg_cb 
    //STDIN_FILENO,终端输入触发EV_READ,调用回调函数cmd_msg_cb,读取终端数据,发送给服务器
    struct event* ev_cmd = event_new(base, STDIN_FILENO,
                                     EV_READ | EV_PERSIST,
                                     cmd_msg_cb, (void*)bev);
	printf("001\n");
    //上树 开始监听标准输入的读事件
    event_add(ev_cmd, NULL);
    
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr) );
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));
    //将ip地址转换为网络字节序
    inet_aton(argv[1], &server_addr.sin_addr);

    //连接到 服务器ip地址和端口 初始化了 socket文件描述符 socket+connect 
    bufferevent_socket_connect(bev, (struct sockaddr *)&server_addr,
                               sizeof(server_addr));
    //设置buffer的回调函数 主要设置了读回调 server_msg_cb ,传入参数是标准输入的读事件
    bufferevent_setcb(bev, server_msg_cb, NULL, event_cb, (void*)ev_cmd);
    bufferevent_enable(bev, EV_READ | EV_PERSIST);
	printf("002\n");

    event_base_dispatch(base);//循环等待
    event_free(ev_cmd);
    bufferevent_free(bev);
    event_base_free(base);
    printf("finished \n");
    return 0;
}
//终端输入回调
void cmd_msg_cb(int fd, short events, void* arg)
{
    char msg[1024];
	// 读标准数据
    int ret = read(fd, msg, sizeof(msg));
    if( ret < 0 )
    {
        perror("read fail ");
        exit(1);
    }
    //得到bufferevent指针,目的是为了写到bufferevent的写缓冲区
    struct bufferevent* bev = (struct bufferevent*)arg;

    //把终端的消息发送给服务器端
    bufferevent_write(bev, msg, ret);
}

void server_msg_cb(struct bufferevent* bev, void* arg)
{
    char msg[1024];
    size_t len = bufferevent_read(bev, msg, sizeof(msg));
    msg[len] = '\0';
    printf("recv %s from server\n", msg);
}

void event_cb(struct bufferevent *bev, short event, void *arg)
{
    if (event & BEV_EVENT_EOF)
        printf("connection closed\n");
    else if (event & BEV_EVENT_ERROR)
        printf("some other error\n");
    else if( event & BEV_EVENT_CONNECTED)
    {
        printf("the client has connected to server\n");
        return ;
    }

    //这将自动close套接字和free读写缓冲区
   	bufferevent_free(bev);
   	
    //释放event事件 监控读终端
  	struct event *ev = (struct event*)arg;
    event_free(ev);
	exit(0);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值