libevent使用

客户端代码,使用libevent+单线程.
libevent,我个人理解,它就是给epoll封装了一下,搞了个时间注册的机制,说到底还是单线程。我觉得,起 数量与CPU核心数量相匹配的 线程来处理计算业务,然后把IO需求都扔个一个单独的线程去处理,这样的效率会更高。

#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <event.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>


#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888

#define APP_EVT_TYPE_LISTEN 0
#define APP_EVT_UPPER 1


void dispatch_event(evutil_socket_t fd, short type, void *arg);


int prepare_socket(char *ip_raw, int port_raw)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port_raw);
    addr.sin_addr.s_addr = inet_addr(ip_raw);

    bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr));
    listen(sock, 1024);

    return sock;
}

struct app_event{
    int type;
    void *arg;
};

struct accept_arg{
    int listen_fd;
    struct event_base *evt_base;
};

struct upper_arg{
    int accept_fd;
    struct event *evt;
};

void *app_accept(void *arg)
{
    struct accept_arg *_arg = (struct accept_arg *)arg;

    struct sockaddr_in client_addr = {0};
    int client_addr_size = sizeof(struct sockaddr_in);
    int accept_fd = accept(_arg->listen_fd, (struct sockaddr *)&client_addr, &client_addr_size);
    if(accept_fd < 0){
        printf("accept failed!\n");
        return NULL;
    }
    char buf_ip[64] = {0};
    printf("new client(ip: %s, port: %d)!\n", 
        inet_ntop(AF_INET, &client_addr.sin_addr, buf_ip, 64), ntohs(client_addr.sin_port));

//申请一个空白的event,不与任何东西绑定,因为我需要先拿到event的指针,然后才能把保存指针的结构体绑定到该event上
//具体看upper_send_back函数中,n_read == 0那个分支的注释
    struct event *evt = event_new(NULL, 0, 0, NULL, NULL);
    struct upper_arg *upper_arg = (struct upper_arg *)malloc(sizeof(struct upper_arg));
    upper_arg->accept_fd = accept_fd;
    upper_arg->evt = evt;

    struct app_event *app_event = (struct app_event *)malloc(sizeof(struct app_event));
    app_event->type = APP_EVT_UPPER;
    app_event->arg = upper_arg;

//把accept好的fd丢到libevent里去,处理函数dispatch_event是为多线程版本准备的,单线程环境下可以直接写处理函数
//event_assign函数专门用于绑定event
    event_assign(evt, _arg->evt_base, accept_fd, EV_READ|EV_PERSIST, dispatch_event, app_event);
    event_add(evt, NULL);

    return NULL;
}

void *upper_send_back(void *arg)
{
    printf("upper_send_back in!\n");
    struct upper_arg *_arg = (struct upper_arg *)arg;
    int fd = _arg->accept_fd;
    struct event *evt = _arg->evt;

    char buf[1024] = {0};
    int n_read = 0;
    //while(1){
        n_read = read(fd, buf, 1023);
        if(n_read < 0){
            printf("read failed! errno: %d\n", errno);
            return NULL;
        }
        if(n_read == 0){
            printf("client closed! fd: %d\n", fd);
            close(fd);
            //需要拿到event指针的意义就在这里,在客户端关闭连接时需要操作对应的event
            event_del(evt);
            event_free(evt);
            return NULL;
        }
        if(n_read > 0){
            printf("READ: %s!\n", buf);
            for(int i = 0; i < n_read; ++i){
                buf[i] = toupper(buf[i]);
            }
            write(fd, buf, strlen(buf));
            printf("WRITE: %s!\n", buf);
            memset(buf, 0x00, 1024);
        }
    //}
    return NULL;
}

