zoukankan      html  css  js  c++  java
  • I/O复用:异步聊天

    一.I/O复用       

          在《TCP套接字编程》的同步聊天程序中,我们看到TCP客户同时处理两个输入:标准输入和TCP套接字。考虑在客户阻塞于标准输入fgets调用时,服务器进程被杀死,服务器TCP虽然会给客户TCP发送一个FIN,但是客户客户进程正阻塞于标准输入读入过程,它将看不到这个EOF,直到从套接字读时为止。这样的进程需要一种预先告知内核的能力,使得内核一旦发现内核指定的一个或多个I/O条件就绪,它就通知进程。这个能力就称之为I/O复用,由select和poll这两个函数支持。

           I/O复用通常应用在下列场合:

    • 当客户处理多个应用场合时(交互式输入和网络套接字);
    • 一个客户同时处理多个套接字;
    • 一个TCP服务器既要处理监听套接字,又要处理已连接套接字;
    • 一个服务器既要处理TCP,又要处理UDP;
    • 一个服务器要处理多个服务或者多个协议。

    二.select函数       

         select函数允许进程指示内核等待多个事件中的任意一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它。

    #include<sys/select.h>
    #include<sys/time.h>
    int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
    

          返回:若有就绪描述符则返回其数目,若超时返回0,若出错返回1。

          头文件<sys/select.h>中定义的FD——SETSIZE常值是数据类型fd_set中的描述符总数,其值通常是1024

          maxfdp1参数指定带测试的描述符个数,它的值是带测试最大描述符加1,描述符0,1,2,...,maxfdp1-1均将被测试。

          中间三个参数readset,writeset和exceptset指定我们要让内核测试读/写和异常条件的描述符。

    void FD_ZERO(fd_set *fdset);         //clear all bits in fdset
    void FD_SET(int fd,fd_set *fdset);   //turn on the bit for fd in fdset
    void FD_CLR(int fd,fd-set *fdset);    //turn off the bit for fd in fdset
    int FD_ISSET(int fd,fd_set *fdset);  //is the bit for fd on in fdset?
    

          其中,描述符集的初始化非常重要

          参数timeout告知内核等待所指定描述符中的任何一个就绪可花多少时间,timeval结构用于指定这段时间的秒数和微妙数。

    struct timeval
    {
         long tv_sec;    //second
         long tv_usec;   //microsecond
    };
    

          这个参数有三种可能:

    • 永远等待下去:仅在有一个描述符准备好I/O时才返回,为此,把该参数设为空;
    • 等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过timeval所指定时间;
    • 根本不等待:检查描述符后立即返回,这个称为轮询(polling),为此,秒数和微妙数必须为0。

    三.异步聊天程序

          写一个TCP异步聊天程序来加深理解。

    服务器代码:

    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<sys/socket.h>
    #include<sys/wait.h>
    #include <unistd.h>
    #include<time.h>
    
    #define MAXSIZE 1024
    #define PORT 8080
    #define BACKLOG 10
    
    int main(int argc,char **argv)
    {
    	int listenfd,connfd;
    	struct sockaddr_in servaddr,cliaddr;
    	socklen_t len;
    	char message[MAXSIZE];
    
    	fd_set rfds;
    //	struct timeval tv;
    	int retval,maxfd=-1;
    
    	if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
    	{
    		perror("socket");
    		exit(1);
    	}
    	else printf("socket create success!
    ");
    
    	bzero(&servaddr,sizeof(servaddr));
    	servaddr.sin_family=AF_INET;
    	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    	servaddr.sin_port=htons(PORT);
    
    	if((bind(listenfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr)))==-1)
    	{
    		perror("bind");
    		exit(1);
    	}
    	else printf("bind success!
    ");
    
    	if(listen(listenfd,BACKLOG)==-1)
    	{
    		perror("listen");
    		exit(1);
    	}
    	else printf("sever is listening!
    ");
    
    	for( ; ; )
    	{
    		printf("等待连接...
    ");
    		len=sizeof(struct sockaddr);
    		if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))==-1)
    		{
    			perror("accept");
    			exit(1);
    		}
    		else printf("客户端:%s: %d
    ",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
    		printf("开始聊天!
    ");
    		for( ; ; )
    		{
    			FD_ZERO(&rfds);
    			FD_SET(0,&rfds);
    			maxfd=0;
    			FD_SET(connfd,&rfds);
    			if(connfd>maxfd) maxfd=connfd;
    			retval=select(maxfd+1,&rfds,NULL,NULL,NULL);
    			if(retval==-1)
    			{
    				printf("select出错!%s",strerror(errno));
    				break;
    			}
    			else if(retval==0)
    			{
    				printf("等待对方输入...
    ");
    				continue;
    			}
    			else
    			{
    				if(FD_ISSET(0,&rfds))
    				{
    					bzero(message,MAXSIZE);
    			        printf("输入:");
    			        fgets(message,MAXSIZE,stdin);
    
    			        if(!strncasecmp(message, "quit", 4))
    			        {
    						printf("终止聊天!
    ");
    				        break;
    			        }
    					else len=send(connfd,message,strlen(message),0);
                        if(len<0) 
    			        {
    						printf("发送失败");
    				        break;
    			        } 
    				}
    				if(FD_ISSET(connfd,&rfds))
    				{
    					bzero(message,MAXSIZE);
    			        len=recv(connfd,message,MAXSIZE,0);
    			        if(len>0) printf("客户端:%s",message);
    			        else 
    					{
    						if(len<0) printf("接受消息失败!
    ");
    			            else printf("客户端不在线!
    ");
    						break;
    					}
    				}
    			}
    		}
            close(connfd);
         	printf("是否退出服务器[Y/N]:");
        	bzero(message,MAXSIZE);
        	fgets(message,MAXSIZE,stdin);
            if(!strncasecmp(message, "Y", 1))
        	{
    	    	printf("服务器已退出!
    ");
    	    	break;
        	}
      	}
    	close(listenfd);
    	return 0;
    }
    

     客户端代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <time.h>
    
    #define MAXSIZE 1024
    #define PORT 8080
    
    int main(int argc, char **argv)
    {
        int sockfd;
        struct sockaddr_in servaddr;
        socklen_t len;
    	fd_set rfds;
    //	struct timeval tv;
    	int retval,maxfd=-1;
    
        char message[MAXSIZE];    
        
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
    	{
            perror("socket");
            exit(1);
        }
    	else printf("socket create success!
    ");
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(PORT);
    
    	inet_aton(argv[1],&servaddr.sin_addr);
    
        if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))==-1)
    	{
            perror("connect");
            exit(1);
        }
    	else printf("conncet success!
    ");
        
        for( ; ; )
    	{
    		FD_ZERO(&rfds);
    		FD_SET(0,&rfds);
    		maxfd=0;
    		FD_SET(sockfd,&rfds);
    		if(sockfd>maxfd) maxfd=sockfd;
    		retval=select(maxfd+1,&rfds,NULL,NULL,NULL);
    		if(retval==-1)
    		{
    			printf("select出错!%s",strerror(errno));
    			break;
    		}
    		else if(retval==0)
    		{
    			printf("等待对方输入...
    ");
    			continue;
    		}
    		else
    		{
    			if(FD_ISSET(sockfd,&rfds))
    			{
    				bzero(message,MAXSIZE);
                    len=recv(sockfd,message,MAXSIZE,0);
                    if(len>0)
                    printf("服务器:%s",message);
                    else
    		        {
    					if(len<0) printf("接受消息失败!
    ");
                        else printf("服务器已退出!
    ");
                        break;    
                    }
    			}
    			if(FD_ISSET(0,&rfds))
    			{
    				bzero(message,MAXSIZE);
                    printf("输入:");
                    fgets(message,MAXSIZE,stdin);
    
                    if(!strncasecmp(message, "quit", 4))
    		        {
    					printf("client 请求终止聊天!
    ");
                        break;
                    }   
                    else len = send(sockfd,message,strlen(message),0);
                    if(len<0)            
                    {
                        printf("消息发送失败!
    ");
                        break;            
                    }
    			}
    		}
    	}
        close(sockfd);
        return 0;
    }
    

     编译:

    gcc -Wall server.c -o server
    gcc -Wall client.c -o client
    

     服务器运行结果:

    ./server 
    socket create success!
    bind success!
    sever is listening!
    等待连接...
    客户端:127.0.0.1: 50235
    开始聊天!
    客户端:
    客户端:你好啊,服务器!
    客户端:我是客户。
    
    输入:你好啊,客户端!
    输入:客户端:
    客户端:Byebye!
    客户端不在线!
    是否退出服务器[Y/N]:Y
    服务器已退出!
    

     客户端运行结果:

    ./client 127.0.0.1
    socket create success!
    conncet success!
    
    输入:你好啊,服务器!
    输入:我是客户端。 
    输入:服务器:
    服务器:你好啊,客户端!
    
    输入:Byebye!
    输入:quit
    输入:client 请求终止聊天!
    
  • 相关阅读:
    二分查找小结
    FZU みねちゃんの修罗场(从一堆出现三次的数中找出出现两次的数)
    《C陷阱与缺陷》杂记
    HDU 1846 Brave Game(巴什博弈)
    【转载】并查集详解
    第七次作业——学末总结
    STL之vector
    fzuoop期中练习
    MFC 文件对话框
    第六次作业——利用MFC实现计算器图形界面以及简单四则运算表达式批处理
  • 原文地址:https://www.cnblogs.com/Rosanna/p/3501605.html
Copyright © 2011-2022 走看看