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;
  }
View Code

我们的客户端在服务器结束是,也结束了任务

 

[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; }

 我开了六个客户端,一切工作正常

posted @ 2018-12-19 20:07  keep!  阅读(286)  评论(0编辑  收藏  举报
Live2D