网络编程多路复用--EPOLL实现一对一聊天

一,IO复用几种方法的比较

select, poll, epoll都可以实现套接字I/O复用,select这个函数是有缺陷的,主要体现在两个方面:

  1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024,这是在<sys/select.h>中的一个常量,因此 Select 模型的最大并发数就被相应限制了,但是对于大多数应用程序而言,这个数是够用的,而且有可能还是太大的,多数应用程序只使用3~10个描述符。而如今的网络服务器小小的都有几万的连接,虽然可以使用多线程多进程(也就有N*1024个)。但是这样处理起来既不方面,性能又低。
  2. 效率问题, select 每次调用后都会线性扫描全部的 fdset 集合,这样效率就会呈现线性下降。
  3. poll 模型

    基本上效率和 select 是相同的, select 缺点的 1和 2 它都没有改掉。

二,select的一对一聊天程序:

1,服务器端代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

#define SERVER_PORT 7777
#define BUF_LEN 256

int setupSocket()
{
    int ret = 0;
    //1,创建socket
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock < 0)
    {
        perror("socket");
        return -1; 
    }
    
    //2,绑定
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    //设置套接字可以复用,在关闭后,重新启动不用等待。
    int flags=1;
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
    
    if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
    {
        perror("bind");
        ret = -1;
        goto sock_error;
    }
    
    //3,监听
    if( listen(server_sock, 10) < 0)
    {
        perror("listen");
        ret = -1;
        goto sock_error;
    }
    ret = server_sock;
    goto sock_ok;
sock_error:
    close (server_sock);
sock_ok:
    return ret;
}

int main()
{    
    int ret = 0, server_sock = 0, clisock = 0, maxid = 0, fd = 0, number = 0;
    char buf[BUF_LEN] = {0};
    struct sockaddr_in  client_addr;
    //socklent_t 就是一个无符号整型 unsigned int
    socklen_t addrlen = sizeof(struct sockaddr_in);
    
    //1,初始化socket
    server_sock = setupSocket();
    if(server_sock < 0)
    {
        perror("setupSocket");
        return -1;
    }
    
    //(1)定义描述符集合
    fd_set readfds, testfds;
    struct timeval  tv;
    tv.tv_sec = 3;
    tv.tv_usec = 0;
    
    //(2)填充要关注的事件,包括,描述符,事件
    //清空fd_set集合
    FD_ZERO(&readfds);
    
    //向fd_set集合中添加一个套接字
    FD_SET(server_sock, &readfds);
    FD_SET(STDIN_FILENO, &readfds);
    
    maxid = server_sock + 1;
    
    while(1)
    {
        testfds = readfds;
        ret =  select(maxid, &testfds, NULL, NULL, &tv);
        //printf("select return: %d, timeout.tv_sec: %lu\n", ++number, (unsigned long)tv.tv_sec);
        
        if(ret < 0)
        {
            perror("select");
            continue;
        }
        else if(ret == 0)
        {
            tv.tv_sec = 3;
            tv.tv_usec = 0;
            //printf("timeout...\n");
            continue;
        }
        
        //
        for(fd = 0; fd < maxid; fd++)
        {
            //判断该描述符是否还存在于fd_set集合中,若存在,则表明该描述字发生了读/写事件
            if(!FD_ISSET(fd, &testfds))
            {
                continue;
            }
            
            if(fd == server_sock)
            {
                //2,接受客户的连接请求
                addrlen = sizeof(struct sockaddr_in);
                if((clisock = accept(server_sock, (struct sockaddr *)&client_addr, &addrlen)) < 0)
                {
                    perror("accept");
                    break;
                }
                printf("connect form : %s, port : %u\n", 
                    (char *)inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
                    
                FD_SET(clisock, &readfds);
                maxid = (maxid > clisock ? maxid : clisock + 1);
            }
            else if(fd == STDIN_FILENO)
            {
                bzero(buf, BUF_LEN);
                if(fgets(buf, BUF_LEN, stdin) == NULL)
                {
                    perror("fgets");
                }
                
                if(send(clisock, buf, strlen(buf), 0) < 0)
                {
                    perror("send");
                    break;
                }
            }
            else if(fd == clisock)
            {
                bzero(buf, BUF_LEN);
                //read(fd, buf, BUF_LEN);
                ret = recv(fd, buf, BUF_LEN, 0);
                if(ret < 0)
                {
                    perror("recv");
                }
                else if(ret == 0)
                {
                    printf("client close\n");
                    close(fd);
                    FD_CLR(fd, &readfds);
                }
                else
                {
                    printf("recv : %s\n", buf);
                }                
            }
        }
    }

error_sock:
    close(server_sock);
    //4,关闭
    return ret;
}

 

2,客户端代码:


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#define SERVER_PORT 7777
#define BUF_LEN 256

int main()
{
    int ret = 0, maxid = 0, i = 0;
    char buf[BUF_LEN] = {0};
    //1,创建socket
    int clisock = socket(AF_INET, SOCK_STREAM, 0);
    if(clisock < 0)
    {
        perror("socket");
        return -1;
    }
    printf("socket ok.\n");
    
    //2,连接到服务器
    struct sockaddr_in  server_addr;
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");    
    
    if((ret = connect(clisock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))) < 0 )
    {
        perror("connect");
        goto error_0;
    }
    printf("connect ok.\n");
    
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    
    FD_SET(STDIN_FILENO, &readfds);
    FD_SET(clisock, &readfds);
    maxid = clisock + 1;
    while(1)
    {
        tempfds = readfds;
        ret = select(maxid, &tempfds, NULL , NULL, NULL);
        if(ret < 0)
        {
            perror("select");
        }
        
        for(i = 0; i < maxid; i++)
        {
            if(!FD_ISSET(i, &tempfds))
            {
                continue;
            }
            
            if(i == STDIN_FILENO)
            {
                memset(buf, 0, BUF_LEN);
                scanf("%s", buf);
                if((ret = send(clisock, buf, strlen(buf), 0)) <= 0)
                {
                    perror("send");
                    goto error_0;
                }        
            }
            else
            {
                memset(buf, 0, BUF_LEN);
                if((ret = recv(clisock, buf, BUF_LEN, 0)) <= 0)
                {
                    perror("recv");
                    goto error_0;
                }
        
                printf("client recv : %s\n", buf);
            }
        }
    }
error_0:
    //4,关闭连接
    close(clisock);
    printf("client closed \n");
    return 0;
}

