Linux select I/O 復用

来源:http://www.cnblogs.com/bymzy/archive/2017/02/12/6391066.html
-Advertisement-
Play Games

用途 在處理多個 socket 套接字的時候,會很自然的遇到一個問題:某個套接字什麼時候可讀?什麼時候可寫?哪些套接字是需要關閉的?我們可以回憶一下,一般我們在最開始編寫socket程式的時候,send,recv都是同步的,send完後就傻等著recv。這種模式的一個很大的問題是,recv會占用一整 ...


用途

在處理多個socket套接字的時候,會很自然的遇到一個問題:某個套接字什麼時候可讀?什麼時候可寫?哪些套接字是需要關閉的?我們可以回憶一下,一般我們在最開始編寫socket程式的時候,send,recv都是同步的,send完後就傻等著recv。這種模式的一個很大的問題是,recv會占用一整個線程,單個線程里沒法處理第二個socket。怎麼辦呢?加線程,每個socket分配一個線程?顯然不合適,1000個客戶端難道要1000個線程麽。select提供了一種方式同時監控多個套接字,執行過程大致為:首先將感興趣的套接字加入到select的集合中,select函數執行,監控這些個套接字,當其中有套接字產生了事件(可讀,可寫,異常)或者select超時後,select返回告知調用者,哪些個套接字發送了事件,調用者就對發生了事件的套接字挨個處理,然後繼續執行select函數,下個迴圈開始。
通過這樣的一種方式,在同一個線程裡面就實現了對多個套接字的讀寫操作。當然需要註意的是,select調用針對的是文件描述符,不管是socket,pipe,file都是可以被select監控的。

函數說明

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

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

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

當前linux下selet的每個fd_set最多可以監控1024個文件描述符

參數

  • nfds 要監聽的所有的文件描述符中最大值 + 1
  • readfds 要監聽其讀事件的文件描述符集合
  • writefds 要監聽其寫事件的文件描述符集合
  • exceptfds 要監聽其異常事件的文件描述符集合(比如socket的帶外數據就會引起exceptfds事件)
  • timeout 超時時間結構,指定為NULL表示一直阻塞,否則就阻塞指定的時間。

返回值

  • -1 出錯
  • = 超時
  • >0 某些套接字產生事件

使用

假設對於一個文件描述符fd,我們想監控其讀事件,就將加入到讀監控集合中。

fd_set read_set;
FD_ZERO(&read_set);
FD_SET(fd, &read_set);

select(maxFd +1, &read_set, NULL, NULL, NULL)

同理,對於寫事件,異常事件也是一樣的處理方式。

當select調用返回後,如果select返回>0 則可以對指定的文件描述符進行操作。

if (FD_ISSET(fd, &read_set)) {
    recv(fd)
}

在程式設計中,一般將select結合while使用。

參考

實例

使用select構建的一個tcp echo程式,程式可以接受多個客戶端的鏈接,並將客戶端發送過來的字元串發送回去。

測試

測試環境:CentOS7.1

server端截圖:

cleint1截圖:

cleint2截圖:

代碼

/* author: bymzy
 * 2017/2/12
 * */

#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>

#define PORT 3333
#define IP "127.0.0.1"
#define BACKLOG 5
#define BUFSIZE 1025

void SetFDSet(int *clientFd, int listenFd, fd_set *read, fd_set *except, int *maxFd)
{
    int i = listenFd + 1;
    FD_ZERO(read);
    FD_ZERO(except);

    for (;i <= *maxFd; ++i) {
        if (clientFd[i] != 0) {
            FD_SET(clientFd[i], read);
            FD_SET(clientFd[i], except);
        }
    }
}

void CheckFDSet(int *clientFd, int listenFd, fd_set *read, fd_set *except, int *maxFd)
{
    int i = listenFd + 1;
    int tempMaxFd = *maxFd;
    char buf[1024];
    int recved = 0;
    bool needClose = false;

    bzero(buf, 1024);
    for (;i <= tempMaxFd; ++i) {
        bzero(buf, recved);
        if (FD_ISSET(clientFd[i], read)) {
            recved = recv(clientFd[i], buf, BUFSIZE -1 , 0);
            if (recved <= 0) {
                perror("Recv error");
                close(clientFd[i]);
                if (i == tempMaxFd) {
                    *maxFd = -1;
                }
                clientFd[i] = 0;
                continue;
            }
            printf("Recv: %s \n", buf);
            send(clientFd[i], buf, recved, 0);
        }

        bzero(buf, recved);
        if (FD_ISSET(clientFd[i], except)) {
            recved = recv(clientFd[i], buf, BUFSIZE - 1, MSG_OOB);
            if (recved <= 0) {
                perror("Recv error");
                close(clientFd[i]);
                if (i == tempMaxFd) {
                    *maxFd = -1;
                }
                clientFd[i] = 0;
                continue;
            }
            printf("OOB: %s \n", buf);
            send(clientFd[i], buf, recved, MSG_OOB);
        }
    }
}

void ChooseMaxFd(int *clientFd, int listenFd, int total, int *maxFd)
{
    int i = listenFd;
    for (;i < total; ++i) {
        if(clientFd[i] != 0) {
            *maxFd = clientFd[i];
        }
    }
}

void CloseFd(int *clientFd, int listenFd, int total)
{
    int i = listenFd + 1;
    for (;i < total; ++i) {
        if(clientFd[i] != 0) {
            close(clientFd[i]);
            clientFd[i] = 0;
        }
    }

}

