1. I/O多路複用基本思路
I/O多路複用就是讓應用程序可以同時對多個I/O端口進行監控以判斷其上的操作是否可以進行,達到時間複用的目的。由於I/O多路複用是在單一進程的上下文中的,因此每個邏輯流程都能訪問該進程的全部地址空間,所以開銷比多進程低得多。由於I/O多路複用都是在單一進程中進行的,所以不會出現多線程中的線程不安全的問題。
2. select模型
在man page中給出的select函數原型:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#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);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
函數准許進程指示內核等待多個事件中的任何一個發生,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒。man page中給出了一個較新的函數pselect,這個函數僅僅是多了一個sigmask參數。sigmask參數指定了在執行pselect函數時屏蔽的信號集合。
select()函數參數:
int nfds, //監控的文件描述符集裏最大文件描述符加1
fd_set *readfds, //監控有讀數據到達文件描述符集合
fd_set *writefds,//監控有寫數據到達文件描述符集合
fd_set *exceptfds,//監控異常發生達文件描述符集合,如帶外數據到達異常,傳入傳出參數
struct timeval *timeout //定時阻塞監控時間,3種情況
/*
1.NULL,永遠等下去
2.設置timeval,等待固定時間
3.設置timeval裏時間均爲0,檢查描述字後立即返回,輪詢
*/
另外的三個函數功能比較簡單:
int FD_ISSET(int fd, fd_set *set); 測試文件描述符集合裏fd是否置1
void FD_SET(int fd, fd_set *set); 把文件描述符集合裏fd位置1
void FD_ZERO(fd_set *set); 把文件描述符集合裏所有位清0
3. select模型回射服務器
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/select.h>
#define SERV_PORT 8000
#define MAXLINE 1024
int main()
{
//maxfd: 打開的最大文件描述符標號
//listenfd: 監聽描述符
//confd: 鏈接描述符
//clientaddrlen 客戶端地址長度
//sockfd: 暫存量
int maxfd, listenfd, confd, clientaddrlen, sockfd, n, i = 0;
struct sockaddr_in serveraddr, clientaddr; //服務器端地址,客戶端地址
char str[INET_ADDRSTRLEN]; //存放IP地址,點分十進制表示
fd_set rset, allset; //select 使用
char buf[MAXLINE]; //傳輸數據
int nReady, client[FD_SETSIZE], maxi; //
//1. 創建一個socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//2. 綁定一個端口
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERV_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
//3. 設置監聽
listen(listenfd, 20);
maxfd = listenfd;
maxi = 0;
for(i=0; i<FD_SETSIZE; ++i); //初始化clinet[]
client[i] = -1;
FD_ZERO(&allset); //select監控文件描述符集
FD_SET(listenfd, &allset);
for(;;)
{
rset = allset;
nReady = select(maxfd + 1, &rset, NULL, NULL, NULL);
if(nReady < 0) //select出錯
{
perror("select err\n");
break;
}
if(FD_ISSET(listenfd, &rset)) //新鏈接的客戶端
{
clientaddrlen = sizeof(clientaddr);
confd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
printf("ip: %s, port: %d \n",
inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)),
ntohs(clientaddr.sin_port));
for(i=0; i<FD_SETSIZE; ++i)
{
if(client[i] < 0)
{
client[i] = confd;
break;
}
}
if(i == FD_SETSIZE)
{
fputs("limited\n", stderr);
exit(1);
}
FD_SET(confd, &allset);
if(confd > maxfd) maxfd = confd;
if(i > maxi) maxi = i;
if(--nReady == 0) continue;
}
for(i=0; i<=maxi; ++i) //遍歷看哪個客戶端有數據就緒
{
if((sockfd = client[i]) < 0) continue;
if(FD_ISSET(sockfd, &rset))
{
if((n = read(sockfd, buf, MAXLINE)) == 0)
{
close(sockfd);
FD_CLR(sockfd, &allset);
client[i]=0;
}
else
write(sockfd, buf, n);
if(--nReady == 0)
break;
}
}
}
close(listenfd);
return 0;
}
測試用客戶端代碼,可使用上一篇博文中的客戶端。