三,epoll实现的一对一聊天程序:

1,服务器端代码:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

#define PORT 10234
#define LISTEN_Q 5
#define BUF_SIZE 256
#define MAX_EVENTS 500

//服务器端的初始化,不需要客户端参与
int setup_listen_sock() 
{
    int sockfd, len;
    struct sockaddr_in address;

    //1,创建TCP套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1) {
        perror("socket");
        exit(1);
    }

    //2,填充要绑定的服务器地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    len = sizeof(address);
    
    //设置可以复用地址
    int on = 1;
    if(setsockopt(sockfd,SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1){
        perror("setsockopt");
        exit(-1);
    }

    //3,绑定地址
    if(bind(sockfd, (struct sockaddr *)&address, len) == -1) {
        perror("bind");
        exit(1);
    }

    //4,监听客户端连接
    if(listen(sockfd, LISTEN_Q) == -1) {
        perror("listen");
        exit(1);
    }

    return sockfd;    
}

int main()
{
    int listen_sockfd, client_sockfd = 0, i = 0, ret = 0, client_len;
    struct sockaddr_in client_address;    
    char rwbuf[BUF_SIZE] = {0};
    //服务器端初始化,返回服务器端的监听套接字
    listen_sockfd = setup_listen_sock();
    
    //(1)定义epoll相关的数据结构
    struct epoll_event eventArray[MAX_EVENTS];
    struct epoll_event event_sock;
    struct epoll_event event_stdin;
    
    //(2)调用epoll_create,创建epoll描述符
    int epollfd = epoll_create(MAX_EVENTS);
    
    //(3)填充要关注的信息,包括套接字描述符和事件
    event_sock.events = EPOLLIN;
    event_sock.data.fd = listen_sockfd;//把listen_sockfd fd封装进events里面
    
    event_stdin.events = EPOLLIN;
    event_stdin.data.fd = STDIN_FILENO;
    
    //(4)epoll_ctl设置属性,注册事件
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sockfd, &event_sock) < 0 )
    {
        printf("epoll 加入失败 fd:%d\n",listen_sockfd);
        exit(-1);
    }
    
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &event_stdin) < 0)
    {
        printf("epoll 加入失败 fd:%d\n",listen_sockfd);
        exit(-1);
    }

    while(1) 
    {
        printf("server waiting\n");        
        
        //(5)等待事件发生
        ret = epoll_wait(epollfd, eventArray, MAX_EVENTS, -1);

        if(ret < 0)
        {
            perror("epoll_wait");
            exit(1);
        }
        
        //(4)获取哪个套接字上发生了事件
        //select返回后会把未发生事件的描述符从fd_set集合中移除
        for(i = 0; i < ret; i++) 
        {            
            //错误输出
            if((eventArray[i].events & EPOLLERR) ||
                (eventArray[i].events & EPOLLHUP) || 
                !(eventArray[i].events & EPOLLIN)) 
            {
                      perror("epoll error");
                   close(eventArray[i].data.fd);
                   exit(-1);
            }

            else if(eventArray[i].data.fd == listen_sockfd)
            {  
                // receive client connect request
                client_len = sizeof(client_address);
                client_sockfd = accept(listen_sockfd, (struct sockaddr *)&client_address, &client_len);

                //把客户端连接注册到监听中
                struct epoll_event event_cli;
                event_cli.data.fd = client_sockfd;
                event_cli.events = EPOLLIN;
                epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sockfd, &event_cli);
                printf("adding client on fd %d\n", client_sockfd);
            }
            else if(STDIN_FILENO == eventArray[i].data.fd)
            {
                read(STDIN_FILENO, rwbuf, BUF_SIZE);                
                if(client_sockfd > 0)
                {
                    send(client_sockfd, rwbuf, strlen(rwbuf), 0);
                    printf("send client_sockfd %d: %s\n",client_sockfd, rwbuf);
                }
            }
            else
            {
                int nread = 0;
                ioctl(eventArray[i].data.fd, FIONREAD, &nread); 
                //得到缓冲区里有多少字节要被读取,然后将字节数放入nread里面
                if(nread == 0) 
                {  
                    // client disconnect
                    close(eventArray[i].data.fd);
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, eventArray[i].data.fd, &eventArray[i]);
                    printf("removing client on fd :%d\n", eventArray[i].data.fd);
                }
                else 
                {  // receive client data
                    read(eventArray[i].data.fd, rwbuf, BUF_SIZE);
                    //printf("serving client on fd %d : %d\n", fd, data);
                    printf("recv fd %d: %s\n", eventArray[i].data.fd, rwbuf);
                }
            }
        }
    }
    
    close(epollfd);
}
        
