久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

accept非阻塞方式

 熊掌大俠 2017-01-13

 C網(wǎng)絡(luò)編程:Server處理多個Client(多進(jìn)程server方法 和 non-blocking與select結(jié)合)

參看基于TCP/UDP的socket代碼,,同一時間Server只能處理一個Client請求:在使用當(dāng)前連接的socket和client進(jìn)行交互的時候,,不能夠accept新的連接請求。為了使Server能夠處理多個Client請求,常見的方法:

多進(jìn)程方法(每個子進(jìn)程單獨(dú)處理一個client連接)
在每個accept成功之后,使用fork創(chuàng)建一個子進(jìn)程專門處理該client的connection,,父進(jìn)程(server)本身可以繼續(xù)accept其他新的client的連接請求。具體如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>

    #include <signal.h>
    #include <sys/wait.h>

    #define DEFAULT_PORT    1984    //默認(rèn)端口    #define BUFFER_SIZE       1024    //buffer大小

    void sigCatcher(int n) {
        //printf("a child process dies\n");
        while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    int clientProcess(int new_sock);

    int main(int argc, char *argv[]) {
        unsigned short int port;

        //get port, use default if not set
        if (argc == 2) {
        port = atoi(argv[1]);
        } else if (argc < 2) {
        port = DEFAULT_PORT;
        } else {
        fprintf(stderr, "USAGE: %s [port]\n", argv[0]);
        return 1;
        }

        //create socket
        int sock;
        if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {
        perror("socket failed, ");
        return 1;
        }
        printf("socket done\n");

        //create socket address and initialize
        struct sockaddr_in bind_addr;
        memset(&bind_addr, 0, sizeof(bind_addr));
        bind_addr.sin_family = AF_INET;
        bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //設(shè)置接受任意地址
        bind_addr.sin_port = htons(port);               //將host byte order轉(zhuǎn)換為network byte order

        //bind (bind socket to the created socket address)
        if ( bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1 ) {
        perror("bind failed, ");
        return 1;
        }
        printf("bind done\n");

        //listen
        if ( listen(sock, 5) == -1) {
        perror("listen failed.");
        return 1;
        }
        printf("listen done\n");

        //handler to clear zombie process
        signal(SIGCHLD, sigCatcher);

        //loop and respond to client
        int new_sock;
        int pid;
        while (1) {
        //wait for a connection, then accept it
        if ( (new_sock = accept(sock, NULL, NULL)) == -1 ) {
            perror("accept failed.");
            return 1;
        }
        printf("accept done\n");

            pid = fork();
            if (pid < 0) {
                perror("fork failed");
                return 1;
            } else if (pid == 0) {
                //這里是子進(jìn)程
                close(sock);            //子進(jìn)程中不需要server的sock
                clientProcess(new_sock);    //使用新的new_sock和client進(jìn)行交互
                close(new_sock);        //關(guān)閉client的連接
                exit(EXIT_SUCCESS);     //子進(jìn)程退出
            } else {
                //這里是父進(jìn)程
                close(new_sock); //由于new_sock已經(jīng)交給子進(jìn)程處理,,這里可以關(guān)閉了
            }
        }
        return 0;
    }

    int clientProcess(int new_sock) {
        int recv_size;
        char buffer[BUFFER_SIZE];

        memset(buffer, 0, BUFFER_SIZE);
        if ( (recv_size = recv(new_sock, buffer, sizeof(buffer), 0)) == -1) {
            perror("recv failed");
            return 1;
        }
        printf("%s\n", buffer);

        char *response = "This is the response";
        if ( send(new_sock, response, strlen(response) + 1, 0) == -1 ) {
            perror("send failed");
            return 1;
        }
        return 0;
    }
其中的signal(SIGCHLD, sigCatcher)代碼為了處理zombie process問題:當(dāng)server進(jìn)程運(yùn)行時間較長,,且產(chǎn)生越來越多的子進(jìn)程,當(dāng)這些子進(jìn)程運(yùn)行結(jié)束都會成為zombie process,,占據(jù)系統(tǒng)的process table,。解決方法是在父進(jìn)程(server進(jìn)程)中顯式地處理子進(jìn)程結(jié)束之后發(fā)出的SIGCHLD信號:調(diào)用wait/waitpid清理子進(jìn)程的zombie信息。

測試:運(yùn)行server程序,,然后同時運(yùn)行2個client(telnet localhost 1984),,可看到該server能夠很好地處理2個client。

多進(jìn)程方法的優(yōu)點(diǎn): 
每個獨(dú)立進(jìn)程處理一個獨(dú)立的client,,對server進(jìn)程來說只需要accept新的連接,,對每個子進(jìn)程來說只需要處理自己的client即可,。

多進(jìn)程方法的缺點(diǎn): 
子進(jìn)程的創(chuàng)建需要獨(dú)立的父進(jìn)程資源副本,開銷較大,,對高并發(fā)的請求不太適合,;且一個進(jìn)程僅處理一個client不能有效發(fā)揮作用。另外有些情況下還需要進(jìn)程間進(jìn)行通信以協(xié)調(diào)各進(jìn)程要完成的任務(wù),。

使用select實(shí)現(xiàn)non-blocking socket(single process concurrent server)
blocking socket VS non-blocking socket
默認(rèn)情況下socket是blocking的,即函數(shù)accept(), recv/recvfrom, send/sendto,,connect等,,需等待函數(shù)執(zhí)行結(jié)束之后才能夠返回(此時操作系統(tǒng)切換到其他進(jìn)程執(zhí)行)。accpet()等待到有client連接請求并接受成功之后,,recv/recvfrom需要讀取完client發(fā)送的數(shù)據(jù)之后才能夠返回,。

可設(shè)置socket為non-blocking模式,即調(diào)用函數(shù)立即返回,,而不是必須等待滿足一定條件才返回,。參看http://www./rpg/socktut/nonblocking.html

    non-blocking: by default, sockets are blocking - this means that they stop the function from returning until all data has been transfered. 
    With multiple connections which may or may not be transmitting data to a server, this would not be very good as connections may have to wait to transmit their data.
設(shè)置socket為非阻塞non-blocking
使用socket()創(chuàng)建的socket(file descriptor),默認(rèn)是阻塞的(blocking),;使用函數(shù)fcntl()(file control)可設(shè)置創(chuàng)建的socket為非阻塞的non-blocking,。

    #include <unistd.h>
    #include <fcntl.h>

    sock = socket(PF_INET, SOCK_STREAM, 0);

    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK); 