//这个dispatch_event是为多线程准备的,单线程下可以把里面调用的两个函数拆出来,分别注册成为listen_fd的回调和accept_fd的回调
void dispatch_event(evutil_socket_t fd, short type, void *arg)
{
    struct app_event *app_evt = (struct app_event *)arg;
    if(app_evt->type == APP_EVT_TYPE_LISTEN){
        app_accept(app_evt->arg);//TODO: send to a thread
        //这里本来想写多线程的
    }
    else if(app_evt->type == APP_EVT_UPPER){
        upper_send_back(app_evt->arg);
        //这里也是本来想写多线程的,感觉多线程下的性能应该会更高
    }
    else{
        printf("event error!\n");
        return;
    }
}

int main()
{
    int server_sock = prepare_socket(SERVER_IP, SERVER_PORT);

    struct event_base *evt_base = event_base_new();//先申请一个event_base

    struct accept_arg *accept_arg = (struct accept_arg *)malloc(sizeof(struct accept_arg));
    accept_arg->listen_fd = server_sock;
    accept_arg->evt_base = evt_base;

    struct app_event *listen_event = (struct app_event *)malloc(sizeof(struct app_event));
    listen_event->type = APP_EVT_TYPE_LISTEN;
    listen_event->arg = accept_arg;

//再申请一个event,设置好关注的事件和回调,并与event_base绑定
    struct event *evt = event_new(evt_base, server_sock, EV_READ|EV_PERSIST, dispatch_event, listen_event);
//event_add的参数是event
    event_add(evt, NULL);

    while(1){
    	//设置的回调函数会在这个dispatch函数中被调用(当事件触发时)
        event_base_dispatch(evt_base);
    }
}

下面是客户端,因为没搞明白怎么把linux的最大fd数量取消掉,所以最多只起了3000个线程来测试。我最后写了个脚本,运行了10个客户端程序给服务端加压,就相当于30000个线程在测试吧,4核8线程的cpu才30%的使用量。没有打印等待时间,需要以后补上

#include <stdlib.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/errno.h>
#include <unistd.h>
#include <netdb.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/resource.h>


//#define SERVER_IP "::1"
//#define SERVER_IP "fe80::1de:4a41:609b:93db"
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888


void *simple_test(void *arg)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
        printf("socket failed! errno: %d\n", errno);
        exit(0);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); 

    int ret = connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
    if(ret < 0){
        printf("connect failed! sock: %d, errno: %d\n", sock, errno);
        return NULL;
    }

    char buff[256] = {0};
    char *write_str = "xuezb test!";
    ret = write(sock, write_str, strlen(write_str));
    if(ret < 0){
        printf("write failed! errno: %d!\n", errno);
        return NULL;
    }
    printf("WRITE: %s\n", write_str);

    read(sock, buff, 256 - 1);
    printf("READ: %s\n", buff);

    close(sock);
    return NULL;
}

void *one_thread_routine(void *arg)
{
    while(1){
        simple_test(NULL);
    }
}

int create_one_thread()
{
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, one_thread_routine, NULL);
    if(ret < 0){
        printf("pthread_create failed! errno: %d\n", errno);
        return 0;
    }
    return 0;
}
int	dpl_set_nofile_limit(int max)
{
	int	stat = 0;	
	int		rv;
	struct	rlimit	lim;

	if(max<= 0)
		return -1;
	
	rv = getrlimit(RLIMIT_NOFILE, &lim);
	if(rv != 0){
		printf("getrlimit errno[%d] \n", errno);
		return -1;
	}
	if(max > lim.rlim_cur){
		if(max > lim.rlim_max){
			lim.rlim_cur = lim.rlim_max;
			printf("out of rlim_max, rlim_max: %ld, want: %d\n", lim.rlim_max, max);
			stat = -1;
		}else
			lim.rlim_cur = max;
		rv = setrlimit(RLIMIT_NOFILE, &lim);
		if(rv != 0){
			printf("getrlimit errno[%d] \n", errno);
			return -1;
		}
	}
	return 0;
}
int main()
{
    int ret = dpl_set_nofile_limit(40000);
    if(ret < 0){
        printf("dpl_set_nofile_limit failed!\n");
        return -1;
    }
    int n = 3000;
    for(int i = 0; i < n; i++){
        printf("i: %d\n", i);
        create_one_thread();
    }
    while(1){
        sleep(100);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值