采用epoll实现echo server和client

转自 http://zhouxiaodan.blog.51cto.com/1177793/1176286

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


using namespace std; 
 
#define MAXLINE 512  
#define OPEN_MAX 100  
#define LISTENQ 20  
#define SERV_PORT  9876 
#define INFTIM 1000  
 
void setnonblocking(int sock)  
{  
    int opts;  
    opts=fcntl(sock,F_GETFL);  
 
    if(opts<0)  
    {  
        perror("fcntl(sock,GETFL)");  
        exit(1);  
    }  
 
    opts = opts | O_NONBLOCK;  
 
    if(fcntl(sock,F_SETFL,opts)<0)  
    {  
        perror("fcntl(sock,SETFL,opts)");  
        exit(1);  
    }  
}  
/* 
   假如发送端流量大于接收端的流量 
   (意思是epoll所在的程序读比转发的socket要快), 
   由于是非阻塞的socket,那么send()函数虽然返回, 
   但实际缓冲区的数据并未真正发给接收端, 
   这样不断的读和发, 
   当缓冲区满后会产生EAGAIN错误(参考man send),同时, 
   不理会这次请求发送的数据.所以, 
   需要封装socket_send()的函数用来处理这种情况, 
   该函数会尽量将数据写完再返回,返回-1表示出错。 
   在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN), 
   那么会等待后再重试.这种方式并不很完美, 
   在理论上可能会长时间的阻塞在socket_send()内部, 
   但暂没有更好的办法. 
 
 */ 
 
ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) 
{ 
    ssize_t tmp; 
    size_t total = buflen; 
    const char *p = buffer; 
 
    while(1) 
    { 
        tmp = send(sockfd, p, total, 0); 
        if(tmp < 0) 
        { 
            // 当send收到信号时,可以继续写,但这里返回-1. 
            if(errno == EINTR) 
                //return -1;zxd 
                continue; 
 
            // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, 
            // 在这里做延时后再重试. 
            if(errno == EAGAIN) 
            { 
                usleep(1000); 
                continue; 
            } 
 
            return -1; 
        } 
 
        if((size_t)tmp == total) 
            return buflen; 
 
        total -= tmp; 
        p += tmp; 
    } 
 
    return tmp; 
} 
 
 
void epoll_ctl_err_show()   
{   
    std::cout << "error at epoll_ctl" << std::endl;   
    if(EBADF == errno)   
    {   
        std::cout << "error at epoll_ctl, error  is EBADF" << std::endl;   
    }   
    else if(EEXIST == errno)   
    {   
        std::cout << "error at epoll_ctl, error  is EEXIST" << std::endl;   
    }   
    else if(EINVAL == errno)   
    {   
        std::cout << "error at epoll_ctl, error  is EINVAL" << std::endl;   
    }   
    else if(ENOENT == errno)   
    {   
        std::cout << "error at epoll_ctl, error  is ENOENT" << std::endl;   
    }   
    else if(ENOMEM == errno)   
    {   
        std::cout << "error at epoll_ctl, error  is ENOMEM" << std::endl;   
    }   
    else if(ENOSPC == errno)   
    {   
        std::cout << "error at epoll_ctl, error  is ENOSPC" << std::endl;   
    }   
      
}  
 
