关于socket编程中FD_XXX以及select函数的理解

文章目录

  • 01 | 宏接口定义
  • 02 | 使用方法
  • 03 | 服务端代码示例

在这里插入图片描述

学习socket编程的时候看到很多FD开头的宏定义和函数,这里记录一下这些宏定义和函数的含义及处理流程

01 | 宏接口定义

  1. fd_set

    fd_set 是一种表示文件描述符的集合类型,在socket编程中,这种类型有三种不同的集合(可读、可写、错误)

  2. FD_ZERO() | FD_CLR()

    • FD_ZERO(fd_set* fdset):把当前描述符集合 fd_set 中所有位的数字都置为0

    • FD_CLR(int fd, fd_set* fdset):清楚所绑定的联系,将 fd 从 fd_set 集合中清除,类似于链表的节点删除(后续节点会填补被删除节点)

  3. FD_SET()

    FD_SET(int fd, fd_set* fdset):实现了句柄和描述符集合的联系,可以把已打开的 fd(句柄)加入到描述符集合 fd_set 中

  4. select()

    用于查询所有描述符(句柄)所处的状态,获取当中可读可写可用的描述符个数

    int select(int maxfdpl, fd_set *restrict readfds,
    		fd_set *restrict writefds,fd_set *restrict exceptfds,
    		struct timeval *restrict typfr);
    // 返回值:准备就绪的描述符数目;若超时返回0,出错则返回-1
    

    参数说明:

    • maxfdpl:最大描述符编号 + 1,即选择需要关注的描述符个数,一般为环境中可能打开的描述符个数加一

    • readfds | writefds | exceptfds:这三个指向描述符的指针,分别代表了所要关心的 可读描述符集 | 可写描述符集 | 出错描述符集

    • typfr:超时时间

  5. FD_ISSET()

    FD_ISSET(int fd, fd_set* fdset):判断加入的 fd 是否还存在于描述符集合 fd_set 中,即判断打开的 fd 是否可用。不存在或不可用则返回0

02 | 使用方法

总体使用流程就是:通过调用 FD_SERO() 将一个 fd_set 变量的所有位设置为0(如果要开启描述符集合中的一位,可以调用 FD_SET();如果要清楚刚才设置的位则可以调用 FD_CLR());再通过调用 select() 对描述符集合进行筛选,得到当前可用描述符个数;最后可以调用 FD_ISSET() 测试当前描述符集合中的一个指定位是否已打开可用

简要流程如下

  1. 初始化一个 fd_set 类型的描述符集合

    fd_set server_fd;
    FD_ZERO(&server_fd);
    
  2. 把 socket调用打开的 fd 放入到已初始化的描述符集合中

    FD_SET(socket_fd, &server_fd);
    
  3. 通过调用 select() 查询这个描述符集合中的所有 fd 状态,并清除描述符集合中 不可读 | 不可写 的 fd,或者说选出 可读 | 可写 的 fd

    int max_fd = -1;
    if (max_fd < socket_fd)
    {
    	max_fd = socket_fd;
    }
    if (select(max_fd + 1, &server_fd, NULL, NULL, &timeout) < 0)
    {
    	return;
    }
    
  4. 判断当前打开的 fd 是否还存在于描述符集合中

    if (FD_ISSET(socketfd, &server_fd))
    {
    // To do
    }
    
  5. 上述步骤完成后,还可以再进行错误处理判断,防止后续错误

    int iRet = 0; 
    int len = sizeof(iRet); 
    if (getsockopt(m_sock, SOL_SOCKET, SO_ERROR, (char*)&iRet, &len) == SOCKET_ERROR) 
    { 
    	return; 
    } 
    

03 | 服务端代码示例

#include
#include
#include
#include
#include
#include
#include
#include
#include

int main()
{
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd < 0)
	{
		perror("socket error");
		return -1;
	}

	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
	if(ret < 0)
	{
		perror("bind error");
		return -1;
	}

	listen(lfd, 128);

	fd_set readset;
	fd_set tmpset;
	FD_ZERO(&readset);
	FD_SET(lfd, &readset);

	int nready;
	int maxfd = lfd;
	int cfd;
	int n;
	char buf[1024];
	while(1)
	{
		tmpset = readset;
		nready = select(maxfd+1, &tmpset, NULL, NULL, NULL);
		if(nready <= 0)
		{
			continue;
		}

		//有客户端连接请求到来
		if(FD_ISSET(lfd, &tmpset))
		{
			cfd = accept(lfd, NULL, NULL);
			FD_SET(cfd, &readset);
			maxfd = cfd > maxfd? cfd : maxfd;
			//说明只有监听描述符被置为1,后面可跳过
			if(--nready == 0)
			{
				continue;
			}
		}

		//有数据发来
		//先有的lfd,后有的cfd
		for(int i=lfd+1; i<=maxfd;i++)
		{
			if(FD_ISSET(i, &tmpset))
			{
				memset(buf, 0, sizeof(buf));
				n = read(cfd, buf, sizeof(buf));
				if(n <= 0)
				{
					close(i);
					FD_CLR(i, &readset);
					printf("read error or client close\n");
					continue;
				}
				printf("n == [%d], buf == [%s]\n", n, buf);

				for(int j=0; j<n; j++)
				{
					buf[j] = toupper(buf[j]);
				}

				write(i, buf, n);
			}
		}
	}

	close(lfd);
	return 0;
}
各位大佬点点关注,点赞,收藏,有空的时候再回来看看,谢谢

你可能感兴趣的:(网络编程,linux,运维,服务器,计算机网络)