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 请求终止聊天!
原文地址:https://www.cnblogs.com/Rosanna/p/3501605.html