10.select的使用

一. select简介

       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

      void FD_CLR(int fd, fd_set *set);       // 清除出集合
      int  FD_ISSET(int fd, fd_set *set);      // 判断是否在集合中
      void FD_SET(int fd, fd_set *set);       // 添加进集合中
      void FD_ZERO(fd_set *set);              // 将集合清零


参数1读写异常集合中的文件描述符的最大值加1;

参数2读集合,关心可读事件;

套接口缓冲区有数据可读
对等连接的写一半关闭。即接收到FIN段,读操作将返回0
如果是监听套接口,已完成连接队列不为空时。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

参数3写集合,关心可写事件;

套接口发送缓冲区有空间容纳数据。(连接一旦建立就可写)

对等连接的读一半关闭。即收到RST段之后,再次调用write操作。

套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

参数4异常集合,关心异常事件;用于检查带外数据

套接口存在带外数据(TCP头部 URG标志,16位紧急指针字段)

参数5超时时间结构体


             对于参数2,3,4来说,如果不关心对应事件则设置为NULL即可。注意5个参数都是输入输出参数,即select返回时可能对其进行了修改,比如集合被修改以便标记哪些套接口发生了事件,时间结构体的传出参数是剩余的时间,如果设置为NULL表示永不超时。用select管理多个I/O,select阻塞等待, 一旦其中的一个或多个I/O检测到我们所感兴趣的事件,select函数返回,返回值为检测到的事件个数,并且返回哪些I/O发送了事件,遍历这些事件,进而处理事件。注意当select阻塞返回后,此时调用accept 接收连接是不会阻塞的,直接返回已连接套接字,可以认为是select 提前阻塞了。但此时调用write 还是可能阻塞的,因为需要写入的空间大小可能缓冲区还不满足。

      核心代码修改:

void echo_cli(int sock)
{
    fd_set rset;// fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄
    FD_ZERO(&rset);

    int nready;
    int maxfd;
    int fd_stdin = fileno(stdin); //取得参数stream指定的文件流所使用的文件描述符
    if (fd_stdin > sock)
        maxfd = fd_stdin;
    else
        maxfd = sock;

    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};

    while (1)
    {

        FD_SET(fd_stdin, &rset);// 输入流文件描述符添加到文件描述符中
        FD_SET(sock, &rset);// sock文件描述符添加到集合中
        nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件
        if (nready == -1)// 负值:select错误
            ERR_EXIT("select error");

        if (nready == 0)///  0:等待超时,没有可读写或错误的文件
            continue;
		/ 正值:某些文件可读写或出错
        if (FD_ISSET(sock, &rset)) // 判断sock 是否在集合中,如果在,进行读操作
        {

            int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取
            if (ret == -1)
                ERR_EXIT("read error");
            else if (ret  == 0)   //服务器关闭
            {
                printf("server close\n");
                break;
            }

            fputs(recvbuf, stdout);
            memset(recvbuf, 0, sizeof(recvbuf));
        }

        if (FD_ISSET(fd_stdin, &rset))// 判断fd_stdin 是否在集合中,如果在,进行写操作
        {

            if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
                break;

            writen(sock, sendbuf, strlen(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
    }

    close(sock);
}

客户端的全部代码:

/// echolic.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

#define ERR_EXIT(m) \
		do{ \
			perror(m); \
			exit(EXIT_FAILURE); \
		}while(0)


ssize_t readn(int fd,void *buf,size_t count)
{
	size_t nleft = count ; // 未读取的数据
	ssize_t nread;// 已读取的数据
	char *bufp= (char*)buf;
	while(nleft > 0)
	{
		if( (nread = read(fd,bufp,nleft)) < 0)
		{
			if( errno == EINTR)
				 nread = 0;//  继续读取数据
			else
				return -1;
		}
		else if( nread == 0) // 对方关闭或已经读到eof
			break;
		bufp +=nread;
		nleft -= nread;
	
	}
	return count-nleft;
}

ssize_t writen(int fd,const void *buf,size_t count)
{
	size_t nleft=count;  // 未读取的
	ssize_t nwritten;    // 已读取的
 	char *bufp = (char*)buf;
 	
 	while(nleft > 0)
 	{
 		if((nwritten = write(fd,bufp,nleft)) < 0)
 		{
 			if( errno == EINTR)
 				continue;
 			else
 				return -1;
 		}
 		else if( nwritten == 0)
 			continue;
 		bufp  += nwritten;
 		nleft -= nwritten;
 	}
 	return count;
}


/// recv()只能读取套接字,而不能读取一般文件描述符
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
	while(1)
	{
		int ret = recv(sockfd,buf,len,MSG_PEEK);//MSG_PEEK接收缓冲区的数据,但是并没有清除
		if( ret == -1 && errno == EINTR)
			continue;
		return ret;
	}
}

// 读到'\n' 就返回,加上'\n'一行最多为maxline个字符
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
	int ret;
	int nread;
	char *bufp =(char *) buf;
	int nleft = maxline;
	//int count=0;
	
	while(1)
	{	
		// recv_peek读取缓冲区的字符个数,并放入到bufp缓存里面
		ret = recv_peek(sockfd,bufp,nleft);
		if(ret < 0)
			return ret;// 表示失败
		else if(ret == 0)
			return ret; // 表示对方关闭连接了
			
		nread = ret;
		// 判断接收到字符是否有'\n'
		int i;
		for(i=0; i<nread; ++i)
		{
			if(bufp[i] == '\n')
			{
			    // readn读取数据,这部分缓冲会被清空的
				ret = readn(sockfd,bufp,i+1);
				if(ret != (i+1))
					exit(EXIT_FAILURE);
				return ret; //+ count;
			}
		}
		if( nread > nleft)
			exit(EXIT_FAILURE);
		nleft -= nread;
		ret = readn(sockfd,bufp,nread);
		if(ret != nread)
			exit(EXIT_FAILURE);
		bufp += nread;// 下一次指针偏移	
		//count += nread;
	}
	return -1;
}

