• 基本套接字编程(3) -- select篇


    1. I/O复用

    我们学习了I/o复用的基本知识,了解到目前支持I/O复用的系统调用有select、pselect、poll、epoll。而epoll技术以其独特的优势被越来越多的应用到各大企业服务器。(后面将有poll & epoll单独学习笔记)

    基本概念

    IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
    (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
    (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
    (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
    (4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
    (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

    与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

    2. select技术

    select()函数确定一个或多个套接口的状态,本函数用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。

     2.1函数原型

    该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:
    #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
    
    函数参数介绍如下:
    (1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。因为文件描述符是从0开始的。
    (2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
              void FD_ZERO(fd_set *fdset);           //清空集合
              void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中
              void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除
              int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 
    (3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
             struct timeval{
                       long tv_sec;   //seconds
                       long tv_usec;  //microseconds
              };
    这个参数有三种可能:
    (1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
    (2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
    (3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
    有关select更加详细的讲解请参考《Unix网络编程 -- 卷一》第六章 Page127 ~ 142;

    2.2 select原理流程图




    3. TCP回射程序实例

    本例是基本套接字编程(1) -- tcp篇中回射程序的改写,其中server端采用select技术,实现I/O复用,可同时为多个客户程序服务!

    3.1 server.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <time.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <fcntl.h>
    
    
    #define PORT 8888
    #define MAX_LINE 2048
    #define LISTENQ 20
    
    
    int main(int argc , char **argv)
    {
    	int i, maxi, maxfd, listenfd, connfd, sockfd;
    
    	int nready , client[FD_SETSIZE];
    	
    	ssize_t n, ret;
    		
    	fd_set rset , allset;
    
    	char buf[MAX_LINE];
    
    	socklen_t clilen;
    
    	struct sockaddr_in servaddr , cliaddr;
    
    	/*(1) 得到监听描述符*/
    	listenfd = socket(AF_INET , SOCK_STREAM , 0);
    
    	/*(2) 绑定套接字*/
    	bzero(&servaddr , sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	servaddr.sin_port = htons(PORT);
    
    	bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
    
    	/*(3) 监听*/
    	listen(listenfd , LISTENQ);
    
    	/*(4) 设置select*/
    	maxfd = listenfd;
    	maxi = -1;
    	for(i=0 ; i<FD_SETSIZE ; ++i)
    	{
    		client[i] = -1;
    	}//for
    	FD_ZERO(&allset);
    	FD_SET(listenfd , &allset);
    
    	/*(5) 进入服务器接收请求死循环*/
    	while(1)
    	{
    		rset = allset;
    		nready = select(maxfd+1 , &rset , NULL , NULL , NULL);
    		
    		if(FD_ISSET(listenfd , &rset))
    		{
    			/*接收客户端的请求*/
    			clilen = sizeof(cliaddr);
    
    			printf("
    accpet connection~
    ");
    
    			if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0)
    			{
    				perror("accept error.
    ");
    				exit(1);
    			}//if		
    
    			printf("accpet a new client: %s:%d
    ", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port);
    
    			/*将客户链接套接字描述符添加到数组*/
    			for(i=0 ; i<FD_SETSIZE ; ++i)
    			{
    				if(client[i] < 0)
    				{
    					client[i] = connfd;
    					break;
    				}//if
    			}//for
    
    			if(FD_SETSIZE == i)
    			{
    				perror("too many connection.
    ");
    				exit(1);
    			}//if
    
    			FD_SET(connfd , &allset);
    			if(connfd > maxfd)
    				maxfd = connfd;
    			if(i > maxi)
    				maxi = i;
    
    			if(--nready < 0)
    				continue;
    		}//if
    
    		for(i=0; i<=maxi ; ++i)
    		{
    			if((sockfd = client[i]) < 0)
    				continue;
    			if(FD_ISSET(sockfd , &rset))
    			{
    				/*处理客户请求*/
    				printf("
    reading the socket~~~ 
    ");
    				
    				bzero(buf , MAX_LINE);
    				if((n = read(sockfd , buf , MAX_LINE)) <= 0)
    				{
    					close(sockfd);
    					FD_CLR(sockfd , &allset);
    					client[i] = -1;
    				}//if
    				else{
    					printf("clint[%d] send message: %s
    ", i , buf);
    					if((ret = write(sockfd , buf , n)) != n)	
    					{
    						printf("error writing to the sockfd!
    ");
    						break;
    					}//if
    				}//else
    				if(--nready <= 0)
    					break;
    			}//if
    		}//for
    	}//while
    }

    3.2 client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <time.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <fcntl.h>
    
    #define PORT 8888
    #define MAX_LINE 2048
    
    int max(int a , int b)
    {
    	return a > b ? a : b;
    }
    
    /*readline函数实现*/
    ssize_t readline(int fd, char *vptr, size_t maxlen)
    {
    	ssize_t	n, rc;
    	char	c, *ptr;
    
    	ptr = vptr;
    	for (n = 1; n < maxlen; n++) {
    		if ( (rc = read(fd, &c,1)) == 1) {
    			*ptr++ = c;
    			if (c == '
    ')
    				break;	/* newline is stored, like fgets() */
    		} else if (rc == 0) {
    			*ptr = 0;
    			return(n - 1);	/* EOF, n - 1 bytes were read */
    		} else
    			return(-1);		/* error, errno set by read() */
    	}
    
    	*ptr = 0;	/* null terminate like fgets() */
    	return(n);
    }
    
    /*普通客户端消息处理函数*/
    void str_cli(int sockfd)
    {
    	/*发送和接收缓冲区*/
    	char sendline[MAX_LINE] , recvline[MAX_LINE];
    	while(fgets(sendline , MAX_LINE , stdin) != NULL)	
    	{
    		write(sockfd , sendline , strlen(sendline));
    
    		bzero(recvline , MAX_LINE);
    		if(readline(sockfd , recvline , MAX_LINE) == 0)
    		{
    			perror("server terminated prematurely");
    			exit(1);
    		}//if
    
    		if(fputs(recvline , stdout) == EOF)
    		{
    			perror("fputs error");
    			exit(1);
    		}//if
    
    		bzero(sendline , MAX_LINE);
    	}//while
    }
    
    /*采用select的客户端消息处理函数*/
    void str_cli2(FILE* fp , int sockfd)
    {
    	int maxfd;
    	fd_set rset;
    	/*发送和接收缓冲区*/
    	char sendline[MAX_LINE] , recvline[MAX_LINE];
    
    	FD_ZERO(&rset);
    	while(1)
    	{
    		/*将文件描述符和套接字描述符添加到rset描述符集*/
    		FD_SET(fileno(fp) , &rset);	
    		FD_SET(sockfd , &rset);
    		maxfd = max(fileno(fp) , sockfd) + 1;
    		select(maxfd , &rset , NULL , NULL , NULL);
    		
    		if(FD_ISSET(fileno(fp) , &rset))
    		{
    			if(fgets(sendline , MAX_LINE , fp) == NULL)
    			{
    				printf("read nothing~
    ");
    				close(sockfd); /*all done*/
    				return ;
    			}//if
    			sendline[strlen(sendline) - 1] = '';
    			write(sockfd , sendline , strlen(sendline));
    		}//if
    
    		if(FD_ISSET(sockfd , &rset))
    		{
    			if(readline(sockfd , recvline , MAX_LINE) == 0)
    			{
    				
    				perror("handleMsg: server terminated prematurely.
    ");
    				exit(1);			
    			}//if
    			
    			if(fputs(recvline , stdout) == EOF)
    			{
    				perror("fputs error");
    				exit(1);
    			}//if
    		}//if	
    	}//while
    }
    
    int main(int argc , char **argv)
    {
    	/*声明套接字和链接服务器地址*/
        int sockfd;
        struct sockaddr_in servaddr;
    
        /*判断是否为合法输入*/
        if(argc != 2)
        {
            perror("usage:tcpcli <IPaddress>");
            exit(1);
        }//if
    
        /*(1) 创建套接字*/
        if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
        {
            perror("socket error");
            exit(1);
        }//if
    
        /*(2) 设置链接服务器地址结构*/
        bzero(&servaddr , sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(PORT);
        if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0)
        {
            printf("inet_pton error for %s
    ",argv[1]);
            exit(1);
        }//if
    
        /*(3) 发送链接服务器请求*/
        if(connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
        {
            perror("connect error");
            exit(1);
        }//if
    
    	/*调用普通消息处理函数*/
    	str_cli(sockfd);	
    	/*调用采用select技术的消息处理函数*/
    	//str_cli2(stdin , sockfd);
    	exit(0);
    }

    3.3 运行结果

    服务器监听终端:



    两个客户链接服务器:






    注:以上部分理论内容来自参考博客,多谢原博主!
  • 相关阅读:
    Android Canvas save() restore()
    Android Custom View
    Android Canvas drawArc()
    每日一记--2014.9.15
    每日一记--2014.9.14
    每日一记--2014.9.13
    每日一记--2014.9.12
    每天一记--2014.9.11
    每日一记,今天开始
    java中的Iterator和Iterable
  • 原文地址:https://www.cnblogs.com/shine-yr/p/5214707.html
Copyright © 2020-2023  润新知