文章目录
select多路IO转接:
-
原理:借助内核,select来监听,客户端连接、数据通信事件。
void FD_ZERO(fd_set *set); ---清空一个文件描述符集合 fd_set rset; FD_ZERO(&rset); void FD_SET(int fd, fd_set *set); ---将待监听的文件描述符,添加到监听集合中 FD_SET(3,&rset); FD_SET(5,&rset) ; FD_SET(6,&rset); void FD_CLR(int fd, fd_set *set); --将一个文件描述符从监听集合中移除。 FD_CLR(4,&rset); int FD_ISSET(int fd, fd_set *set); ---判断一个文件描述符是否在监听集合中。 返回值:在:1;不在。0; FD_ISSET (4,&rset) ;
-
int select(int nfds,fd_set *readfds,fd_set *writefds, fd_set *exceptfds,struct timeva1 *timeout);
nfds:监听的所有文件描述符中,最大文件描述符+1
readfds: 读文件描述符监听集合。传入、传出参数
writefds: 写文件描述符监听集合。传入、传出参数 NULL
exceptfds: 异常文件描述符监听集合。传入、传出参数NULL
timeout: >0:设置监听超时时长。NULL:阻塞监听。0:非阻塞监听,轮询
返回值:>0:所有监听集合(3个)中,满足对应事件的总数。0:没有满足监听条件的文件描述符。-1:errnol
思路分析
lfd = socket() ; 创建套接字
bind() ; 绑定地址结构
listen(); 设置监听上限
fd_set rset,allset; 创建r监听集合
FD_ZERO(&allset); 将r监听集合清空
FD_SET(lfd, &allset); 将lfd 添加至读集合中。
while (1){
rset = a11set; 保存监听集合
ret = select(lfd+1,&rset,NULL,NULL,NULL); 监听文件描述符集合对应事件。
if (ret > 0){ 有监听的描述符满足对应事件
if (FD_ISSET(lfd,&rset)){ // 1在。0不在。
cfd = accept () ﹔ 建立连接,返回用于通信的文件描述符
FD_SET(cfd,,&allset); 添加到监听通信描述符集合中。
}
for (i = lfd+1;i <=最大文件描述符; i++){
FD_ISSET(i,&allset) 有read、write事件
read ()
小-大
write();
}
}
}
代码实现
服务端:
#define SERV_PORT 6666
int main(int argc,char *argv[]){
int i, j,n,nready;
int maxfd = 0;
int listenfd,connfd;
char buf[BUFSIZ]; /*#define INET_ADDRSTRLEN 16*/
struct sockaddr_in clie_addr , serv_addr;
socklen_t clie_addr_len;
listenfd = Socket(AF_INET,SOCK_STREAM,0);int opt = 1;
setsockopt(listenfd,soL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&serv_addr , sizeof(serv_addr));
serv_addr.sin_fanily= AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port= htons(SERV_PORT);
Bind(listenfd,(struct sockaddr * )&serv_addr , sizeof(serv_addr));
Listen(listenfd,128);
fd_set rset,allset; /*set 读事件文件描述符集合allset用来暂存*/
maxfd = listenfd;
FD_ZERO(&allset);
FD_SET(listenfd,&allset); /*构造select监控文件描述符集*/
while (1){
rset = allset; /*每次循环时都从新设置select监控信号集*/
nready = select(naxfd+1,&rset,NULL,NULL,NULL);
if (nready < 0)
perr_exit( "select error");
if (FD_ISSET(listenfd,&rset)) {
/*说明有新的客户端链接请求*/
clie_addr_len = sizeof(clie_addr);
connfd = Accept(listenfd,(struct sockaddr * )&clie_addr,&clie_addr_len); /* Accept不会阻塞*/
FD_SET(connfd,&allset); /*向监控文件描述符集合allset添加新的文件描述符connfd*/
if (naxfd < connfd)
maxfd = connfd;
if (0 == --nready) /*只有listenfd有事件,后续的for不需执行*/
continue;
}
for (i = listenfd+1; i <= naxfd; i++) {
/*检测哪个clients有数据就绪*/
if (FD_ISSET(i,, &rset)) {
if ((n = Read(i,buf,sizeof(buf))) == 0) /*当client关闭链接时,服务器端也关闭对应链接*/
close(i);
FD_CLR(i, &allset); /*解除select对此文件描述符的监控*/
}else if (n > 0) {
for (j = 0; j <n; j++)
buf[j] = toupper(buf[j]);write(i, buf, n);
}
}
}
}
return 0;
}
客户端:
int main(int argc,char *argv[]){
struct sockaddr_in servaddr;char buf[MAXLINE];
int sockfd,n;
sockfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr ,sizeof(servaddr ));
servaddr.sin_fanily = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr.s_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd,(struct sockaddr *)&servaddr ,sizeof(servaddr));
printf("---------- --connect ok----------------\n");
while (fgets(buf,MAXLINE,stdin) != NULL){
write(sockfd,buf, strlen(buf));
n = Read(sockfd,buf,MAXLINE);
if (n == 0) {
printf( "the other side has been closed.\n");
break;
}
else
write(STDOUT_FILENO,buf,n);
}
close(sockfd);
return 0;
}
select优缺点:
缺点:
监听上限受文件描述符限制。最大1024.
检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度。
优点:
跨平台。win、linux、macOS、Unix、类Unix、mips