I/O複用3個小例項+將signal轉化為IO事件

_llc發表於2020-11-08

I/O複用3個小例項:

  1. nonblock connect():利用error:EINPROGRESS

    非阻塞connect()

    man手冊connect()

    The socket is nonblocking and the connection cannot be completed immediately. (UNIX domain sockets failed with EAGAIN instead.) It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).

    非阻塞socket發起連線不能立馬完成,利用io多路的寫事件,然後用getsocketopt拿到socket的error,判斷error是否==0,=0表示連線成功

    #include "me.h"
    #define BUFFER_SIZE 1023
    
    int setnonblocking(int fd)
    {
        int old_option = fcntl(fd,F_GETFL);
        int new_option = old_option | O_NONBLOCK;
        fcntl(fd,F_SETFL,new_option);
        return old_option;
    }
    
    //Linux下nonblock-connect()
    int unblock_connect(const char *ip,int port,int time)
    {
        struct sockaddr_in addr;
        memset(&addr,0,sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET,ip,&addr.sin_addr);
    
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        int fdopt = setnonblocking(sockfd);//fdopt儲存的是原來的fd status
        int ret = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
        // 非阻塞connect直接連線成功
        if (ret == 0)
        {
            printf("connect with server immediately\n");
            fcntl(sockfd,F_SETFL,fdopt);
            return sockfd;
        }
        else if (errno != EINPROGRESS)
        {
            printf("unblock connect not support\n");
            return -1;
        }
        //select使用
        fd_set readfds;
        fd_set writefds;
        struct timeval timeout;
    
        FD_ZERO(&readfds);
        FD_SET(sockfd,&writefds);
        
        timeout.tv_sec = time;
        timeout.tv_usec = 0;
    
        ret = select(sockfd + 1,&readfds,&writefds,NULL,&timeout);
        if (ret <= 0)
        {
            //超時未發生,或者有錯誤
            printf("connection time out\n");
            close(sockfd);
            return -1;
        }
    
        //或者sockfd不在返回的writefds表中
        if (!FD_ISSET(sockfd,&writefds))
        {
            printf("no events on sockfd found\n");
            close(sockfd);
            return -1;
        }
    
        //E IN PROGRESS 情況,
        int error = 0;
        socklen_t length = sizeof(error);
        //獲得SOL_SOCKET的SO_ERROR選項
        if (getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&length) < 0)
        {
            printf("get socket option failed\n");
            close(sockfd);
            return -1;
        }
    
        //error!=0說明不連線不成功,(出現其他錯誤
        if (error != 0)
        {
            printf("connection failed after select with the error: %d\n",error);
            close(sockfd);
            return -1;
        }
        //連線成功
        printf("connection ready after select with the socket: %d\n",sockfd);
        fcntl(sockfd,F_SETFL,fdopt);//恢復成 block fd
        return sockfd;
    }
    
    int main(int argc,char* argv[])
    {
        if (argc <= 2)
            error_handle("argc <= 2")
        const char* ip = argv[1];
        int port = atoi(argv[2]);
        
        int sockfd = unblock_connect(ip,port,10);
        if (sockfd < 0)
            return 1;
        close(sockfd);
        return 0;
    }
    

    在*inx下不具有移植性

  2. epoll實現監聽同一埠的tcp和udp服務

    #include "me.h"
    
    #define MAX_EVENT_NUMBER 1024
    #define TCP_BUFFER_SIZE 512
    #define UDP_BUFFER_SIZE 1024
    
    int setnonblocking(int fd)
    {
        int old_option = fcntl(fd,F_GETFL);
        int new_option = old_option | O_NONBLOCK;
        fcntl(fd,F_SETFL,new_option);
        return old_option;
    }
    
    void addfd(int epollfd,int fd)
    {
        struct epoll_event event;
        event.data.fd = fd;
        event.events = EPOLLIN | EPOLLET;//邊沿觸發模式
        epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
        setnonblocking(fd);
    }
    
    int main(int argc,char* argv[])
    {
        if (argc <= 2)
            error_handle("argc <= 2")
        const char *ip = argv[1];
        int port = atoi(argv[2]);
        
        struct sockaddr_in addr;
        memset(&addr,0,sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET,ip,&addr.sin_addr);
    
        //建立TCP SOCKET並且bind and listen
        int listenfd = socket(AF_INET,SOCK_STREAM,0);
        assert(listenfd >= 0);
    
        int ret = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr));
        assert(ret != -1);
    
        ret = listen(listenfd,5);
        assert(ret != -1);
    
        //建立UDP SOCKET
        memset(&addr,0,sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET,ip,&addr.sin_addr);
    
        int udpfd = socket(AF_INET,SOCK_DGRAM,0);
        assert(udpfd >= 0);
    
        //監聽相同埠
        ret = bind(udpfd,(struct sockaddr*)&addr,sizeof(addr));
        assert(ret != -1);
    
        struct epoll_event events[MAX_EVENT_NUMBER];
        int epollfd = epoll_create(5);
        assert(epollfd != -1);
    
        //註冊TCP UDP 監聽socket上的可讀事件
        addfd(epollfd,listenfd);
        addfd(epollfd,udpfd);
    
        while(1)
        {
            int number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);//一直阻塞到有事件發生
            if (number < 0)
            {
                printf("epoll failure\n");
                break;
            }
    
            for (int i=0; i<number; i++)
            {
                int sockfd = events[i].data.fd;
                if (sockfd == listenfd)
                {
                    struct sockaddr_in client;
                    socklen_t client_addrlength = sizeof(client);
                    int connfd = accept(sockfd,(struct sockaddr*)&client,&client_addrlength);
                    addfd(epollfd,connfd);
                }
                //udp socket有EPOLLIN事件應該是可讀
                else if (sockfd == udpfd)
                {
                    char buf[UDP_BUFFER_SIZE];
                    memset(buf,'\0',UDP_BUFFER_SIZE);
                    struct sockaddr_in client;
                    socklen_t client_addrlength = sizeof(client);
                    ret = recvfrom(sockfd,buf,UDP_BUFFER_SIZE-1,0,(struct sockaddr*)&client,&client_addrlength);
                    if (ret > 0)//echo服務
                        sendto(sockfd,buf,UDP_BUFFER_SIZE-1,0,(struct sockaddr*)&client,client_addrlength);
                }
                else if (events[i].events & EPOLLIN)
                {
                    //建立TCP連線的新加入的TCP SOCKET
                    char buf[TCP_BUFFER_SIZE];
                    //ET模式不重複觸發,迴圈讀取完畢
                    while(1)
                    {
                        memset(buf,'\0',TCP_BUFFER_SIZE);
                        ret = recv(sockfd,buf,TCP_BUFFER_SIZE-1,0);
                        if (ret < 0)
                        {
                            if (errno == EAGAIN || errno == EWOULDBLOCK)
                                break;//下次再處理
                            close(sockfd);//其他的error,
                            epoll_ctl(epollfd,EPOLL_CTL_DEL,sockfd,NULL);
                            break;
                        }
                        else if (ret == 0)
                        {
                            close(sockfd);
                            epoll_ctl(epollfd,EPOLL_CTL_DEL,sockfd,NULL);
                            break;
                        }
                        else {//正常接收,正常傳送
                            send(sockfd,buf,ret,0);
                        }
                    }
                }
                else {
                    printf("something else happened \n");
                }
            }
        }
    
        close(listenfd);
        return 0;
    }
    
  3. poll實現的聊天室server,

    #define _GNU_SOURCE
    #include "me.h"
    
    #define USER_LIMIT 50
    #define BUFFER_SIZE 64
    #define FD_LIMIT 65535
    
    //儲存client的資料
    typedef struct {
        struct sockaddr_in addr;
        char* write_buf;
        char buf[BUFFER_SIZE];
    }client_data;
    
    int setnonblocking(int fd)
    {
        int old_option = fcntl(fd,F_GETFL);
        int new_option = old_option | O_NONBLOCK;
        fcntl(fd,F_SETFL,new_option);
        return old_option;
    }
    
    int main(int argc,char* argv[])
    {
        if (argc <= 2)
            error_handle("argc <= 2")
        const char* ip = argv[1];
        int port = atoi(argv[2]);
    
        struct sockaddr_in addr;
        memset(&addr,0,sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET,ip,&addr.sin_addr);
    
        int listenfd = socket(AF_INET,SOCK_STREAM,0);
        assert(listenfd >= 0);
    
        int ret = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr));
        assert(ret != -1);
    
        ret = listen(listenfd,5);
        assert(ret != -1);
    
        client_data* users = malloc(sizeof(client_data) * FD_LIMIT);
        struct pollfd fds[USER_LIMIT + 1];//最多監聽USER_LIMIT個使用者
        int user_counter = 0;
        for (int i=0; i <= USER_LIMIT; i++)
        {
            fds[i].fd = -1;
            fds[i].events = 0;
        }
    
        //把listenfd首先加入到poll事件中
        fds[0].fd = listenfd;
        fds[0].events = POLLIN | POLLERR;
        fds[0].revents = 0;
        
        while(1)
        {
            ret = poll(fds,user_counter+1,-1);
            if (ret < 0)
            {
                printf("poll failure\n");
                break;
            }
            
            //poll會返回所有註冊的事件,要遍歷一次所有的事件
            for(int i=0; i<user_counter+1; i++)
            {
                //處理新連線
                if ((fds[i].fd == listenfd) && (fds[i].revents & POLLIN))
                {
                    struct sockaddr_in client;
                    socklen_t client_addrlength = sizeof(client);
                    int connfd = accept(listenfd,(struct sockaddr*)&client,&client_addrlength);
                    if (connfd < 0)
                    {
                        printf("errno is: %d\n",errno);
                        continue;
                    }
    
                    /*請求超過連線限制*/
                    if (user_counter >= USER_LIMIT)
                    {
                        const char* info = "too many users\n";
                        printf("%s",info);
                        send(connfd,info,strlen(info),0);
                        close(connfd);
                        continue;
                    }
                    
                    //儲存新連線connfd的資訊 
                    user_counter++;
                    users[connfd].addr = client;
                    setnonblocking(connfd);//設定連線sock是non block的
                    fds[user_counter].fd = connfd;
                    fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;
                    fds[user_counter].revents = 0;
                    printf("comes a new user,now have %d users\n",user_counter);
                }
                //fd發生error事件
                else if (fds[i].revents & POLLERR)
                {
                    printf("get an error from %d\n",fds[i].fd);
                    char errors[100];
                    memset(errors,'\0',sizeof(errors));
                    socklen_t length = sizeof(errors);
                    if (getsockopt(fds[i].fd,SOL_SOCKET,SO_ERROR,errors,&length) < 0)
                        printf("get socket option failed\n");
                    continue;
                }
                //客戶端關閉連線
                else if (fds[i].revents & POLLRDHUP)
                {
                    //客戶端資源 / 監聽事件 2者移動到i處
                    users[fds[i].fd] = users[fds[user_counter].fd];
                    fds[i] = fds[user_counter--]; 
                    close(fds[i--].fd);
                    printf("a client left\n");
                }
                //客戶端發來資訊
                else if (fds[i].revents & POLLIN)
                {
                    int connfd = fds[i].fd;
                    memset(users[connfd].buf,'\0',BUFFER_SIZE);
                    ret = recv(connfd,users[connfd].buf,BUFFER_SIZE-1,0);
                    printf("get %d bytes of client data %s from %d \n",ret,users[connfd].buf,connfd);
                    if (ret < 0)
                    {
                        if (errno != EAGAIN)//如果是其他錯誤,則關閉此連線,釋放監聽事件
                        {
                            close(connfd);
                            users[fds[i].fd] = users[fds[user_counter].fd];
                            fds[i--] = fds[user_counter--];
                        }
                    }
                    else if (ret == 0)
                    {}
                    else {//接收到客戶端發來的資料,
                        for (int j=1; j<=user_counter; j++)
                        {
                            if (fds[j].fd == connfd)//不需要將資訊傳送給自己
                                continue;
                            // fds[j].events |= ~POLLIN;
                            fds[j].events &= ~POLLIN;//取消原POLLIN事件
                            fds[j].events |= POLLOUT;//在有接受資料的fd上註冊POLLOUT事件
                            users[fds[j].fd].write_buf = users[connfd].buf;
                        }
                    }
                }
                //傳送待傳送的資料
                else if (fds[i].revents & POLLOUT)
                {
                    int connfd = fds[i].fd;
                    if (!users[connfd].write_buf)
                        continue;
                    ret = send(connfd,users[connfd].write_buf,strlen(users[connfd].write_buf),0);
                    users[connfd].write_buf = NULL;//清除帶傳送資料
                    // fds[i].events |= ~POLLOUT;
                    fds[i].events &= ~POLLOUT;//取消原POLLOUT事件
                    fds[i].events |= POLLIN;
                }
            }
        }
    
        free(users);
        close(listenfd);
        return 0;
    }
    

    可以用下面的client來測試聊天室的效果:

    #include <me.h>
    #define BUF_SIZE 100
    #define NAME_SIZE 20
    
    void *send_msg(void*);
    void *recv_msg(void*);
    
    char name[NAME_SIZE] = "[DEFAULT]";
    char msg[BUF_SIZE];
    
    int main(int argc,char *argv[])
    {
      int sock;
      struct sockaddr_in serv_adr;
      pthread_t snd_thread,rcv_thread;
      void *thread_return;
      if (argc != 4)
      {
        printf("Usage : %s <IP> <port> <name>\n",argv[0]);
        exit(1);
      }
    
      sprintf(name,"[%s]",argv[3]);//將名字加入到待傳送的buf中 
      sock = socket(AF_INET,SOCK_STREAM,0);
    
      memset(&serv_adr,0,sizeof(serv_adr));
      serv_adr.sin_family = AF_INET;
      serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
      serv_adr.sin_port = htons(atoi(argv[2]));
    
      if (connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1)
        error_handle("connect() error")
    
      //建立傳送和接受訊息的執行緒 
      pthread_create(&snd_thread,NULL,send_msg,(void*)&sock);
      pthread_create(&rcv_thread,NULL,recv_msg,(void*)&sock);
    
      pthread_join(snd_thread,&thread_return);
      pthread_join(rcv_thread,&thread_return);
      close(sock);
      return 0;
    }
    
    void* send_msg(void *arg)
    {
      int sock = *((int *)arg);
      char name_msg[NAME_SIZE + BUF_SIZE];
      while(1)
      {
        fgets(msg,BUF_SIZE,stdin);
        if (!strcmp(msg,"q\n") || !strcmp(msg,"Q\n"))
        {
          close(sock);
          exit(0);
        }
        sprintf(name_msg,"%s %s",name,msg);
        write(sock,name_msg,strlen(name_msg));
      }
      return NULL;
    }
    
    void* recv_msg(void *arg)
    {
      int sock = *((int*)arg);
      char name_msg[NAME_SIZE + BUF_SIZE];
      int str_len;
      while(1)//伺服器傳送訊息到所有客戶端 
      {
        str_len = read(sock,name_msg,NAME_SIZE+BUF_SIZE-1);
        if (str_len == -1)
          return (void*)-1;
        name_msg[str_len] = 0;
        fputs(name_msg,stdout); //接受訊息,列印到客戶端 
      }
      return NULL;
    }
    

