zoukankan      html  css  js  c++  java
  • Socket网络编程--聊天程序(4)

      上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理。对于多用户连接时,服务器会受不了的,而且还很消耗资源。据说有个select函数可以用,好像还很NB的样子。

      使用select多路转换处理聊天程序

      下面摘取APUE 14.5小结 I/O多路转接

    当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中循环中使用阻塞I/O:

      while((n = read(STDIN_FILENO, buf, BUFFSIZ))>0)

        if(write(STDOUT_FILENO, buf, n)!=n)

          err_sys("write error");

    这种形式的阻塞I/O到处可见。但是如果必须从两个描述符读,又将如何呢?如果仍旧使用阻塞I/O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。所以为了处理这种情况显然需要另一种不同的技术。

    方法一:也就是上一小节使用的方法,使用多进程。每一个进程处理一个描述符

    方法二:和上面相似的,使用多线程,不同的线程处理不同的描述符

    方法三:仍然使用一个进程执行该程序,但使用非阻塞I/O读取数据。然后对所有的描述符进行遍历一遍,判断对应的描述符是否有数据,如果有就读取,如果没有就立即返回。这种办法就是轮询(polling)

    方法四:异步I/O。其基本的思想是进程告诉内核,当一个描述符已经准备好可以进行I/O时,用一个信号通知它。

    方法五:这是一种比较好的办法。叫做I/O多路转换(I/O multiplexing)。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才会返回。在返回时,它高数进程哪些描述符已经准备好可以进行I/O。

      poll,pselect和select这三个函数使我们能够执行I/O多路转换。本程序只使用select函数。

      #include <sys/select.h>

      int select (int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct time val *restrict tvptr);  //返回值:准备就绪的描述符数,若超时则返回0,否则出错返回-1

      select 函数讲解
      FD_ISSET判断描述符fd是否在给定的描述符集fdset中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。

      fd_set数据类型的操作

      #include <sys/select.h>

      int FD_ISSET(int fd, fd_set *fdset);  //判断fd是否在fdset中

      void FD_CLR(int fd, fd_set *fdset);  //进fd从fdset中取出

      void FD_SET(int fd, fd_set *fdset);  //将fd放入fdset

      void FD_ZERO(fd_set *fdset);    //将fdset清空

      timeval结构分析

      struct timeval{

        long tv_sec; //seconds

        long tv_usec; //and microseconds

      };

      client.c的代码没有改

      server.c的代码如下

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <errno.h>
      4 #include <string.h>
      5 #include <netdb.h>
      6 #include <sys/types.h>
      7 #include <sys/socket.h>
      8 #include <sys/time.h>
      9 #include <sys/un.h>
     10 #include <sys/ioctl.h>
     11 #include <sys/wait.h>
     12 #include <sys/select.h>
     13 #include <netinet/in.h>
     14 #include <arpa/inet.h>
     15 
     16 #define SERVER_PORT 12138
     17 #define BACKLOG 20
     18 #define MAX_CON_NO 10
     19 #define MAX_DATA_SIZE 4096
     20 
     21 int MAX(int a,int b)
     22 {
     23     if(a>b) return a;
     24     return b;
     25 }
     26 
     27 int main(int argc,char *argv[])
     28 {
     29     struct sockaddr_in serverSockaddr,clientSockaddr;
     30     char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
     31     int sendSize,recvSize;
     32     int sockfd,clientfd;
     33     fd_set servfd,recvfd;//用于select处理用的
     34     int fd_A[BACKLOG+1];//保存客户端的socket描述符
     35     int conn_amount;//用于计算客户端的个数
     36     int max_servfd,max_recvfd;
     37     int on=1;
     38     socklen_t sinSize=0;
     39     char username[32];
     40     int pid;
     41     int i;
     42     struct timeval timeout;
     43 
     44     if(argc != 2)
     45     {
     46         printf("usage: ./server [username]
    ");
     47         exit(1);
     48     }
     49     strcpy(username,argv[1]);
     50     printf("username:%s
    ",username);
     51 
     52     /*establish a socket*/
     53     if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
     54     {
     55         perror("fail to establish a socket");
     56         exit(1);
     57     }
     58     printf("Success to establish a socket...
    ");
     59 
     60     /*init sockaddr_in*/
     61     serverSockaddr.sin_family=AF_INET;
     62     serverSockaddr.sin_port=htons(SERVER_PORT);
     63     serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
     64     bzero(&(serverSockaddr.sin_zero),8);
     65 
     66     /*
     67      * SOL_SOCKET.SO_REUSEADDR 允许重用本地地址
     68      * */
     69     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
     70 
     71     /*bind socket*/
     72     if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-1)
     73     {
     74         perror("fail to bind");
     75         exit(1);
     76     }
     77     printf("Success to bind the socket...
    ");
     78 
     79     /*listen on the socket*/
     80     if(listen(sockfd,BACKLOG)==-1)
     81     {
     82         perror("fail to listen");
     83         exit(1);
     84     }
     85 
     86     timeout.tv_sec=1;//1秒遍历一遍
     87     timeout.tv_usec=0;
     88     sinSize=sizeof(clientSockaddr);//注意要写上,否则获取不了IP和端口
     89 
     90     FD_ZERO(&servfd);//清空所有server的fd
     91     FD_ZERO(&recvfd);//清空所有client的fd
     92     FD_SET(sockfd,&servfd);
     93     conn_amount=0;
     94     max_servfd=sockfd;//记录最大的server端描述符
     95     max_recvfd=0;//记录最大的client端的socket描述符
     96     while(1)
     97     {
     98         FD_ZERO(&servfd);//清空所有server的fd
     99         FD_ZERO(&recvfd);//清空所有client的fd
    100         FD_SET(sockfd,&servfd);
    101         //timeout.tv_sec=30;//可以减少判断的次数
    102         switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))//为什么要+1,是因为第一个参数是所有描述符中最大的描述符fd号加一,原因的话在APUE中有讲,因为内部是一个数组,第一个参数是要生成一个这样大小的数组
    103         {
    104             case -1:
    105                 perror("select error");
    106                 break;
    107             case 0:
    108                 //在timeout时间内,如果没有一个描述符有数据,那么就会返回0
    109                 break;
    110             default:
    111                 //返回准备就绪的描述符数目
    112                 if(FD_ISSET(sockfd,&servfd))//sockfd 有数据表示可以进行accept
    113                 {
    114                     /*accept a client's request*/
    115                     if((clientfd=accept(sockfd,(struct sockaddr  *)&clientSockaddr, &sinSize))==-1)
    116                     {
    117                         perror("fail to accept");
    118                         exit(1);
    119                     }
    120                     printf("Success to accpet a connection request...
    ");
    121                     printf(">>>>>> %s:%d join in!
    ",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port));
    122                     //每加入一个客户端都向fd_A写入
    123                     fd_A[conn_amount++]=clientfd;
    124                     max_recvfd=MAX(max_recvfd,clientfd);
    125                 }
    126                 break;
    127         }
    128         //FD_COPY(recvfd,servfd);
    129         for(i=0;i<MAX_CON_NO;i++)//最大队列进行判断,优化的话,可以使用链表
    130         {
    131             if(fd_A[i]!=0)
    132             {
    133                 FD_SET(fd_A[i],&recvfd);//对所有还连着服务器的客户端都放到fd_set中用于下面select的判断
    134             }
    135         }
    136 
    137         switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout))
    138         {
    139             case -1:
    140                 //select error
    141                 break;
    142             case 0:
    143                 //timeout
    144                 break;
    145             default:
    146                 for(i=0;i<conn_amount;i++)
    147                 {
    148                     if(FD_ISSET(fd_A[i],&recvfd))
    149                     {
    150                         /*receive datas from client*/
    151                         if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,0))==-1)
    152                         {
    153                             //perror("fail to receive datas");
    154                             //表示该client是关闭的
    155                             printf("close
    ");
    156                             FD_CLR(fd_A[i],&recvfd);
    157                             fd_A[i]=0;//表示该描述符已经关闭
    158                         }
    159                         else
    160                         {
    161                             printf("Client:%s
    ",recvBuf);
    162                             //可以判断recvBuf是否为bye来判断是否可以close
    163                             memset(recvBuf,0,MAX_DATA_SIZE);
    164                         }
    165                     }
    166                 }
    167                 break;
    168         }
    169 
    170     }
    171     return 0;
    172 }

      运行后的截图结果

      可以看出三个客户端都可以随时连接到服务器,并且发送数据给服务器。实现的效果跟上一节的多进程实现是一样的。毕竟没有大量客户端进行连接,所以就看不出效果,从书中和网上介绍说,这样可以提高某些方面的性能。

      下一节将介绍服务器端向各个还在线的客户端进行发送数据,实现交互。然后再实现聊天室功能,大概的思路就是对接收到的数据进行转发。

      参考资料: http://www.cnblogs.com/gentleming/archive/2010/11/15/1877976.html

      

      本文出处: http://www.cnblogs.com/wunaozai/p/3870338.html

  • 相关阅读:
    尚筹网11阿里云OSS对象存储
    阿里云的OSS对象存储
    尚筹网10用户登录
    尚筹网09用户注册
    尚筹网08环境搭建
    实体类的进一步划分
    尚筹网07分布式架构
    临时弹出一个QQ对话窗口
    Input框改placeholder中字体的颜色
    判断银行卡号的正则
  • 原文地址:https://www.cnblogs.com/wunaozai/p/3870338.html
Copyright © 2011-2022 走看看