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 }

浙公网安备 33010602011771号