采用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; 
} 


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用 epoll 实现的一个简单的 TCP 客户端: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #define MAX_EVENTS 10 #define BUF_SIZE 1024 int main(int argc, char *argv[]) { if (argc != 3) { printf("Usage: %s <ip> <port>\n", argv[0]); exit(1); } int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(atoi(argv[2])); if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("connect"); exit(1); } int epollfd = epoll_create(MAX_EVENTS); if (epollfd == -1) { perror("epoll_create"); exit(1); } struct epoll_event ev, events[MAX_EVENTS]; memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = sockfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) { perror("epoll_ctl"); exit(1); } char buf[BUF_SIZE]; while (1) { int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { if (errno == EINTR) { continue; } perror("epoll_wait"); exit(1); } for (int i = 0; i < nfds; ++i) { if (events[i].data.fd == sockfd) { int n = read(sockfd, buf, BUF_SIZE); if (n == -1) { perror("read"); exit(1); } else if (n == 0) { printf("Server closed connection\n"); close(sockfd); exit(0); } else { buf[n] = '\0'; printf("Received message: %s", buf); } } } } return 0; } ``` 首先创建了一个 socket,并连接到指定的服务器。然后创建一个 epoll 实例,将 socket 加入到 epoll 中,并设置监听的事件类型为可读。之后不断循环调用 epoll_wait 等待事件的发生,如果是 socket 的可读事件,则读取数据并打印。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值