select函数

本文摘自:

http://www.cppblog.com/hoolee/archive/2014/03/26/206351.aspx

https://blog.51cto.com/10704527/1782636

https://blog.csdn.net/a445849497/article/details/80512853

http://control.blog.chinaunix.net/uid-30271883-id-5604817.html

https://blog.csdn.net/cstarbl/article/details/7645298

https://blog.csdn.net/luckysym/article/details/7724879

1、select函数介绍

系统提供select函数来实现I/O复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄中有一个或多个发生生了状态改变。

(一个句柄就是你给一个文件,设备,套接字(socket)或管道的一个名字, 以便帮助你记住你正处理的名字, 并隐藏某些缓存等的复杂性。)

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

函数参数及返回值:

  • maxfdp:select监视的文件句柄数,是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1

  • readfds:select监视的可读文件句柄集合。是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中 读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断 是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

  • writefds: select监视的可写文件句柄集合。是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件 中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判 断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

  • exceptfds:select监视的异常文件句柄集合。同上面两个参数的意图,用来监视文件错误异常。

  • timeout:本次select()的超时结束时间。是select的超时时间,这个参数至关重要,它可以使select处于三种状态:

    • 若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
    • 若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
    • timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
  • 返回值:

    • 负值:select错误
    • 正值:某些文件可读写或出错
    • 0:等待超时,没有可读写或错误的文件

2、fd_set结构体

fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd。

在/usr/include/sys/select.h中有select函数和fd_set结构体的定义:

/* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;

/* Some versions of <linux/posix_types.h> define this macros.  */
#undef	__NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))
#define	__FD_ELT(d)	((d) / __NFDBITS)
#define	__FD_MASK(d)	((__fd_mask) (1UL << ((d) % __NFDBITS)))

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];

# define __FDS_BITS(set) ((set)->fds_bits)

#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];

# define __FDS_BITS(set) ((set)->__fds_bits)

#endif
  } fd_set;

/* Maximum number of file descriptors in `fd_set'.  */
#define	FD_SETSIZE		__FD_SETSIZE

#ifdef __USE_MISC
/* Sometimes the fd_set member is assumed to have this type.  */
typedef __fd_mask fd_mask;

/* Number of bits per word of `fd_set' (some code assumes this is 32).  */

# define NFDBITS		__NFDBITS

#endif


/* Access macros for `fd_set'.  */
#define	FD_SET(fd, fdsetp)	__FD_SET (fd, fdsetp)
#define	FD_CLR(fd, fdsetp)	__FD_CLR (fd, fdsetp)
#define	FD_ISSET(fd, fdsetp)	__FD_ISSET (fd, fdsetp)
#define	FD_ZERO(fdsetp)		__FD_ZERO (fdsetp)


__BEGIN_DECLS

/* Check the first NFDS descriptors each in READFDS (if not NULL) for read
   readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS
   (if not NULL) for exceptional conditions.  If TIMEOUT is not NULL, time out
   after waiting the interval specified therein.  Returns the number of ready
   descriptors, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int select (int __nfds, fd_set *__restrict __readfds,
		   fd_set *__restrict __writefds,
		   fd_set *__restrict __exceptfds,
		   struct timeval *__restrict __timeout);

从上面的代码可以将fd_set结构体简化成

typedef struct{
  long int fds_bits[__FD_SETSIZE / (8*(sizeof(long int)))];
}fd_set;

其实fd_set就是一个long int类型的数组,每一位可以代表一个文件描述符。

__FD_SETSIZE在x86_64-linux-gnu/bits/typesizes.h文件中有定义为1024,所以fd_set最多表示1024个文件描述符!

#define __FD_SETSIZE		1024

我的电脑是64位机,查看下表可知64位机器中long int 类型是占8位,也就是说sizeof(long int) = 8,所以fds_bits数组元素个数为:1024/(8*8)=16,每个元素可以代表64个文件描述符。

charchar*shortintunsigned intlongunsigned longlonglongfloatdouble
32位机1字节4字节2字节4字节4字节4字节4字节8字节4字节8字节
64位机1字节8字节2字节4字节4字节8字节8字节8字节4字节8字节

对于fd_set类型通过下面四个宏来操作:

  • void FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
  • void FD_SET(int fd, fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
  • void FD_CLR(int fd, fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。
  • void FD_ISSET(int fd, fd_set *fdset);用于测试指定的文件描述符是否在该集合中。

下面通过代码来验证:

#include <sys/select.h>

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
  fd_set set;

  FD_ZERO(&set);
  
  int i;
  for(i = 0;i < 16;i++)
  {
      printf("set.__fds_bits[%d]:%ld\t",i,set.fds_bits[i]);
  }
  printf("\n/\n");
  FD_SET(10,&set);
  for(i = 0;i < 16;i++)
  {
      printf("set.__fds_bits[%d]:%ld\t",i,set.fds_bits[i]);
  }
  printf("\n/\n");
  FD_ZERO(&set);
  FD_SET(64,&set);
  for(i = 0;i < 16;i++)
  {
    printf("set.__fds_bits[%d]:%ld\t",i,set.fds_bits[i]);
  }
}

结果如下图:

在这里插入图片描述

FD_SET(10,&set);将set的第10位置1,也就是set.fds_bits[0]为1024;

FD_SET(64,&set);将set的第64位置1,也就是set.fds_bits[1]为1。

3、timeval结构体

timeval结构体用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。在/usr/include/bits/types/struct_timeval.h文件中定义:

struct timeval
{
  __time_t tv_sec;		/* Seconds.  */
  __suseconds_t tv_usec;	/* Microseconds.  */
};

