linux select 學習


一、select介紹

函數原型:

#include <sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
// 返回值:准備就緒的描述符數,若超時返回0,若出錯返回-1

參數說明:

maxfd:是需要監視的最大的文件描述符值+1;readfds/writefds/exceptfds分別對應需要檢測的可讀文件描述符的集合、可寫描述符集合以及異常描述符集合。

tvptr:說明願意等待多久。

有三種情況:

tvptr=NULL 永遠等待。

tvptr->tv_sec==0 && tvptr->tv_usec=0 完全不等待。

tvptr->tv_sec!=0 || tvptr->tv_usec != 0 等待指定時間。若超時,則返回0。

POSIX允許實現中修改tvptr的值,所以在每次select開始時都需要重新設置該值。

對fd_set類型的處理有四個專有函數:

#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset); // 測試某一個描述符 返回值:若fd在描述符集中則返回非0,否則返回0
void FD_CLR(int fd, fd_set *fdset); // 刪除某一個描述符
void FD_SET(int fd, fd_set *fdset); // 設置某一個描述符
void FD_ZERO(fd_sete *fdset); // 清空

返回值:

select有三個可能的返回值:

返回-1表示出錯。

返回0表示沒有描述符准備好。

返回正值表示已經准備好的描述符數。該值是三個描述符集中已准備好的描述符的和。

select函數的中間三個參數的任意一個或全部都可以為NULL。如果三個都是NULL,則select提供一個高精度的計時器。

二、select使用

示例1:回顯服務器

/*******************************************************************************
* File Name        : select.cpp
* Author        : zjw
* Email            : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode)
* Create Time    : 2015年07月15日 星期三 11時52分07秒
*******************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;

const int SERVER_PORT = 8080;
const int FD_SIZE = 1024;
const int RECV_SIZE = 1024;
const int SEND_SIZE = 1040;

int main(int argc, char **argv)
{
    int server = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr_server;
    addr_server.sin_family = AF_INET;
    addr_server.sin_port = htons(SERVER_PORT);
    addr_server.sin_addr.s_addr = INADDR_ANY;
    memset(addr_server.sin_zero, 0, sizeof(addr_server.sin_zero));

    if (bind(server, (struct sockaddr*)&addr_server, sizeof(addr_server)))
    {
        perror("bind failed!");
        return -1;
    }

    listen(server, 5);

    fd_set fdsr;
    int fd[FD_SIZE] = { 0 };
    int maxsock = server;
    char recvBuf[RECV_SIZE + 1];
    char sendBuf[SEND_SIZE + 1];
    map<int, sockaddr_in> mapClients;

    while (1)
    {
        FD_ZERO(&fdsr);
        FD_SET(server, &fdsr);

        struct timeval tv;
        tv.tv_sec = 5;
        tv.tv_usec = 0;
    
        for (int i = 0; i < 1024; i++)
        {
            if (fd[i] != 0)
            {
                FD_SET(fd[i], &fdsr);
            }
        }

        int ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
        if (ret == -1)
        {
            perror("select error!");
            return -1;
        }
        else if (ret == 0)
        {
            cout << "timeout!" << endl;
            continue;
        }
        
        // check every fd in the fdset
        for (int i = 0; i < FD_SIZE; i++)
        {
            if (FD_ISSET(fd[i], &fdsr))
            {
                memset(recvBuf, 0, RECV_SIZE + 1);
                memset(sendBuf, 0, SEND_SIZE + 1);
                ret = recv(fd[i], recvBuf, RECV_SIZE, 0);
                sprintf(sendBuf, "Your said:%s", recvBuf);
                send(fd[i], sendBuf, SEND_SIZE, 0);
                if (ret <= 0)
                {
                    cout << "client " << fd[i] << " close" << endl;
                    mapClients.erase(fd[i]);
                    close(fd[i]);
                    FD_CLR(fd[i], &fdsr);
                    fd[i] = 0;
                }
                else
                {
                    cout << "client " << fd[i] << " ip[" << inet_ntoa(mapClients[fd[i]].sin_addr) << "]" << " said:" << recvBuf << endl;
                }
            }
        }

        // check server socket
        if (FD_ISSET(server, &fdsr))
        {
            int client;
            sockaddr_in addr_client;
            socklen_t len;
            client = accept(server, (struct sockaddr*)&addr_client, &len);
            if (client == -1)
            {
                perror("accept error!");
                return -1;
            }

            for (int i = 0; i < FD_SIZE; i++)
            {
                if (fd[i] == 0)
                {
                    fd[i] = client;
                    mapClients.insert(make_pair<int, sockaddr_in>(client, addr_client));
                    break;
                }
            }
        }
        maxsock = server;
        for (int i = 0; i < FD_SIZE; i++)
        {
            if (fd[i] > maxsock)
            {
                maxsock = fd[i];
            }
        }
    }
    
    return 0;
}

Makefile:

echo: select.cpp
    g++ -o $@ $<

clean:
    rm -rf echo

執行結果:

server端:echo

client端:telnet

提示:使用telnet測試非常方便,但是退出telnet有點麻煩,輸出ctrl+],然后輸入quit即可退出。

示例2:定時器

/*******************************************************************************
* File Name        : timer.cpp
* Author        : zjw
* Email            : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode)
* Create Time    : 2015年07月21日 星期二 16時45分16秒
*******************************************************************************/
#include <sys/select.h>
#include <unistd.h>
#include <iostream>
using namespace std;

int main(int argc, char **argv)
{
    struct timeval tv;

    while (1)
    {
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        select(0, NULL, NULL, NULL, &tv);
        cout << "Five second have passed." << endl;
    }

    return 0;
}

三、參考

http://blog.csdn.net/turkeyzhou/article/details/8609360

四、疑問

select是如何判斷每個描述符可讀、可寫或者有異常的?

每個描述符沒有設置為非阻塞的,那么如果select使用recv來嘗試讀,它是如何做到不阻塞的?或者說,select將這些描述符設置為非阻塞的了?還是它又另外的方法來測試可讀?

select為什么有最大連接限制?不理解所謂的FD_SETSIZE。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM