EPOLL高并发服务器

IO复用模型有select、poll、epoll,关于它们的区别:

1.select能处理的fd数量很少,我的电脑是1024个(sizeof(fd_set)*8),而epoll的fd数量没有限制,只与内存大小有关。

2.select当fd就绪时,需要将所有fd拷贝到内核态再将活跃的fd拷贝回用户态,还有遍历fd数组判断哪个是活跃的。而epoll不需要频繁的拷贝,当fd就绪时调用回调函数插入到fd就绪链边中。

3.select是水平触发,epoll支持水平触发和边缘触发,边缘触发在调用epoll_wait时,只有fd从不可读/写到可读/写才通知给用户。而select只要fd的数据没有读完都会不断通知给用户,所以epoll读/写数据时要一次性完成(比如循环读写),否则造成fd死锁。

4.单线程多路复用应该设置nonblock套接字,否则某个客户端出现block就会影响其他fd的处理。

 

具体一点说就是:

调用select函数需要将用户buffer的fd拷贝到内核buffer进行轮询问,有事件发生又将它们拷贝回用户buffer(两个buffer都在物理内存上面)。既然都在内存上面为啥不公用一块内存呢?epoll就是这么做,mmap将地址空间的虚拟地址映射到一块物理内存,提供给用户和内核使用,减少了拷贝过程,同时那块内存用红黑树来存储fd(加速增删查改)。

服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <list>
#include <fcntl.h>

#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8888
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1024

#define EPOLLEVENTS 100

using namespace std;
//设置非阻塞socket,IO多路复用应该用非阻塞socket
void set_nonblock(int fd);
//添加、删除、修改事件到epoll
void do_event(int epollfd, int fd, int op, int state);
bool Read(int epollfd, int fd, char *buff, int size);
bool Send(int epollfd, int fd, char *buff);