这个结构体的精度可以精确至百万分之1秒。

4、select 在socket的应用

可以使用select函数来检查创建的socket句柄的状态。

select函数提供了一种方法,使得程序在操作socket时(如recv操作),无需因阻塞而等待直至超时。特别是在一个线程中操作多个socket时,对多个socket逐一操作直至超时将浪费大量时间。select机制则是同时对多个socket句柄进行监控,一旦存在可操作的socket,函数及返回并通知程序。

举例:socket服务端使用select函数监听客户端发送来的消息

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>

typedef int NativeSocket;

#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif

#ifndef INVALID_SOCKET
#define INVALID_SOCKET  static_cast<NativeSocket>(-1)
#endif

int main(int argc, char** argv)
{
    //创建套节字
    int socket = ::socket(AF_INET, SOCK_STREAM, 0); 
    if (socket == INVALID_SOCKET){
        std::cout << "Failed to create server socket." << std::endl;
        return -1;
    }

    //设置SO_REUSEADDR选项,使得端口释放后立即就可以被再次使用
    int nEnabled = 1;
    ::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR,
            reinterpret_cast<const char*>(&nEnabled), sizeof(nEnabled));

    //绑定这个套节字到一个本地地址
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(9090);
    if (::bind(socket, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
        printf("bind failed\n");
        return -1;
    }

    //进入监听模式
    if (::listen(socket, 5) == SOCKET_ERROR) {
        printf("listen failed\n");
        return -1;
    }

    int accepted = 0;
    int client_socket = -1;
    while(accepted == 0) {
        //接受客户的连接请求
        struct sockaddr_in addr = {0};
        socklen_t nSize = sizeof(addr);
        client_socket = ::accept(socket, reinterpret_cast<sockaddr*>(&addr), &nSize);
        if (client_socket == INVALID_SOCKET) {
            return -1;
        }
        char sTmp[64]; sTmp[0] = '\0'; sTmp[63] = '\0';
        const char* sPeerAddress = inet_ntop(AF_INET, &addr.sin_addr, sTmp, 63);
        std::cout << "client ip = " << sPeerAddress << std::endl;
        accepted ++;
    }

    fd_set set;
    FD_ZERO(&set);
    FD_SET(client_socket, &set);
    struct timeval timeout = { 10, 0 };
    while(true) {
        if (select(client_socket+1, &set, NULL, NULL, &timeout) != SOCKET_ERROR) {
            // 从客户端接收数据
            char buff[256] ;
            int nRecv = ::recv(client_socket, buff, 256, 0); 
            if (nRecv > 0) {
                buff[nRecv] = '\0';
                printf(" 接收到数据:%s\n", buff);
            }
        }
    }

    // 关闭同客户端的连接
    ::close(client_socket);
    // 关闭监听套节字
    ::close(socket);
    return 0;
}

客户端:

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdio.h>
#include <cassert>
#include <unistd.h>
#include <fcntl.h>

typedef int NativeSocket;

#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif

#ifndef INVALID_SOCKET
#define INVALID_SOCKET  static_cast<NativeSocket>(-1)
#endif

int main(int argc, char** argv)
{
    //创建套节字
    int socket = ::socket(AF_INET, SOCK_STREAM, 0);
        if (socket == INVALID_SOCKET){
        std::cout << "Failed to create client socket." << std::endl;
        return -1;
    }

    //连接server
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("172.16.82.52");
    addr.sin_port = htons(9090);
    if(::connect(socket, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
        printf(" Failed connect() \n");
        return 0; 
    }

    char buff[256];
    char szText[256];
    while(true)
    {
        std::cin.getline(szText, 256);
        szText[255] = '\0';
        ::send(socket, szText, strlen(szText), 0) ;
    }

    // 关闭套节字
    ::close(socket);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_34214088

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值