linux下的I/O複用模型之select詳解【值得看】

select函數詳解
int select(int maxfdp, fd_set *readfds, fd_set *writefds, 
               fd_set *exceptfds, struct timeval *timeout);
參數:
(1)maxfdp:    當前最大描述符數+1
(2)readfds:    指向一個套接字集合,用於檢測其可讀性    
(3)writefds:    指向一個套接字集合,用於檢測其可寫性    
(4)exceptfds:指向一個套接字集合,用於檢測錯誤
(5)timeout:    select函數的超時時間
   結構體以下:
   struct timeval{
       time_t tv_sec;        
       time_t tv_usec;    
   };
   它可使select處於三種狀態:
   (a)若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,
       必定等到監視文件描述符集合中某個文件描述符發生變化爲止;
   (b)若將時間值設爲0秒0微秒,就變成一個純粹的非阻塞函數,
       無論文件描述符是否有變化,都馬上返回繼續執行,
       文件無變化返回0,有變化返回一個正值;
   (c)timeout的值大於0,這就是等待的超時時間,即 select在timeout時間內阻塞,
       超時時間以內有事件到來就返回了,不然在超時後無論怎樣必定返回。linux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fd_set其實這是一個數組的宏定義,其實是一long類型的數組,
每個數組元素都能與一打開的文件句柄(socket、文件、管道、設備等)創建聯繫,
創建聯繫的工做由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,
由此來通知執行了select()的進程哪一個句柄可讀。
=============================
ps:
linux下的fd_set結構和windows下的fd_set結構仍是有所不一樣的,
Windows系統下的fd_count和fd_array[]是不可使用的程序員

1
2
3
4
5
6
7
8
9
系統提供瞭如下宏能夠在不一樣的套接字環境中提升移植性
(1)FD_CLR(s,*set):從集合set中刪除套接字s
(2)FD_ISSET(s,*set):若套接字s是集合中的一員,返回值非0,不然是0    
(3)FD_SET(s,*set)    :將套接字s添加到集合中
(4)FD_ZERO(*set):將set集合初始化爲空集NULLwindows

select模型流程(重點)
select函數返回的是就緒socket的數量,用戶須要自定義數組來保存就緒的socket而且處理。
fd_set這個結構只是負責監聽,可是要注意須要咱們本身人爲的將傳入時的集合(inset)和傳出時的集合(outset)分離開來。
inset:表明須要監視的文件描述符,將他們置爲1
outset:表明將就緒的socket返回時依舊保留爲1,其他的就是置爲0
以下圖所示:數組


客戶端代碼服務器

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

#define ServerIP "192.168.43.8"
#define ServerPort 27015socket