這樣使用原本blocking的各種函數(shù),可以立即獲得返回結(jié)果,。通過判斷返回的errno了解狀態(tài):

accept(): 
在non-blocking模式下,,如果返回值為-1,且errno == EAGAIN或errno == EWOULDBLOCK表示no connections沒有新連接請求,;

recv()/recvfrom(): 
在non-blocking模式下,,如果返回值為-1,且errno == EAGAIN表示沒有可接受的數(shù)據(jù)或很在接受尚未完成,;

send()/sendto(): 
在non-blocking模式下,,如果返回值為-1,且errno == EAGAIN或errno == EWOULDBLOCK表示沒有可發(fā)送數(shù)據(jù)或數(shù)據(jù)發(fā)送正在進(jìn)行沒有完成,。

read/write: 
在non-blocking模式下,,如果返回-1,且errno == EAGAIN表示沒有可讀寫數(shù)據(jù)或可讀寫正在進(jìn)行尚未完成,。

connect(): 
在non-bloking模式下,,如果返回-1,且errno = EINPROGRESS表示正在連接,。

使用如上方法,,可以創(chuàng)建一個non-blocking的server的程序,類似如下代碼:

    int main(int argc, char *argv[]) {

        int sock;
        if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {
            perror("socket failed");
            return 1;
        }

        //set socket to be non-blocking
        int flags = fcntl(sock, F_GETFL, 0);
        fcntl(sock, F_SETFL, flags | O_NONBLOCK);

        //create socket address to bind
        struct sockaddr_in bind_addr
        ...

        //bind
        bind(...)
        ...

        //listen
        listen(...)
        ...

        //loop 
        int new_sock;
        while (1) {
            new_sock = accept(sock, NULL, NULL);
            if (new_sock == -1 && errno == EAGAIN) {
                fprintf(stderr, "no client connections yet\n");
                continue;
            } else if (new_sock == -1) {
                perror("accept failed");
                return 1;
            }

            //read and write
            ...

        }  

        ...
    }
純non-blocking程序缺點(diǎn):如果運(yùn)行如上程序會發(fā)現(xiàn)調(diào)用accept可以理解返回,,但這樣會耗費(fèi)大量的CPU time,,實(shí)際中并不會這樣使用,。實(shí)際中將non-blocking和select結(jié)合使用。