2,epoll客户端代码

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

#define PORT 10234
#define BUF_LEN 256
#define MAX_EVENTS 500

int main()
{
    int ret = 0, i = 0;
    char buf[BUF_LEN] = {0};
    
    //1,创建socket
    int clisock = socket(AF_INET, SOCK_STREAM, 0);
    if(clisock < 0)
    {
        perror("socket");
        return -1;
    }
    printf("socket ok.\n");
    
    //2,连接到服务器
    struct sockaddr_in  server_addr;
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");    
    
    if((ret = connect(clisock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))) < 0 )
    {
        perror("connect");
        goto error_0;
    }
    printf("connect ok.\n");
    
    //(1)定义相关数据结构
    int epollfd=0;
    struct epoll_event event_stdin;
    struct epoll_event event_clisock;
    struct epoll_event eventArray[MAX_EVENTS];
    
    //(2)创建epoll专用的描述符
    epollfd = epoll_create(MAX_EVENTS);
    
    //(3)填充要关注的信息,包括套接字描述和事件
    event_stdin.events = EPOLLIN|EPOLLET;
    event_stdin.data.fd = STDIN_FILENO;//把STDIN_FILENO封装进events里面    
    
    event_clisock.events = EPOLLIN|EPOLLET;
    event_clisock.data.fd = clisock;//把clisock封装进events里面
    
     //(4)epoll_ctl设置属性,注册事件
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &event_stdin) < 0 )
    {
        printf("epoll 加入失败 fd:%d\n",STDIN_FILENO);
        exit(-1);
    }
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clisock, &event_clisock) < 0 )
    {
        printf("epoll 加入失败 fd:%d\n",clisock);
        exit(-1);
    }

    while(1)
    {

        //(5)等待事件发生
        ret = epoll_wait(epollfd, eventArray, MAX_EVENTS, -1);
        if(ret < 0)
        {
            perror("epoll_wait");
            exit(1);
        }        
        
        //(6)处理事件
        for(i = 0; i < ret; i++)
        {
            //错误输出
            if((eventArray[i].events & EPOLLERR) ||
                (eventArray[i].events & EPOLLHUP) || 
                !(eventArray[i].events & EPOLLIN)) 
            {
                      perror("epoll error");
                   close(eventArray[i].data.fd);
                   exit(-1);
            }
            
            else if(STDIN_FILENO == eventArray[i].data.fd)
            {
                memset(buf, 0, BUF_LEN);
                read(STDIN_FILENO, buf, BUF_LEN);    

                send(clisock, buf, strlen(buf), 0);
                printf("send clisock %d: %s\n",clisock, buf);
            }
            else
            {
                memset(buf, 0, BUF_LEN);
                ret = recv(clisock, buf, BUF_LEN, 0);
                if(ret <0)
                {
                    perror("recv");
                    goto error_0;
                }
                else if(ret == 0)
                {
                    goto error_0;
                }
        
                printf("client recv : %s\n", buf);
            }

        }
    }
error_0:
    //4,关闭连接
    close(clisock);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haospark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值