1024多人激情在线聊天室---select函数的使用

效果展示

step1.服务器启动!端口号3006

 

 

 

step2.1号客户端启动!jack加入了群聊

 

 

 

step3.2号客户端启动!kelly加入了群聊

 

 

 

 

step4.3号客户端启动!zwj加入了群聊

 

 

 

 

 

step5.开始聊天吧!zwj发出问候

 

 

step6.Kelly尴尬回应,并询问jack情况

 

 

step7.jack回到

 

 具体代码实现(客户端)

在客户端,存在“收到消息”和“发送消息”这两个事件。其中“收到消息”是随机发生的。“收到消息”取决于其他客户端什么时候发送消息,而“发送消息”取决于用户什么时候键入数据。正是因为“收到消息”这个事件随机发生的,因此不能让程序被采集键盘数据所阻塞掉,这样会使得某个一直不发送消息的用户,无法及时收到其他用户的对话。因此需要使用select函数,对文件描述符监听。哪一个就就绪,就先处理哪一个事件。下面是客户端的核心代码段。

 1 if(severfd > 0){
 2     printf("connction estabilshed\n");
 3 
 4     FD_ZERO(&read_set);
 5     FD_SET(0,&read_set);                                                                                                    
 6     FD_SET(severfd,&read_set);
 7 
 8     while(1){
 9         ready_set = read_set;
10         Select(severfd+1,&ready_set,NULL,NULL,NULL);
11         if(FD_ISSET(0,&ready_set)){
12             read(0, sbuf, sizeof(sbuf));//send data to server
13             mergeMessage(name,sbuf,Message);
14             write(severfd,Message,StrLen(Message));
15         }
16         if(FD_ISSET(severfd,&ready_set)){
17             read(severfd, rbuf, sizeof(rbuf));
18             printf("%s",rbuf);
19         }
20    }
21 }else{
22             printf("fail to estabilsh connction\n");
23 }

 

第3行的ready_set和read_set是 fd_set类型结构体变量,在进入循环之前,已经为read_set做好了初始化。第5行将标准输入0,放置于fd_set中,第6行将从服务器返回的serverfd置于fd_set。在第10行使用select函数,监听0号和serverfd文件描述符,此时进程被挂起。0和serverfd谁先就绪,就会唤醒进程,继续处理下面两个if语句。如果是0号先就绪,那么从标准输入中读取数据,并将输入数据和nickname封装成Messgae,写至serverfd去。如果是serverfd先就绪,那么就从serverfd中读出数据,并打印至控制台显示。客户端代码较容易,逻辑简单,接下来讲述服务器端代码如何实现。

 

 具体代码实现(服务器端)

在编写服务器代码时,也存在“接受新客户端的链接”和“推送消息”这两个事件。这两个事件都是随机发生的。“接受新客户端的链接”取决于客户端什么时候发起请求链接,向其他客户端“推送消息”取决于是否有新的消息被发送至服务器段。因此既不能让“推送消息”事件阻塞服务器,也不能让“接受新客户端的链接”事件阻塞服务器,而应是,哪一个事件先就绪,就先处理哪一个事件。否则的话,服务器要么就一直等待处理链接请求,要么就一直等待推送消息。服务器与客户端代码还有不同的一点是,服务器应该记录下所有客户发送的消息和客户端信息,然后才能将该客户发送的消息推送至其他客户端。因此服务器,应该设置一个数据结构,用于存储和记录所有客户端的信息。

服务器数据结构

服务器段用到的数据结构,该数据结构用于记录所有访问服务器的客户链接请求。其中nready和maxi用于减少遍历循环时的次数,每一次有客户端链接到服务器,就更新maxi的值,因为文件描述符在linux系统下,是从3开始递增(其中0,1,2为标准输入输出,2为标准错误输出)。clientfd是一个int数组用于记录所有客户端的文件描述符。clientfbuf是一个指针数组,每一个指针都指向客户端专属的缓冲器。服务器用此结构,记录每一个客户端的输入数据。

1 typedef struct { 
2     fd_set read_set;  
3     fd_set ready_set; 
4     int nready;       
5     int maxi;        
6     int clientfd[FD_SETSIZE];   
7     char* clientbuf[FD_SETSIZE]; 
8 } pool; 

服务器核心代码

服务器在第4行使用select函数,将使得服务器被挂起,一旦当发生了请求链接事件,服务器将处理该链接,并将该请求添加至pool中,用于推送消息。

 1     while (1) {
 2         pool.ready_set = pool.read_set;
 3         printf("waiting \n");
 4         pool.nready = Select(pool.maxfd+1, &pool.ready_set, NULL, NULL, NULL);
 5 
 6         if (FD_ISSET(listenfd, &pool.ready_set)) { 
 7             connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); 
 8             add_client(connfd, &pool); 
 9         }
10 
11         push_message(&pool); 
12     }

下面是添加客户端的代码,每当有一个客户端成功和服务器成功建立起链接,就把该客户端的文件描述符存入结构中,并为其初始化buf。在第12行,把该文件描述符,加入到服务器的fd_set中,让服务器的select函数监听该客户端。

 1 void add_client(int connfd, pool *p)
 2 {
 3     int i;
 4     p->nready--;
 5     for (i = 0; i < FD_SETSIZE; i++)  
 6         if (p->clientfd[i] < 0) {
 7          
 8             p->clientfd[i] = connfd;                
 9             
10             initBuf(&p->clientbuf[i]);
11             
12             FD_SET(connfd, &p->read_set);
13 
14             if (i > p->maxi)       
15                 p->maxi = i;      
16             break;
17         }
18 }                

 

下面是推送消息的代码,循环遍历pool,并从中取出文件描述符,和它对应的buf。然后并读出它所发送的数据,并在第16行将消息转发至除它以外所有的其他客户端。

 1 void push_messgae(pool *p)
 2 {
 3     int i, connfd, n, j, cfd;
 4     char *buf;
 5  6 
 7     for (i = 0; (i <= p->maxi) && (p->nready > 0); i++) {
 8         connfd = p->clientfd[i];
 9         buf = p-> clientbuf[i];10 
11         
12         if ((connfd > 0) && (FD_ISSET(connfd, &p->ready_set))) {
13             p->nready--;
14             if ((n = read(connfd, buf, sizeof(buf))) != 0) {
15                 printf("i am in,maxi = %d \n",p->maxi);
16                 for(j=0;j <= p->maxi;j++){
17                         cfd = p->clientfd[j];
18                         if(cfd == connfd) continue;
19                         printf("transfering to %d\n",cfd);
20                         write(cfd , buf, n); 
21                 }
22             }
23             else {
24                 Close(connfd); 
25                 FD_CLR(connfd, &p->read_set); 
26                 p->clientfd[i] = -1;          
27             }
28         }
29     }
30 }

 

 

 

posted @ 2019-12-06 21:09  zwjsec  阅读(4090)  评论(1)    收藏  举报