客户端代码,使用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;
}