int main(int argc, char* argv[])
{
    int err = 0;
    int listenFd = -1;

    do {
        listenFd = socket(AF_INET, SOCK_STREAM, 0);
        if (listenFd < 0) {
            err = errno;
            perror("Create listen socket failed");
            break;
        }

        struct sockaddr_in bindaddr;
        bzero(&bindaddr, 0);
        bindaddr.sin_addr.s_addr = inet_addr(IP);
        bindaddr.sin_port = htons(PORT);
        bindaddr.sin_family = AF_INET;
        socklen_t socklen = sizeof(bindaddr);
        int reuse = 1;

        setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

        err = bind(listenFd, (sockaddr*)&bindaddr, socklen);
        if (err != 0) {
            err = errno;
            perror("Listen socket bind failed!");
            break;
        }
        
        err = listen(listenFd, BACKLOG);
        if (err != 0) {
            err = errno;
            perror("Listen socket listen failed!");
            break;
        }

        /* use select read data */
        fd_set read_set;
        fd_set exception_set;
        struct timeval tv;
        tv.tv_sec = 5;
        tv.tv_usec = 0;

        int *clientFd = (int*)malloc(sizeof(int) * (FD_SETSIZE + listenFd + 1));
        clientFd[listenFd] = listenFd;
        int maxFd = -1;
        while (1) {
            if (maxFd == -1) {
                ChooseMaxFd(clientFd, listenFd, FD_SETSIZE + listenFd + 1, &maxFd);
            }

            SetFDSet(clientFd, listenFd, &read_set, &exception_set, &maxFd);

            if (maxFd != (FD_SETSIZE + listenFd)) {
                FD_SET(listenFd, &read_set);
            }

            tv.tv_sec = 5;
            tv.tv_usec = 0;

            err = select(maxFd + 1, &read_set, NULL, &exception_set, &tv);
            if (err < 0) {
                perror("Select failed");
                err = errno;
                break;
            } else if (err == 0) {
                printf("Select timeout!\n");
            } else {
                if (FD_ISSET(listenFd, &read_set)) {
                    struct sockaddr_in clientaddr;
                    socklen_t clientlen;
                    int tempFd= -1;
                    tempFd = accept(listenFd, (sockaddr*)&clientaddr, &clientlen);
                    if (tempFd < 0) {
                        err = errno;
                        perror("accept failed!");
                        break;
                    }

                    clientFd[tempFd] = tempFd;
                    if (tempFd > maxFd) {
                        maxFd = tempFd;
                    }
                    printf("Accept client fd: %d\n", tempFd);
                }

                CheckFDSet(clientFd, listenFd, &read_set, &exception_set, &maxFd);
            }
        }

        CloseFd(clientFd, listenFd, FD_SETSIZE + listenFd + 1);
        free(clientFd);

    } while(0);

    if (listenFd != -1) {
        close(listenFd);
        listenFd = -1;
    }

    return err;
}




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Java中的內部類 小記:內部類可以作為其他類的成員,而且可訪問它所在類的成員。 ...
  • 最近開始看Spring Boot,發現其開發起來真是方便。今天就來實現一個簡單的Spring MVC 請求,純Java代碼的哦。 1、Maven必不可少,先看看都載入了那些依賴: 2、Controller 3、頁面,放在src/main/resources/templates/目錄下,方便解析 4、 ...
  • 浮點數會有精度損失這個在上大學的時候就已經被告知,但是至今完全沒有想明白其中的原由,老師講的時候也是一筆帶過的,自己也沒有好好琢磨。終於在工作的時候碰到了,於是google了一番。 問題: 對兩個double類型的值進行運算,有時會出現結果值異常的問題。比如: 輸出: 39.989999999999 ...
  • 最近一直在搞郵件這塊,本來我們郵件發送是用的騰訊免費的企業郵箱,郵件功能沒有問題,但是由於郵件的限制,如下: 這些限制導致我們的部分客戶是收不到郵件的,哪怕付費,這樣的固定頻率限制也是無法解決的,可以說我們國內的郵件廠商都是這樣,而國外的卻要收費。 那麼問題來了,如何突破發送郵件的頻率限制? 1. ...
  • 當JSTL標簽庫已經無法滿足我們的需求時候,就需要自己開發自定義標簽,來滿足我們的需求,自定義標簽實際上是一個普通的java類,繼承SimpleTagSupport類。 做類。派生自SimpleTagSupport,重寫doTag()方法。getJspBody(),getJspContext(),i ...
  • 理解volatile 平時工作中對於多線程的應用並不太多,但是不能說工作中不應用就可以對此不去瞭解,至少要做的知道有這麼個東西,主要是作什麼的,這樣有助於看其它人寫的代碼。提到這個volatile,一般都會想到併發,同步,鎖之類,但要想搞清楚需要看看下麵一些知識。 處理器,高速緩存,主記憶體之間的關係 ...
  • Java 生成驗證碼的流程是: 收到請求 生成驗證碼所用的隨機數 使用隨機數寫出圖片 將隨機數記錄到Session中 輸出驗證碼 Java 驗證驗證碼的流程是: 收到請求 獲取用戶傳過來的驗證碼數字 驗證是否正確 輸出驗證結果 下麵通過一個例子來展示驗證碼的生成流程,該例子使用基本Java Spri ...
  • 如果計算一個表達式,比如 4+5+6*2,隨著計算器的不同,簡單的四功能計算器是30,許多科學計算器知道乘法的優先順序高於加法,所以科學答案是21。典型計算順序可以是計算4+5,存為臨時變數a,再計算6*2,存為b,最後計算a+b可得出最後結果。這種操作順序如下:45+62*+ 這種記法就是尾碼表達式 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...