int main()  
{  
    int i, maxi, listenfd, connfd, sockfd, epfd, nfds;  
    ssize_t n;  
    char line[MAXLINE];  
    socklen_t clilen;  
 
    struct epoll_event ev,events[20]; //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件  
    epfd=epoll_create(256); //生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256  
 
    struct sockaddr_in clientaddr;  
    struct sockaddr_in serveraddr;  
 
    listenfd = socket(AF_INET, SOCK_STREAM, 0);  
 
    setnonblocking(listenfd); //把用于监听的socket设置为非阻塞方式  
 
    ev.data.fd=listenfd; //设置与要处理的事件相关的文件描述符  
    ev.events=EPOLLIN | EPOLLET; //设置要处理的事件类型  
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); //注册epoll事件 
 
    bzero(&serveraddr, sizeof(serveraddr));  
    serveraddr.sin_family = AF_INET;  
    char *local_addr="127.0.0.1";  
    inet_aton(local_addr,&(serveraddr.sin_addr)); 
    serveraddr.sin_port=htons(SERV_PORT);  //或者htons(SERV_PORT);  
 
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));  
 
    listen(listenfd, LISTENQ);  
 
    maxi = 0;  
 
    for( ; ; ) {  
        nfds=epoll_wait(epfd, events, 20, -1); //等待epoll事件的发生  
         
 
        for(i=0;i<nfds;++i) //处理所发生的所有事件  
        {  
            if(events[i].data.fd==listenfd)    /**监听事件**/ 
            {  
                //循环accept 
                while((connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen)) > 0) 
                { 
                    setnonblocking(connfd); //把客户端的socket设置为非阻塞方式 
 
                    char *str = inet_ntoa(clientaddr.sin_addr);  
                    std::cout<<"connect from "<<str<<std::endl;  
 
                    ev.data.fd=connfd; //设置用于读操作的文件描述符  
                    ev.events=EPOLLIN | EPOLLET; //设置用于注测的读操作事件  
                    epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //注册ev事件  
                } 
            }  
            else if(events[i].events&EPOLLIN)     /**读事件**/ 
            {  
                //fprintf(stderr, "EPOLLIN................%d\n", maxi++); 
                if ( (sockfd = events[i].data.fd) < 0)  
                { 
                    continue;  
                } 
                memset(line, 0, MAXLINE); 
                n = 0; 
                int nread = 0; 
                while((nread= read(sockfd, line + n, MAXLINE)) > 0) 
                { 
                    n += nread; 
                }//读到EAGAIN,说明读完了 
 
 
                if(nread == -1 && errno != EAGAIN) 
                { 
                    epoll_ctl_err_show(); 
                    std::cout<<"readline error"<<std::endl; 
                    close(sockfd); //关闭一个文件描述符,那么它会从epoll集合中自动删除 
                    //描述符关闭后,后面的邋邋邋邋EPOLLOUT设置了,但不起作用了 
                    events[i].data.fd = -1;  
                } 
 
                //这里要加上判断,nread为0时,说明客户端已经关闭 
                //此时,需要关闭描述符,否则在/proc/id/fd下能看到描述符会一直存在 
                if(nread == 0) 
                { 
                    close(sockfd); 
                    continue; 
                } 
 
 
                ev.data.fd=sockfd; //设置用于写操作的文件描述符  
                ev.events=EPOLLOUT | EPOLLET; //设置用于注测的写操作事件  
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改sockfd上要处理的事件为EPOLLOUT  
            }  
            else if(events[i].events & EPOLLOUT)    /**写事件**/ 
            {  
                sockfd = events[i].data.fd;  
                //write(sockfd, line, n); orig 
 
                int iRet = socket_send(sockfd, line, strlen(line) + 1); 
                if(iRet == -1 || iRet != strlen(line) + 1) 
                { 
                    perror("write error!"); 
                }/*zxd*/ 
 
                ev.data.fd=sockfd; //设置用于读操作的文件描述符  
                ev.events=EPOLLIN | EPOLLET; //设置用于注册的读操作事件  
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); //修改sockfd上要处理的事件为EPOLIN  
            }  
        }  
    }  
} 

对于server的实现,是先读后写的模式,以后再将sfd设成EPOLLIN|EPOLLOUT|EPOLLET的模式再实验一下。

epoll_wait在客户端关闭后会返回,EPOLLIN被激活,此时nread为0,因客户端已经关闭,所以需要把sfd关闭。

nread为-1且errno==EAGAIN,说明数据已经读完,设置EPOLLOUT。

与之对应的echo client,先写后读,采用epoll模式:

#include <netinet/in.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <sys/epoll.h> 
#include <errno.h> 
#include <string.h> 
#include <fcntl.h> 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
#define BUFSIZE 512  
#define RET_ERR -1
#define RET_OK 0
#define Debug_UserLog printf
#define Debug_SysLog printf
 
