Unix网络-select
我们通过代码来看
回射服务器的客户端 \\client.cpp ___________________________________________________ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<iostream> using namespace std; int main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("socket() err"); return -1; } char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { //send write(sockfd,sendbuf,sizeof(sendbuf)); //recv int rc=read(sockfd,recvbuf,sizeof(recvbuf)); if(rc<0) { perror("read() error"); break; }else if(rc==0) { printf("connect is cloesd !\n"); break; } printf("recv message:%s\n",recvbuf); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } return 0; }
回射服务器 \\server.cpp __________________________________________________________ #include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { perror("setsockopt() err"); return -1; } // struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { perror("bind() err"); return -1; } if (listen(sockfd, SOMAXCONN) == -1) { perror("bind() err"); return -1; } struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn = accept(sockfd, (struct sockaddr *) &peeraddr, &peerlen); if (conn == -1) { perror("accept() err"); return -1; } printf("accept by :%s \n", inet_ntoa(peeraddr.sin_addr)); char recvbuf[1024] = { 0 }; while (1) { int rc = read(conn, recvbuf, sizeof(recvbuf)); if (rc == 0) { printf("client is closed !\n"); break; } else if (rc < 0) { printf("read() failed ! error message:%s\n", strerror(errno)); break; } printf("recv message:%s\n", recvbuf); write(conn, recvbuf, rc); memset(recvbuf, 0, sizeof(recvbuf)); } close(conn); close(sockfd); return 0; }
以上便是回射服务器和客户端,但是他们存在着一个问题,那就是我们会发现,当服务器端单方结束的时候,客户端仍然在启动



造成这种原因的问题是,我们的客户端被阻塞在了fgets0这个函数,从标准输出中读取。只要没有输入就会一直停在这个地方。当服务器结束是,客户端便无法读到服务器发来的FIN。
select就是用来解决这个问题的。I/O复用模型

select先阻塞,有活动套接字才返回可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监视所有select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,我们就可以调用recvfrom处理数据。
接着看使用select改过后客户端的代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<iostream> using namespace std; int main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("socket() err"); return -1; } char sendbuf[1024]={0}; char recvbuf[1024]={0}; fd_set rset; FD_ZERO(&rset); int nready; int fd_stdin=fileno(stdin); \\这里只考虑了读字符集,stdin从键盘中读取信息到缓冲区中。 while(1){ \\重置集合中的描述符,因为在rset中,如果某个时刻,只有fd_stdin有信号,那么sockfd会被删除,只剩下fd_stdin,如果不重置,那么下一次循环就监控不了fd_stdin了,在这里定义一个fd_set tmp=rset;也是可以的 FD_SET(fd_stdin,&rset); FD_SET(sockfd,&rset); int maxfd; if(sockfd>fd_stdin) maxfd=sockfd; else maxfd=fd_stdin; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) perror("nready"); if(nready==0) continue; if(FD_ISSET(sockfd,&rset)){ int rc=read(sockfd,recvbuf,sizeof(recvbuf)); if(rc<0){ perror("read() error"); break; }else if(rc==0){ printf("connect is cloesd !\n"); break; } } printf("recv message:%s\n",recvbuf); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); if(FD_ISSET(fd_stdin,&rset)){ if(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) write(sockfd,sendbuf,sizeof(sendbuf)); else break; } } /* while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { //send write(sockfd,sendbuf,sizeof(sendbuf)); //recv int rc=read(sockfd,recvbuf,sizeof(recvbuf)); if(rc<0) { perror("read() error"); break; }else if(rc==0) { printf("connect is cloesd !\n"); break; } printf("recv message:%s\n",recvbuf); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); }*/ return 0; }


我们的客户端在服务器结束是,也结束了任务
[0,maxfd);
由于我们是通过循环rset处理描述符,所以实际上是一个并发处理
接下来我们用select来改善服务器,不用fo--------------select的并发服务器----------------------------------------------------
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include<iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<sys/select.h> #include<errno.h> using namespace std; int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { perror("setsockopt() err"); return -1; } char recvbuf[1024]; int conn; int client[FD_SETSIZE]; for(int i=0;i<FD_SETSIZE;i++){ client[i]=-1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { perror("bind() err"); return -1; } if (listen(sockfd, SOMAXCONN) == -1) { perror("bind() err"); return -1; } struct sockaddr_in peeraddr; // socklen_t peerlen = sizeof(peeraddr); /* conn = accept(sockfd, (struct sockaddr *) &peeraddr, &peerlen); if (conn == -1) { perror("accept() err"); return -1; } printf("accept by :%s \n", inet_ntoa(peeraddr.sin_addr));*/ //char recvbuf[1024] = { 0 }; int nready; int maxfd=sockfd; fd_set rset; int maxi=0;//最大的client的非负数的下标设置这个的目的是当select检测到同时有监听信号和接受信号时,当处理完监听信号后,处理接受信号时,不用将client数组全部遍历一遍, fd_set allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(sockfd,&allset); while(1){ rset=allset;//这里用一个allset的原因是,当没有监听信号时,rset的监听位会被消除,所以用rset做监测监听位的参数,allset做监听接受信息的参数。 nready=select(maxfd+1,&rset,NULL,NULL,NULL);\\补充一下,这个select函数内部是在maxfd+1个文件描述符遍历一次,然后将其拷贝到rset中‘ if(FD_ISSET(sockfd,&rset)){\\FE_ISSET内部也是遍历一次描述符,寻找哪些文件描述符发生了I/O事件 socklen_t peerlen = sizeof(peeraddr); conn=accept(sockfd,(struct sockaddr *) &peeraddr, &peerlen); printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); for(int i=0;i<FD_SETSIZE;i++){ if(client[i]<0){ client[i]=conn; if(i>maxi) maxi=i; break; } } FD_SET(conn,&allset); if(conn>maxfd) maxfd=conn; if(--nready<=0)//当只有监听一个信号时,不用再去遍历接收信号 continue; } for(int i=0;i<=maxi;i++){ conn=client[i]; if(conn==-1) continue; if(FD_ISSET(conn,&rset)){ int rc = read(conn, recvbuf, sizeof(recvbuf)); if (rc == 0) { cout<<"client has closed"<<endl; FD_CLR(conn,&allset);//当关闭一个客户端,需要将其位清除 client[i]=-1;//同时将client数组置为-1; break; } else { printf("recv message:%s\n", recvbuf);//这里第一次写的时候写错了,忘了加else并且将--nready包括进去。 write(conn, recvbuf, rc); memset(recvbuf, 0, sizeof(recvbuf)); if(--nready<=0) break;
} } } /* while (1) { int rc = read(conn, recvbuf, sizeof(recvbuf)); if (rc == 0) { printf("client is closed !\n"); break; } else if (rc < 0) { printf("read() failed ! error message:%s\n", strerror(errno)); break; } printf("recv message:%s\n", recvbuf); write(conn, recvbuf, rc); memset(recvbuf, 0, sizeof(recvbuf)); }*/ close(conn); close(sockfd); return 0; }
我开了六个客户端,一切工作正常


浙公网安备 33010602011771号