non-blocking和select結(jié)合使用
select通過輪詢,,監(jiān)視指定file descriptor(包括socket)的變化,,知道:哪些ready for reading, 哪些ready for writing,哪些發(fā)生了錯誤等,。select和non-blocking結(jié)合使用可很好地實(shí)現(xiàn)socket的多client同步通信,。

select函數(shù):

    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>

    int select(int maxfd, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

    //maxfd: 所有set中最大的file descriptor + 1
    //readfds: 指定要偵聽ready to read的file descriptor,可以為NULL
    //writefds: 指定要偵聽ready to write的file descriptor,,可以為NULL
    //errorfds: 指定要偵聽errors的file descriptor,,可以為NULL
    //timeout: 指定偵聽到期的時間長度,如果該struct timeval的各個域都為0,,則相當(dāng)于完全的non-blocking模式,;如果該參數(shù)為NULL,相當(dāng)于block模式,;

    //select返回total number of bits set in readfds, writefds and errorfds,,當(dāng)timeout的時候返回0,發(fā)生錯誤返回-1,。
    //另外select會更新readfds(保存ready to read的file descriptor), writefds(保存read to write的fd), errorfds(保存error的fd),,且更新timeout為距離超時時刻的剩余時間。
另外,,fd_set類型需要使用如下4個宏進(jìn)行賦值:

    FD_ZERO(fd_set *set);       //Clear all entries from the set.
    FD_SET(int fd, fd_set *set);    //Add fd to the set.
    FD_CLR(int fd, fd_set *set);    //Remove fd from the set.
    FD_ISSET(int fd, fd_set *set);  //Return true if fd is in the set.
因此通過如下代碼可以將要偵聽的file descriptor/socket添加到響應(yīng)的fd_set中,,例如:

    fd_set readfds;
    FD_ZERO(&readfds);

    int sock;
    sock = socket(PF_INET, SOCK_STREAM, 0);

    FD_SET(sock, &readfds);     //將新創(chuàng)建的socket添加到readfds中
    FD_SET(stdin, &readfds);    //將stdin添加到readfds中
struct timeval類型:

    struct timeval {
        int tv_sec;     //seconds
        int tv_usec;    //microseconds,注意這里是微秒不是毫秒,,1秒 = 1000, 000微秒
    }; 
因此,,使用select函數(shù)可以添加希望偵聽的file descriptor/socket到read, write或error中(如果對某一項(xiàng)不感興趣,可以設(shè)置為NULL),,并設(shè)置每次偵聽的timeout時間,。

注意如果設(shè)置timeout為:

    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
相當(dāng)于每次select立即返回相當(dāng)于純non-blocking模式;

如果設(shè)置timeout參數(shù)為NULL,,則每次select持續(xù)等待到有變化則相當(dāng)于blocking模式,。

使用select和non-blocking實(shí)現(xiàn)server處理多client實(shí)例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/time.h>

    #define DEFAULT_PORT    1984    //默認(rèn)端口
    #define BUFF_SIZE       1024    //buffer大小
    #define SELECT_TIMEOUT  5       //select的timeout seconds

    //函數(shù):設(shè)置sock為non-blocking mode
    void setSockNonBlock(int sock) {
        int flags;
        flags = fcntl(sock, F_GETFL, 0);
        if (flags < 0) {
            perror("fcntl(F_GETFL) failed");
            exit(EXIT_FAILURE);
        }
        if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
            perror("fcntl(F_SETFL) failed");
            exit(EXIT_FAILURE);
        }
    }
    //函數(shù):更新maxfd
    int updateMaxfd(fd_set fds, int maxfd) {
        int i;
        int new_maxfd = 0;
        for (i = 0; i <= maxfd; i++) {
            if (FD_ISSET(i, &fds) && i > new_maxfd) {
                new_maxfd = i;
            }
        }
        return new_maxfd;
    }

    int main(int argc, char *argv[]) {
        unsigned short int port;

        //獲取自定義端口
        if (argc == 2) {
            port = atoi(argv[1]);
        } else if (argc < 2) {
            port = DEFAULT_PORT;
        } else {
            fprintf(stderr, "USAGE: %s [port]\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        //創(chuàng)建socket
        int sock;
        if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {
            perror("socket failed, ");
            exit(EXIT_FAILURE);
        }
        printf("socket done\n");

        //in case of 'address already in use' error message
        int yes = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) {
            perror("setsockopt failed");
            exit(EXIT_FAILURE);
        }

        //設(shè)置sock為non-blocking
        setSockNonBlock(sock);

        //創(chuàng)建要bind的socket address
        struct sockaddr_in bind_addr;
        memset(&bind_addr, 0, sizeof(bind_addr));
        bind_addr.sin_family = AF_INET;
        bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //設(shè)置接受任意地址
        bind_addr.sin_port = htons(port);               //將host byte order轉(zhuǎn)換為network byte order

        //bind sock到創(chuàng)建的socket address上
        if ( bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1 ) {
            perror("bind failed, ");
            exit(EXIT_FAILURE);
        }
        printf("bind done\n");

        //listen
        if ( listen(sock, 5) == -1) {
            perror("listen failed.");
            exit(EXIT_FAILURE);
        }
        printf("listen done\n");

        //創(chuàng)建并初始化select需要的參數(shù)(這里僅監(jiān)視read),并把sock添加到fd_set中
        fd_set readfds;
        fd_set readfds_bak; //backup for readfds(由于每次select之后會更新readfds,,因此需要backup)
        struct timeval timeout;
        int maxfd;
        maxfd = sock;
        FD_ZERO(&readfds);
        FD_ZERO(&readfds_bak);
        FD_SET(sock, &readfds_bak);

        //循環(huán)接受client請求
        int new_sock;
        struct sockaddr_in client_addr;
        socklen_t client_addr_len;
        char client_ip_str[INET_ADDRSTRLEN];
        int res;
        int i;
        char buffer[BUFF_SIZE];
        int recv_size;

        while (1) {

            //注意select之后readfds和timeout的值都會被修改,,因此每次都進(jìn)行重置
            readfds = readfds_bak;
            maxfd = updateMaxfd(readfds, maxfd);        //更新maxfd
            timeout.tv_sec = SELECT_TIMEOUT;
            timeout.tv_usec = 0;
            printf("selecting maxfd=%d\n", maxfd);

            //select(這里沒有設(shè)置writefds和errorfds,如有需要可以設(shè)置)
            res = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
            if (res == -1) {
                perror("select failed");
                exit(EXIT_FAILURE);
            } else if (res == 0) {
                fprintf(stderr, "no socket ready for read within %d secs\n", SELECT_TIMEOUT);
                continue;
            }

            //檢查每個socket,,并進(jìn)行讀(如果是sock則accept)
            for (i = 0; i <= maxfd; i++) {
                if (!FD_ISSET(i, &readfds)) {
                    continue;
                }
                //可讀的socket
                if ( i == sock) {
                    //當(dāng)前是server的socket,,不進(jìn)行讀寫而是accept新連接
                    client_addr_len = sizeof(client_addr);
                    new_sock = accept(sock, (struct sockaddr *) &client_addr, &client_addr_len);
                    if (new_sock == -1) {
                        perror("accept failed");
                        exit(EXIT_FAILURE);
                    }
                    if (!inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip_str, sizeof(client_ip_str))) {
                        perror("inet_ntop failed");
                        exit(EXIT_FAILURE);
                    }
                    printf("accept a client from: %s\n", client_ip_str);
                    //設(shè)置new_sock為non-blocking
                    setSockNonBlock(new_sock);
                    //把new_sock添加到select的偵聽中
                    if (new_sock > maxfd) {
                        maxfd = new_sock;
                    }
                    FD_SET(new_sock, &readfds_bak);
                } else {
                    //當(dāng)前是client連接的socket,可以寫(read from client)
                    memset(buffer, 0, sizeof(buffer));
                    if ( (recv_size = recv(i, buffer, sizeof(buffer), 0)) == -1 ) {
                        perror("recv failed");
                        exit(EXIT_FAILURE);
                    }
                    printf("recved from new_sock=%d : %s(%d length string)\n", i, buffer, recv_size);
                    //立即將收到的內(nèi)容寫回去,,并關(guān)閉連接
                    if ( send(i, buffer, recv_size, 0) == -1 ) {
                        perror("send failed");
                        exit(EXIT_FAILURE);
                    }
                    printf("send to new_sock=%d done\n", i);
                    if ( close(i) == -1 ) {
                        perror("close failed");
                        exit(EXIT_FAILURE);
                    }
                    printf("close new_sock=%d done\n", i);
                    //將當(dāng)前的socket從select的偵聽中移除
                    FD_CLR(i, &readfds_bak);
                }
            }
        }

        return 0;
    }
編譯并運(yùn)行如上程序,,然后嘗試使用多個telnet localhost 1984連接該server,。可以發(fā)現(xiàn)各個connection很好地獨(dú)立工作,。因此,,使用select可實(shí)現(xiàn)一個進(jìn)程盡最大所能地處理盡可能多的client。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多