I/O多路轉接之select
阿新 • • 發佈:2019-02-11
在完成I/O操作時,程式中完成真正I/O的時間可能只有少的一部分,而大部分時間都處於一個等的時間。比如,此時需要從一個套接字中讀取資料read(socket, buf, BUFSIZE); 這個操作可能會一直阻塞,直到有資料從網路的另一端傳送過來。等的時間過於長,這是I/O效率低下的真正原因。可能有人會提出讓程式碼不要阻塞的等,可以進行非阻塞的等待,比如當read一個socket發現沒有資料時,就不在等待,而去read其他的socket,進行一種輪詢式的read。可是這種模式還是會進行read的這個操作,不過這時進行操作時不成功的話就去read其他socket,效率還是低下的。
為了解決上述問題,提出了I/O多路轉接 。它的做法是這樣的,一次等多個檔案描述符,當有一個或者多個檔案描述符就緒,可以進行I/O操作時,便返回通知有哪些那些檔案描述符可以I/O。
系統提供了select函式實現多路複用輸入/輸出模型,select系統呼叫可以監視多個檔案描述符的狀態變化。程式會停在select這裡等待,直到被監視的檔案描述符至少有一個的狀態發生了變化。
函式的定義:
引數描述:
- nfds:要關心的檔案描述符
- readfds:表示要監視檔案描述符集中,所有檔案描述符的讀狀態
- writefds:表示要監視檔案描述符集中,所有檔案描述符的寫狀態
- exceptfds:表示要監視檔案描述符集中,所有檔案描述符的異常狀態
- timeout:監視多長時間,
- 當timeout被設定為0,表示以非阻塞方式等待;
- 當timeout被設定為大於0的數字,則表示其等待時間,有秒和毫秒的區分
- 當timeout設定為NULL時,表示已阻塞方式等待。
- 當監視的檔案描述符集中有檔案描述符就緒,則會返回一個大於0的數
- 當監視的檔案描述符沒有任何一個就緒時,並且指定的時間已到,返回0
- 當函式調用出錯時,返回-1
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/select.h>
#include<error.h>
#include<unistd.h>
#include<sys/types.h>
#define NUMS 1024
static void Usage(char* proc)
{
printf("Usage: %s [local_ip] [local_port]\n", proc);
}
int startup(char* ip, int port)
{
//建立檔案特性
//AF_INET ipv4,SOCK_STREAM,基於位元組流服務
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror("socket");
return 2;
}
printf("sock = %d\n", sock);
struct sockaddr_in local;
//確定地址協議型別
local.sin_family = AF_INET;
//繫結埠
local.sin_port = htons(port);
//繫結ip
local.sin_addr.s_addr = inet_addr(ip);
//繫結網路特性
if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
perror("bind");
return 3;
}
//監聽套接字
if (listen(sock, 10) < 0)
{
perror("listen");
return 4;
}
return sock;
}
//初始化檔案描述符陣列
void Init(int *fds)
{
int i = 0;
for (; i < NUMS; i++)
{
fds[i] = -1;
}
}
//將檔案描述符陣列所儲存的檔案描述符設定進檔案描述符集
int Addfd(int *fds, fd_set *set)
{
int i = 0;
int maxfd = fds[0];
for (; i < NUMS; i++)
{
if (fds[i] != -1)
{
FD_SET(fds[i], set);
if (maxfd < fds[i])
{
maxfd = fds[i];
}
}
}
//返回最大的檔案描述符
return maxfd;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 5;
}
//獲取監聽到的套接字
int listen_sock = startup(argv[1], atoi(argv[2]));
//建立一個輔助的空間(一個數組)用於儲存 檔案描述符資訊
//fds中儲存的檔案描述符將會被監聽讀狀態
int fds[NUMS];
//wfd中儲存的檔案描述符將會被監聽寫狀態
int wfd[NUMS];
//建立檔案描述符集
fd_set set;
fd_set wset;
//將陣列初始化
Init(fds);
Init(wfd);
//監聽listen_sock新增進陣列
fds[0] = listen_sock;
printf("listen_sock:%d\n", fds[0]);
while (1)
{
//初始化檔案描述符集
FD_ZERO(&set);
FD_ZERO(&wset);
int maxrfd = -1;
int maxwfd = -1;
//將存於陣列中的檔案描述符新增至檔案描述符集
maxrfd = Addfd(fds, &set);
maxwfd = Addfd(wfd, &wset);
struct timeval timeread = { 2, 0 };
struct timeval timewrite = { 0, 0 };
if (maxrfd < maxwfd)
maxrfd = maxwfd;
//監視set中的讀狀態資訊,監視wset中的寫狀態資訊
int rres = select(maxrfd + 1, &set, &wset, NULL, &timeread);
switch (rres)
{
case -1:
{
perror("selete");
break;
}
case 0:
{
printf("Timeout...\n");
break;
}
default:
{//至少有一個檔案描述符狀態就緒
int i = 0;
for (; i < NUMS; i++)
{
//此條件滿足,說明客戶端的連線已經就緒
if (i == 0 && fds[i] != -1 && \
FD_ISSET(fds[i], &set))
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//接受監聽到的套接字
int new_sock = accept(listen_sock, \
(struct sockaddr*)&client, &len);
printf("client [%s] [%d]\n", \
inet_ntoa(client.sin_addr), \
ntohs(client.sin_port));
if (new_sock < 0)
{
perror("accept");
continue;
}
//連線套接字後,將其新增到陣列中,他將被監聽讀狀態就緒
for (i; i < NUMS; i++)
{
if (fds[i] == -1)
{
fds[i] = new_sock;
break;
}
}
}
//監視到有檔案描述符的讀狀態就緒,就可以執行讀操作
else if (i != 0 && fds[i] != -1 && FD_ISSET(fds[i], &set))
{
char buf[1024];
//從套接字讀取資訊到buf中
ssize_t s = read(fds[i], buf, sizeof(buf)-1);
if (s > 0)
{
buf[s] = 0;
printf("client# %s\n", buf);
int j = 1;
//從這個檔案描述符中讀取資料後,就可以監視它的寫狀態
for (; j < NUMS; j++)
{
if (wfd[j] == -1)
{
wfd[j] = fds[i];
fds[i] = -1;
break;
}
}
}
else if (s == 0)
{
close(fds[i]);
printf("客戶端已經退出!\n");
fds[i] = -1;
}
else
{
perror("read");
close(fds[i]);
fds[i] = -1;
}
}
//監視到有檔案描述符的寫狀態就緒,就可以進行寫操作
if (wfd[i] != -1 && FD_ISSET(wfd[i], &wset))
{
char buf[1024];
printf("Please Enter# ");
fflush(stdout);
//從鍵盤輸入資訊到buf中
ssize_t _s = read(0, buf, sizeof(buf)-1);
if (_s > 0)
{
buf[_s - 1] = 0;
//傳送資訊到套接字
write(wfd[i], buf, strlen(buf));
//寫完之後,就可等待其回覆,監視其讀狀態
int j = 0;
for (; j < NUMS; j++)
{
if (fds[j] == -1)
{
fds[j] = wfd[i];
wfd[i] = -1;
break;
}
}
}
else
{
perror("read");
close(wfd[i]);
wfd[i] = -1;
break;
}
}
}
}
}
}
return 0;
}
client:#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<sys/types.h>
#define NUMS 1024
static void Usage(char *proc)
{
printf("Usage %s [server_ip] [server_port]\n", proc);
}
void Init(int *fds)
{
int i = 0;
for (; i < NUMS; i++)
{
fds[i] = -1;
}
}
int Addfd(int *fds, fd_set *set)
{
int i = 0;
int maxfd = fds[0];
for (; i < NUMS; i++)
{
if (fds[i] != -1)
{
FD_SET(fds[i], set);
if (maxfd < fds[i])
{
maxfd = fds[i];
}
}
}
return maxfd;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
//建立套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
//將建立的套接字,連入指定的網路服務中,這裡連到了伺服器
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
perror("connect111");
return 3;
}
//建立一個輔助的空間(一個數組)用於儲存 檔案描述符資訊
//wfd中儲存的檔案描述符將會被監聽寫狀態
int wfd[NUMS];
//rfd中儲存的檔案描述符將會被監聽讀狀態
int rfd[NUMS];
//初始化陣列
Init(wfd);
Init(rfd);
//客戶端先寫,所以將sock新增進wfd中
wfd[0] = sock;
//建立檔案描述符集
fd_set rset;
fd_set wset;
char buf[1024];
while (1)
{
//清空檔案檔案描述符集
FD_ZERO(&rset);
FD_ZERO(&wset);
int maxrfd = -1;
int maxwfd = -1;
//將存於陣列中的檔案描述符新增至檔案描述符集
maxrfd = Addfd(rfd, &rset);
maxwfd = Addfd(wfd, &wset);
struct timeval timeread = { 2, 0 };
struct timeval timewrite = { 0, 0 };
if (maxrfd < maxwfd)
maxrfd = maxwfd;
//監視rset中的讀狀態資訊,監聽wset中的寫狀態資訊
int res = select(maxrfd + 1, &rset, &wset, NULL, &timeread);
switch (res)
{
case -1:
{
perror("selete");
break;
}
case 0:
{
printf("Timeout...\n");
break;
}
default:
{
int i = 0;
for (; i < NUMS; i++)
{
//寫狀態就緒
if (wfd[i] != -1 && FD_ISSET(wfd[i], &wset))
{
printf("Please Enter# ");
fflush(stdout);
//從鍵盤寫入內容到緩衝區
ssize_t s = read(0, buf, sizeof(buf)-1);
if (s >0)
{
buf[s - 1] = 0;
printf("server# ");
fflush(stdout);
//將緩衝區內容通過套接字傳送到伺服器
write(wfd[i], buf, strlen(buf));
int j = 0;
//寫操作完成,應該進行讀操作,監視其讀操作
for (; j < NUMS; j++)
{
if (rfd[j] == -1)
{
rfd[j] = wfd[i];
wfd[i] = -1;
break;
}
}
}
else
{
FD_CLR(wfd[i], &wset);
close(wfd[i]);
return 5;
}
}
if (rfd[i] != -1 && FD_ISSET(rfd[i], &rset))
{
//在從套接字中讀取伺服器的迴應資訊
ssize_t _s = read(sock, buf, sizeof(buf)-1);
if (_s > 0)
{
buf[_s] = 0;
printf("%s\n", buf);
int j = 0;
//讀操作完成,應該進行寫操作,監視其寫操作
for (; j < NUMS; j++)
{
if (wfd[j] == -1)
{
wfd[j] = rfd[i];
rfd[i] = -1;
break;
}
}
}
if (_s < 0)
{
FD_CLR(rfd[i], &rset);
close(rfd[i]);
perror("read");
return 4;
}
}
}
}
}
}
return 0;
}