(P13)socket編程(八)

1.物種I/O模型

  • (1)阻塞IO
    1)一遞交讀操作,讀操作就阻塞了,直到對方有數據到來;
    2)將套接口接收緩衝區拷貝到用戶空間緩衝區buff中,拷貝完成,recv函數就返回了;
    在這裏插入圖片描述
  • (2)非阻塞IO
    1)recv函數即使沒有數據到來,也不會阻塞,因爲把他設置爲了非阻塞模式了
    2)EMOULDBLOCK:改正爲:EWOULDBLOCK
    3)忙等待:想等待一定的數據,而這些數據又沒有到來,又需要佔用CPU時間片,這種等待稱之爲忙等待
    4)不推薦使用這種非阻塞IO模型,應用很窄
    5)將套接口設置非阻塞方式
fcntl(fd, F_SETFL, flag|O_NONBLOCK)

在這裏插入圖片描述

  • (3)I/O複用(select和poll)
    1)select管理多個文件描述符
    2)阻塞的位置提前到了select,一旦監測到事件,recv則不再阻塞了
    在這裏插入圖片描述

  • (4)信號驅動IO
    1)不經常用
    2)調用sigaction來安裝信號處理程序,安裝完畢後,程序可以做其它的事情
    3)當數據到來時,以信號的方式通知應用進程,應用進程要在信號處理程序中處理接收數據的細節,使得異步處理得到可能(因爲信號是異步處理的方式,當提交信號後,應用進程可以處理其它事情,直到有數據到來,通過信號處理程序來處理到來的事件)
    在這裏插入圖片描述

  • (5)異步IO
    1)效率最高
    2)會提交一個應用層的緩衝區buf,即使內核沒有數據,也會立刻返回,一旦返回,應用進程就處理其它的事情,達到了異步處理的能力;
    當有數據到來,內核會將數據自動拷貝到應用層buff緩衝區,一旦複製完成,會通過信號等方式來通知應用進程的程序,這取決於aio_read的實現(大多數的系統實現,或多或少會有些問題,與windows的完成端口IOCP基本一樣)
    3)異步IO與信號驅動IO的區別
    異步IO是推的機制,
    一旦內核接收到數據,會主動的將數據從內核返還給用戶的buf緩衝區,用戶空間沒有必要調用recv方法來接收他
    信號驅動IO是拉的機制。
    一旦有數據到來,僅僅是通過SIGIO信號通知應用進程,應用進程還得調用recv函數將數據從內核空間到用戶空間中
    拉比推,可能多一些延遲
    在這裏插入圖片描述

2.select

  • select
int select(int nfds, fd_set *readfds, fd_set *writefds,
                 fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);將文件描述符從集合set中移除;
int  FD_ISSET(int fd, fd_set *set);判斷fd是否在集合set中?
void FD_SET(int fd, fd_set *set);將fd添加到集合set中
void FD_ZERO(fd_set *set);清空fd集合


nfds:讀,寫,異常集合中的文件描述符的最大值+1

可讀集合:檢測到有數據可讀的套接口,放到readfds集合中,一旦有數據可讀,select就可以返回。
		IO中的緩衝區有數據了,就會產生可讀事件。
		若關心某個IO的可讀事件,就將其fd放到readfds集合中。
		是輸入輸出參數。

可寫集合:是輸入輸出參數。

異常集合:NULL;
		是輸入輸出參數。
		
超時時間:若有超時時間,在超時時間到來時,若沒有監測到任何一個事件也可以返回,返回事件個數爲0,select失敗返回-1NULL表示不會超時,表示一定要檢測到某個事件纔會返回
				是輸入輸出參數。
  • select可以管理多個文件描述符,也就是管理多個IO。
    eg:select會監測1,2,3,4,5。。等io,如果select監測到了其中的某些IO發生了事件,select就會立刻返回,並將感興趣的事件填充到readfds中,並且返回事件的個數,通過輪詢這些事件,一個個來處理,這時候處理基本不會阻塞了,因爲select提前阻塞了,他的返回意味着事件已經到來
  • select是個管理者,是個中心
    (1)用select來管理多個IO,一旦其中的一個I/O或者多個I/O檢測到我們所感興趣的事件,select函數會返回,返回值爲檢測到的事件的個數
    (2)並且返回哪些I/O發生了事件,遍歷這些事件,進而處理事件
    (3)與信號集的集合操作函數類似

3.用select改進回射客戶端程序

  • eg:客戶端:NetworkProgramming-master (1)\LinuxNetworkProgramming\P13echocli6.cpp
    服務端還是:NetworkProgramming-master (1)\LinuxNetworkProgramming\P11echo_srv.c
//
// Created by wangji on 19-8-6.
//

// socket編程 8 select模型

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>


using namespace std;

struct packet
{
    int len;
    char buf[1024];
};

#define ERR_EXIT(m) \
        do  \
        {   \
            perror(m);  \
            exit(EXIT_FAILURE); \
        } while(0);