list<int>all_fd;
int main()  {
    char buff[MAXSIZE];
    char WARN[] = "just you online, honey.\n";
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd == -1){
        perror("socket error:");
        exit(1);
    }
    else puts("create socket done.");
    struct sockaddr_in servaddr;
    servaddr.sin_addr.s_addr = inet_addr(IPADDRESS);
    servaddr.sin_port = htons(PORT);
    servaddr.sin_family = AF_INET;
    if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){
        perror("bind error:");
        exit(1);
    }
    else puts("bind done.");
    listen(listenfd, LISTENQ);
    puts("listen done");
    struct epoll_event events[EPOLLEVENTS];

    int epollfd = epoll_create(FDSIZE);
    do_event(epollfd, listenfd, EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
    set_nonblock(listenfd);
    while(true){
        int num = epoll_wait(epollfd, events, EPOLLEVENTS, -1);//阻塞epoll_wait
        for(int i=0; i<num; ++i){
            int fd = events[i].data.fd;
            if(fd == listenfd){
                printf("epolled get a new\n");
                struct sockaddr_in cliaddr;
                socklen_t cliaddrlen = sizeof(struct sockaddr_in);
                int clifd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
                if(clifd == -1){
                    perror("accept error");
                    continue;
                }
                set_nonblock(clifd);
                printf("accept a new client: %s %d, welcome!\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
                do_event(epollfd, clifd, EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
                all_fd.push_back(clifd);
            }
            else{
                if(Read(epollfd, fd, buff, MAXSIZE)){
                    printf("success to read. %s %d\n",buff,all_fd.size());
                    if(all_fd.size() == 1){
                        Send(epollfd, fd, WARN);
                    }
                    else{
                        for(auto it=all_fd.begin(); it!=all_fd.end(); ++it){
                            int tfd = *it;
                            if(tfd != fd){
                                if(!Send(epollfd, tfd, buff)){
                                    *it = -1;
                                }
                            }
                        }
                        all_fd.remove(-1);
                    }
                }
                else{
                    perror("server read message fail.");
                    continue;
                }
            }
        }
    }
    close(epollfd);
    close(listenfd);
    return 0;
}

void set_nonblock(int fd){
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK);
}
void do_event(int epollfd, int fd, int op, int state){
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = state;
    epoll_ctl(epollfd, op, fd, &ev);
}
bool Read(int epollfd, int fd, char *buff, int size){
    memset(buff, 0, MAXSIZE);
    int num;
    do{
        num = recv(fd, buff, size, 0);
        if(num < 0){
            if(errno == EAGAIN){
                return true;
            }
            else{
                perror("read error:");
                close(fd);
                do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
                return false;
            }
        }
        else{
            size -= num;
            buff += num;
        }
    }while(num > 0);

    printf("read done %s\n",buff);
    return true;
}
bool Send(int epollfd, int fd, char *buff){
    int size = strlen(buff);
    while(size > 0){
        int num = send(fd, buff, size, 0);
        if(num < 0){
            perror("send error:");
            close(fd);
            do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
            return false;
        }
        else{
            size -= num;
            buff += num;
        }
    }
    printf("perfect send\n");
    return true;
}

客户端(或者直接TELNET连服务器)

客户端参考https://blog.csdn.net/qq_31564375/article/details/51581038的思路,开个子进程,父子进程管道相连,子进程负责写数据,父进程负责读数据和服务器通信。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <list>
#include <fcntl.h>
#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8888
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1024
#define EPOLLEVENTS 100

using namespace std;
//设置非阻塞socket,IO多路复用应该用非阻塞socket
void set_nonblock(int fd);
//添加、删除、修改事件到epoll
void do_event(int epollfd, int fd, int op, int state);
bool Read(int epollfd, int fd, char *buff, int size);
bool Send(int epollfd, int fd, char *buff);

int main(){
    struct epoll_event events[3];
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(IPADDRESS);
    servaddr.sin_port = htons(PORT);

    int serfd = socket(AF_INET, SOCK_STREAM, 0);
    if(serfd == -1){
        perror("socket create fail.\n");
        exit(-1);
    }
    else puts("socket create done.");
    if(connect(serfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        perror("connect fail.\n");
        exit(-1);
    }
    else puts("connect done.");

    set_nonblock(serfd);
    int pip_fd[2];
    if(pipe(pip_fd) < 0){
        perror("pipe error.");
        exit(-1);
    }
    else puts("pipe done.");
    int epollfd = epoll_create(FDSIZE);
    do_event(epollfd, serfd, EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
    do_event(epollfd, pip_fd[0], EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
    bool cliworking = true;

    int pid = fork();
    if(pid < 0){
        perror("fork error.");
        exit(-1);
    }
    else if(pid == 0){
        close(pip_fd[0]);
        printf("print exit to leave.\n");
        while(cliworking){
            char buff[MAXSIZE]={0};
            scanf("%s",buff);
            if(strncmp("exit", buff, 4) == 0) cliworking = 0;
            write(pip_fd[1], buff, strlen(buff));
        }
        close(serfd);
        close(pip_fd[1]);
        close(epollfd);
        exit(0);
    }
    else{
        close(pip_fd[1]);
        while(cliworking){
            int num = epoll_wait(epollfd, events, 2, -1);
            for(int i=0; i<num; ++i){
                int fd = events[i].data.fd;
                if(fd == serfd){
                    char buff[MAXSIZE]={0};
                    if(Read(epollfd, fd, buff, MAXSIZE)){
                        printf("%s\n",buff);
                    }
                    else{
                        perror("server unconnect.");
                        cliworking = 0;
                    }
                }
                else{
                    char buff[MAXSIZE]={0};
                    read(fd, buff, MAXSIZE);
                    if(strncmp("exit", buff, 4) == 0){
                        printf("here\n");
                        cliworking = 0;
                    }
                    else if(!Send(epollfd, serfd, buff)){
                        perror("send error.");
                    }
                }
            }
        }
        close(pip_fd[0]);
        close(serfd);
        close(epollfd);
        exit(0);
    }
    return 0;

}

void set_nonblock(int fd){
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK);
}
void do_event(int epollfd, int fd, int op, int state){
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = state;
    epoll_ctl(epollfd, op, fd, &ev);
}
bool Read(int epollfd, int fd, char *buff, int size){
    memset(buff, 0, MAXSIZE);
    int num;
    do{
        num = recv(fd, buff, size, 0);
        if(num < 0){
            if(errno == EAGAIN){
                return true;
            }
            else{
                perror("read error:");
                close(fd);
                do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
                return false;
            }
        }
        else{
            size -= num;
            buff += num;
        }
    }while(num > 0);
    return true;
}
bool Send(int epollfd, int fd, char *buff){
    int size = strlen(buff);
    while(size > 0){
        int num = send(fd, buff, size, 0);
        if(num < 0){
            perror("send error:");
            close(fd);
            do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
            return false;
        }
        else{
            size -= num;
            buff += num;
        }
    }
    return true;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值