/*
void echo_cli(int sock)
{

	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while( fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)  
	{
		writen(sock,sendbuf,strlen(sendbuf));// 需要的注意的的时sizeof(sendbuf)和strlen(recvbuf)不一样的,容易出现混淆
		int ret = readline(sock,recvbuf,1024);///最后一个参数为缓冲区的最大值
		if( ret == -1 )
			ERR_EXIT("readline");
		else if(ret == 0)
		{
			printf("client close \n");
			break;
		}
		fputs(recvbuf,stdout);
		memset(recvbuf,0,sizeof(recvbuf));
		memset(sendbuf,0,sizeof(sendbuf));
	}
    close(sock); 	
}
*/

 核心修改代码
void echo_cli(int sock)
{
    fd_set rset;// fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄
    FD_ZERO(&rset);

    int nready;
    int maxfd;
    int fd_stdin = fileno(stdin); //取得参数stream指定的文件流所使用的文件描述符
    if (fd_stdin > sock)
        maxfd = fd_stdin;
    else
        maxfd = sock;

    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};

    while (1)
    {

        FD_SET(fd_stdin, &rset);// 输入流文件描述符添加到文件描述符中
        FD_SET(sock, &rset);// sock文件描述符添加到集合中
        nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件
        if (nready == -1)// 负值:select错误
            ERR_EXIT("select error");

        if (nready == 0)///  0:等待超时,没有可读写或错误的文件
            continue;
		/ 正值:某些文件可读写或出错
        if (FD_ISSET(sock, &rset)) // 判断sock 是否在集合中,如果在,进行读操作
        {

            int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取
            if (ret == -1)
                ERR_EXIT("read error");
            else if (ret  == 0)   //服务器关闭
            {
                printf("server close\n");
                break;
            }

            fputs(recvbuf, stdout);
            memset(recvbuf, 0, sizeof(recvbuf));
        }

        if (FD_ISSET(fd_stdin, &rset))// 判断fd_stdin 是否在集合中,如果在,进行写操作
        {

            if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
                break;

            writen(sock, sendbuf, strlen(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
    }

    close(sock);
}



int main()
{
	int sock;
	if( (sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
			ERR_EXIT("sock err");
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));

	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
	
	if( connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
			ERR_EXIT("connect error");
	
	struct sockaddr_in localaddr;
	socklen_t addrlen = sizeof(localaddr);
	if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen) < 0)
		ERR_EXIT("getsockname err");
	printf("ip=%s ,port = %d \n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
	
	
	echo_cli(sock);
      
	return 0;
}





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值