int main()
{
    int ret;
    char sendbuf[128];
    bzero(sendbuf,sizeof(sendbuf));
    函數

    struct sockaddr_in addrServer;
    bzero(&addrServer,sizeof(addrServer));
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(ServerPort);
    inet_pton(AF_INET,ServerIP,&addrServer.sin_addr.s_addr);
    
    //建立socket
    int clientfd = socket(AF_INET,SOCK_STREAM,0);
    if(clientfd ==-1){
        printf("socket error\n");
        return -1;
    }
    //發起鏈接請求
    ret = connect(clientfd,(struct sockaddr*)&addrServer,sizeof(addrServer));
    if(ret == -1){
        printf("connect error\n");
        close(clientfd);
        return -1;
    }
    
    printf("connect successful.....\n");
    
    printf("Please input sendbuf:");
    scanf("%s",sendbuf);
    
    //發送數據測試

    ret = send(clientfd,sendbuf,sizeof(sendbuf),0);
    if(ret > 0){
        printf("host send:%s\n",sendbuf);
    }
    close(clientfd);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
select模型的服務器代碼
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>.net

int main()
{
    //網絡初始化
    char recvbuf[1024];
    char ip[16];
    int  ret;
    int     maxfd;
    int  clientfdArr[1024];

    struct sockaddr_in addrserver,addrclient;
    bzero(&addrserver,sizeof(addrserver));
    bzero(&recvbuf,sizeof(recvbuf));
    bzero(&ip,sizeof(ip));

    addrserver.sin_family = AF_INET;
    addrserver.sin_port = htons(27015);
    addrserver.sin_addr.s_addr = htonl(INADDR_ANY);

    //建立socket
    int serverfd = socket(AF_INET,SOCK_STREAM,0);
    if(serverfd == -1){
        printf("create socket error\n");
        return 1;
    }
    //綁定
    ret = bind(serverfd,(struct sockaddr*)&addrserver,sizeof(addrserver));
    if(ret == -1)
    {
        printf("bind error\n");
        close(serverfd);
        return 1;
    }
    //監聽
    ret = listen(serverfd,128);
    if(ret == -1)
    {
        printf("listen error\n");
        close(serverfd);
        return 1;
    }
    printf("Select Server Runing.....\n");
    
    
    //啓用select模型

    fd_set inset,outset;            
    FD_ZERO(&inset);                
    FD_SET(serverfd,&inset);        //將serverfd放入readset集合中
    
    //初始化socket描述符數組
    for(int i = 0; i < 1024; ++i)
        clientfdArr[i] = -1;
    
    maxfd = serverfd;
    
    //select循環調用
    while(1)
    {
                
        outset = inset;    //將傳入監聽集合賦給傳出監聽集合
        ret = select(maxfd+1,&outset,NULL,NULL,NULL);
        if(ret > 0)
        {
            printf("select successful...\n");
            printf("就緒的數量爲:%d\t事件:某客戶端請求鏈接.....\n",ret);
            
            //serverfd就緒
            if(FD_ISSET(serverfd,&outset))
            {
                socklen_t size = sizeof(addrclient);
                int clientfd = accept(serverfd,(struct sockaddr*)&addrclient,&size);
                //將新的clientfd加入監聽集合和socket描述符數組中
                FD_SET(clientfd,&inset);
                for(int i = 0; i < 1024; ++i)
                    if(clientfdArr[i] == -1){
                        clientfdArr[i] = clientfd;
                        break;
                    }
                maxfd = clientfd > maxfd ? clientfd : maxfd;
            }

            //clientfd就緒,數據傳輸
            else
            {
                for(int i = 0; i < 1024; ++i)
                {
                    if(clientfdArr[i] != -1)
                    {
                        if(FD_ISSET(clientfdArr[i],&outset))
                        {
                            ret = recv(clientfdArr[i],recvbuf,sizeof(recvbuf),0);
                            if(ret > 0)
                            {
                                printf("recv data is:%s\n",recvbuf);
                                continue;
                            }
                            else if(ret == 0)
                            {
                                printf("client normal closed...\n");
                                close(clientfdArr[i]);
                                FD_CLR(clientfdArr[i],&inset);
                                clientfdArr[i] = -1;
                                break;
                            }
                            else{
                                printf("recv error\n");
                                close(clientfdArr[i]); 
                                FD_CLR(clientfdArr[i],&inset);
                                clientfdArr[i] = -1;
                                break;
                            }
                        }
                    }
                }
            }
        }
        else
        {
            printf("select error\n");
            break;
        }

    }

    close(serverfd);
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
測試結果

6.模型評價: (1)系統開銷比較小,實現和維護比較方便,局域網首選 (2)若是IO複用的時候對時間精度要求較高,select支持微妙級別,而poll,epoll只支持到毫秒級別 (3)具備良好的兼容性,支持跨平臺 (4)在Linux內核中,select所用到的FD_SET是有限的,由參數FD_SETSIZE來決定,最多也就是1024個 (5)在內核中採用輪詢方法,即每次檢測都會遍歷全部FD_SET中的句柄,即 select要檢測的句柄數越多就會越費時,效率會變得很低。 (6)select沒有將傳入傳出分離,用戶每次要本身準備傳入參數,select返回的只是就緒的數量,用戶須要本身取出來校驗哪一個socket就緒,有必定的系統開銷 (7)select函數調用時每次都會將整個監聽集合拷貝給內核,內核將集合中全部的socket掛載到設備隊列中(裏面有大量的重複socket描述符),這種方案產生大量無心義的拷貝開銷和設備掛載開銷 ---------------------  做者:bryant_xw  來源:CSDN  原文:https://blog.csdn.net/bryant_xw/article/details/89032938  版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索