將訊號通過全雙工pipe轉化為IO事件 然後 由IO多路統一處理訊號和IO事件

利用signal的SA_RESTART flags,系統呼叫被訊號處理函式中斷後能夠重新被呼叫

這裡僅修改了4個訊號的預設處理函式,

通過kill -signo pid測試是否成功轉換為IO事件

#include "me.h"
#define MAX_EVENT_NUMBER 1024

int pipefd[2];
int setnonblocking(int fd)
{
    int old_option = fcntl(fd,F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
    return old_option;
}

void addfd(int epollfd,int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;//邊沿觸發模式
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    setnonblocking(fd);//邊沿模式下,fd需要nonblock,避免觸發後一直被block
}

//訊號處理函式
void sig_handler(int signo)
{
    int save_errno = errno;
    int msg = signo;
    send(pipefd[1],(char*)&msg,1,0);//1Byte大小足夠表示所有訊號了
    errno = save_errno;
}

//註冊訊號signo的處理函式
void addsig(int signo)
{
    struct sigaction sa;
    memset(&sa,0,sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;//重啟被中斷的系統呼叫
    sigfillset(&sa.sa_mask);
    assert(sigaction(signo,&sa,NULL) != -1);
}

int main(int argc,char* argv[])
{
    if (argc <= 2)
        error_handle("argc <= 2")
    printf("pid = %d\n",getpid());
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET,ip,&addr.sin_addr);

    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    assert(listenfd >= 0);

    int ret = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr)); 
    if (ret == -1)
    {
        printf("errno is %d\n",errno);
        return 1;
    }
    ret = listen(listenfd,5);
    assert(ret != -1);

    struct epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    addfd(epollfd,listenfd);

    //全雙工,本地,unnamed socket
    ret = socketpair(AF_UNIX,SOCK_STREAM,0,pipefd);
    assert(ret != -1);
    setnonblocking(pipefd[1]);
    addfd(epollfd,pipefd[0]);

    addsig(SIGHUP);
    addsig(SIGCHLD);
    addsig(SIGTERM);
    addsig(SIGINT);
    bool stop_server = false;

    while(!stop_server)
    {
        int number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
        //函式執行錯誤的原因不是被訊號處理程式中斷了
        if ((number < 0) && (errno != EINTR))
        {
            printf("epoll failure\n");
            break;
        }

        for (int i=0; i<number; i++)
        {
            int sockfd = events[i].data.fd;
            if (sockfd == listenfd)
            {
                struct sockaddr_in client;
                socklen_t client_addrlength = sizeof(client);
                int connfd = accept(sockfd,(struct sockaddr*)&client,&client_addrlength);
                addfd(epollfd,connfd);
            }
            //本地socket有EPOLLIN事件發生
            else if((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) 
            {
                int signo;
                char signals[1024];//給每個signal排隊
                ret = recv(pipefd[0],signals,sizeof(signals),0);
                if (ret == -1)
                    continue;
                else if (ret == 0)
                    continue;
                else 
                {
                    //接收到由ret個訊號處理程式發過來的signo
                    for (int i=0; i<ret; i++)
                    {
                        switch(signals[i])
                        {
                        case SIGCHLD:
                        case SIGHUP:
                            printf("receive SIGCHLD / SIGHUP\n");
                            continue;
                        case SIGTERM:
                        case SIGINT:
                            stop_server = true;
                        }
                    }
                }
            }
            else{}
        }
    }
    printf("close fds\n");
    close(listenfd);
    close(pipefd[0]);
    close(pipefd[1]);
    return 0;
}

相關文章