ssize_t readn(int fd, void *buf, size_t count)
{
    size_t nleft = count;   // 剩餘字節數
    ssize_t nread;
    char *bufp = (char*) buf;

    while (nleft > 0)
    {
        nread = read(fd, bufp, nleft);
        if (nread < 0)
        {
            if (errno == EINTR)
            {
                continue;
            }
            return  -1;
        } else if (nread == 0)
        {
            return count - nleft;
        }

        bufp += nread;
        nleft -= nread;
    }
    return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten;
    char* bufp = (char*)buf;

    while (nleft > 0)
    {
        if ((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
            {
                continue;
            }
            return -1;
        }
        else if (nwritten == 0)
        {
            continue;
        }
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count;
}


ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while (1)
    {
        int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看傳入消息
        if (ret == -1 && errno == EINTR)
        {
            continue;
        }
        return ret;
    }
}

ssize_t readline(int sockfd, void *buf, size_t maxline)
{
    int ret;
    int nread;
    char *bufp = (char*)buf;    // 當前指針位置
    int nleft = maxline;
    while (1)
    {
        ret = recv_peek(sockfd, buf, nleft);
        if (ret < 0)
        {
            return ret;
        }
        else if (ret == 0)
        {
            return ret;
        }
        nread = ret;
        int i;
        for (i = 0; i < nread; i++)
        {
            if (bufp[i] == '\n')
            {
                ret = readn(sockfd, bufp, i+1);
                if (ret != i+1)
                {
                    exit(EXIT_FAILURE);
                }
                return ret;
            }
        }
        if (nread > nleft)
        {
            exit(EXIT_FAILURE);
        }
        nleft -= nread;
        ret = readn(sockfd, bufp, nread);
        if (ret != nread)
        {
            exit(EXIT_FAILURE);
        }
        bufp += nread;
    }
    return -1;
}

void ehco_cli(int sockfd)
{
//    char recvbuf[1024];
//    char sendbuf[1024];
//    // struct packet recvbuf;
//    // struct packet sendbuf;
//    memset(recvbuf, 0, sizeof recvbuf);
//    memset(sendbuf, 0, sizeof sendbuf);
//    int n = 0;
//    while (fgets(sendbuf, sizeof sendbuf, stdin) != NULL)   // 鍵盤輸入獲取
//    {
//        writen(sockfd, sendbuf, strlen(sendbuf)); // 寫入服務器
//
//        int ret = readline(sockfd, recvbuf, sizeof recvbuf);    // 服務器讀取
//        if (ret == -1)
//        {
//            ERR_EXIT("readline");
//        }
//        if (ret == 0)
//        {
//            printf("server close\n");
//            break;
//        }
//
//        fputs(recvbuf, stdout); // 服務器返回數據輸出
//
//        // 清空
//        memset(recvbuf, 0, sizeof recvbuf);
//        memset(sendbuf, 0, sizeof sendbuf);
//    }
    fd_set rset;
    FD_ZERO(&rset);

    int nready;//檢測到的事件個數
    int maxfd;
    int fd_stdin = fileno(stdin);//fileno:獲取標準輸入的文件描述符
    if (fd_stdin > sockfd)
    {
        maxfd = fd_stdin;
    } else {
        maxfd = sockfd;
    }

    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};
	
	//標準輸入IO和網絡IO
    while (1)//檢測套接口是否有可讀事件,檢測標準輸入是否有可讀事件
    {
        FD_SET(fd_stdin, &rset);//將fd_stdin放到讀的集合rset中,關心fd_stdin文件描述符的事件
        FD_SET(sockfd, &rset);//將sockfd放到讀的集合rset中
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);//讀集合中最大文件描述+1
        //到底是哪一個套接口檢測到事件?rset集合是會改變的!!返回的是哪一些io或者套接口檢測到了事件
        //說明rset是輸入輸出參數
        if (nready == -1)
        {
            ERR_EXIT("select");
        }
        if (nready == 0)
        {
            continue;
        }
        
        //判斷標準輸入產生的可讀事件,還是套接口產生的可讀事件?
        if (FD_ISSET(sockfd, &rset))    // 套接口產生的可讀事件
        {
            int ret = readline(sockfd, recvbuf, sizeof(recvbuf));    // 服務器讀取
            if (ret == -1)
            {
                ERR_EXIT("readline");
            }
            if (ret == 0)
            {
                printf("server close\n");
                break;
            }

            fputs(recvbuf, stdout); // 服務器返回數據輸出
            memset(recvbuf, 0, sizeof(recvbuf));
        }

        if (FD_ISSET(fd_stdin, &rset))//判斷標準輸入產生的可讀事件
        {
            if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)   //等於NULL,說明客戶端已經得到一個EOF結束符
            {
                break;
            }
            writen(sockfd, sendbuf, strlen(sendbuf)); // 寫入服務器
            memset(sendbuf, 0, sizeof(sendbuf));
        }
    }
    close(sockfd);
};

void handle_sigchld(int sig)
{
    // wait(NULL);
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main(int argc, char** argv) {
    // signal(SIGCHLD, SIG_IGN);
    signal(SIGCHLD, handle_sigchld);
    // 1. 創建套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        ERR_EXIT("socket");
    }

    // 2. 分配套接字地址
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof servaddr);
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    // servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // inet_aton("127.0.0.1", &servaddr.sin_addr);

    // 3. 請求鏈接
    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
        ERR_EXIT("connect");
    }

    struct sockaddr_in localaddr;
    socklen_t addrlen = sizeof localaddr;
    if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0)
    {
        ERR_EXIT("getsockname");
    }
    printf("id = %s, ", inet_ntoa(localaddr.sin_addr));
    printf("port = %d\n", ntohs(localaddr.sin_port));

    // 4. 數據交換
    ehco_cli(sockfd);

    // 5. 斷開連接
    close(sockfd);


    return 0;
}
  • 測試:
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    不再有FIN_WAIT2和CLOSE_WAIT狀態了,服務器段的FIN_WAIT狀態已經變成了TIME_WAIT狀態了,客戶端的CLOSE_WAIT狀態向前推進,變成了cloes的狀態,其結束了,就不再顯示出來了
    在這裏插入圖片描述
  • select優點
    本質上用單進程的方式處理IO比較方便,單進程可以輪詢事件,然後對這些事件進行處理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章