void SetNonBlock(int fd) 
{ 
    int flag = fcntl ( fd, F_GETFL, 0 ); 
    fcntl ( fd, F_SETFL, flag | O_NONBLOCK ); 
} 
 
int main(int argc, char** argv) 
{ 
    int iRet = RET_OK; 
 
    if(4 != argc) 
    { 
        Debug_UserLog("Parameter: ServerIP Message ServerPort", RET_ERR); 
        return RET_ERR; 
    } 
 
    in_port_t i16_port = atoi(argv[3]); 
    if(0 >= i16_port) 
    { 
        Debug_UserLog("port number is wrong", RET_ERR); 
        return RET_ERR; 
    } 
 
    int sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    if(-1 == sk) 
    { 
        Debug_SysLog("open socket failed!"); 
        return RET_ERR; 
    } 
 
 
    struct sockaddr_in sa = {0}; 
    sa.sin_family = AF_INET; 
    sa.sin_port = htons(i16_port); 
 
    struct sockaddr_in *psa = &sa; 
 
    iRet = inet_pton(AF_INET, argv[1], &psa->sin_addr.s_addr); 
    if(0 == iRet) 
    { 
        Debug_UserLog("inet_pton failed, invalid address!", RET_ERR); 
        close(sk); 
        return RET_ERR; 
    } 
    else if(iRet < 0) 
    { 
        Debug_SysLog("inet_pton failed"); 
        close(sk); 
        return RET_ERR; 
    } 
 
    if(connect(sk, (struct sockaddr*)&sa, sizeof(sa)) < 0) 
    { 
        Debug_SysLog("connect failed"); 
        close(sk); 
        return RET_ERR; 
    } 
 
    SetNonBlock(sk); 
 
    int efd;  
    efd = epoll_create(10);  
    if(efd == -1) 
    { 
        perror("epoll_create error!"); 
        exit(1); 
    } 
 
    struct epoll_event event; 
    struct epoll_event events[10]; 
 
    event.events = EPOLLOUT | EPOLLIN | EPOLLET; 
    event.data.fd = sk; 
 
    epoll_ctl(efd, EPOLL_CTL_ADD, sk, &event); 
 
 
    getchar(); 
    int loop = 0; 
    while(1) 
    { 
        ssize_t numBytesRcvd = 0; 
        char buffer[BUFSIZE] = {0}; 
        int n = 0; 
        int i = 0; 
 
        if(loop == 1) 
        { 
            break; 
        } 
  
        n = epoll_wait(efd, events, 10, -1); 
 
        printf("%d\n", n); 
 
        for(i = 0; i < n; i++) 
        { 
            if(events[i].events & EPOLLOUT) 
            { 
                printf("EPOLLOUT...............\n"); 
                snprintf(buffer, BUFSIZE, "i am process %d, just say: %s\n", getpid(), argv[2]); 
 
                int n = strlen(buffer); 
                int nsend = 0; 
 
                while(n > 0) 
                { 
                    //nsend = send(events[i].data.fd, buffer + nsend, n, 0); 
                    nsend = write(events[i].data.fd, buffer + nsend, n); 
                    if(nsend < 0 && errno != EAGAIN) 
                    { 
 
                        Debug_SysLog("send failed"); 
                        close(events[i].data.fd); 
                        return RET_ERR; 
                    } 
                    n -= nsend; 
                } 
            } 
 
            if(events[i].events & EPOLLIN) 
            { 
                printf("EPOLLIN...............\n"); 
                memset(buffer, 0, BUFSIZE); 
 
                int len = strlen(buffer); 
                int n = 0; 
                int nrecv = 0; 


                while(1){ 
                    nrecv = read(events[i].data.fd, buffer + n, BUFSIZE - 1) ; 
                    if(nrecv == -1 && errno != EAGAIN) 
                    { 
                        perror("read error!"); 
                    } 
                    if((nrecv == -1 && errno == EAGAIN) || nrecv == 0) 
                    { 
                        break; 
                    } 
                    n += nrecv; 
                } 
                loop = 1; 
                printf("%s\n", buffer); 
            } 
        } 
    } 
    close(sk); 
    close(efd); 
    return RET_OK; 
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值