select 模型代码详解

一、select 的第一个参数(监视的范围)

在这里插入图片描述
  这个参数用来表示监视的位图的大小,也就是select 监视的 socket 的范围。我们知道 FD_SET 这个集合可以存放 1024 个socket,但是我们并不是都将这个集合放满了。这个集合就是存放了一些socket,因此在监视的时候,只要监视那个范围就行了,这样就提高了效率。或者说,这个值是监视的位图的范围,扫描位图的范围。

这个值固定为1024也行,只不过效率太低。
在这里插入图片描述

这个就有点像去放羊,有一个草原,放养的时候,我只是把羊集中放到草原中的一块区域。然后我监视羊的时候不是监视整个草原,只是监视那一片区域,这样就提高了效率。

二、第二个参数

在这里插入图片描述
  select 可以监听多个集合,这个是读集合。当有事件发生,就从这个集合将事件读取出来;比如有新的客户端连接上来或者客户端有报文发送过来。

三、第三个参数

在这里插入图片描述
  这个参数是写的集合,如果我们要往socket写数据,也可以用 select,但是一般比较少用。一般填空

往 socket 写数据的话,我们直接调用写函数来完成。为什呢?目前我们没有听说过 write 函数会被阻塞 ,等待。就是速度比较快

其实这个函数也会产生阻塞,当数据量很大的时候 。但是内核读取数据交给网卡的速度很快,基本上看不到等待,就算产生等待了也可以忽略。
在这里插入图片描述

四、第四个参数

在这里插入图片描述
  产生异常的集合,一般不需要。主要是与外围设备有关,一般这网络服务开发中填空。

五、第五个参数(超时的机制)

在这里插入图片描述
  这个是超时的机制。

六、函数返回值

1.成功的返回

1.1 成功的返回的含义

  成功的话,返回值的是监视的集合中,发生事件的socket的个数。如果监视的是多个集合,不管是哪个集合发生了事件,都返回。

在这里插入图片描述

1.2 FD_ISSET() 判断是哪个socket发生事件

注意一点:select 成功的返回值是发生事件的socket 的个数,并不是具体告诉你是哪个socket 发生了事件。所以我们一般要去遍历集合,找出是哪个socket 发生的事件,使用FD_ISSET() 函数。
在这里插入图片描述

1.3 FD_ISSET() 的返回值

在这里插入图片描述
返回值只有0和1,是这个socket发生事件了就返回1.
在这里插入图片描述

2.错误的返回值

在这里插入图片描述
  当发生错误时,返回-1;返回-1的情况,一般是系统内存满了;给了错误的集合,错误的集合在开发的时候可能会遇到;捕捉到了信号
在这里插入图片描述

3.演示错误的集合

  我们知道,程序一开始会将监听的 socket 放到集合中,也就是读的集合,因为当有客户连接上来,要读取出来。

3.1 正常的情况

正常的情况是下面这样的,运行没有错误
在这里插入图片描述

3.2 错误的情况

当我们将一个随便的值(20)放到读集合中,但是这个值肯定是非法的。
在这里插入图片描述
运行程序:
在这里插入图片描述

4.捕捉到了信号

  select函数会阻塞,如果给它发送一个信号,就是说select函数能捕捉信号。

(1)运行程序
在这里插入图片描述
(2)发送信号
在这里插入图片描述

(3)查看结果

在这里插入图片描述
在这里插入图片描述

5.返回0的情况(超时)

  返回0,就是超时了。

(1)设置超时的时间为5s,如果5秒后没有客户端连接上来,或者说没有事件发生,就返回0.这里我们用监听的socket测试,所以是客户端连接的问题。
在这里插入图片描述
(2)启动程序,5s后超时了
在这里插入图片描述

七、利用select函数来实现tcp的超时机制

  通常是讲tcp中的通信的socket,放到select函数中,从而到达tcp的超时机制。
在这里插入图片描述

八、pselect 函数

  select 函数会被信号中断,所以就将select 函数改进了一下,增加了一个参数(sigset),这个参数就是函数在运行的时候可以屏蔽的信号。

  但这个函数不常用。
在这里插入图片描述

九、select 服务端代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>

// 初始化服务端的监听端口。
int initserver(int port);

int main(int argc,char *argv[])
{
  if (argc != 2)
  {
    printf("usage: ./tcpselect port\n"); return -1;
  }

  // 初始化服务端用于监听的socket。
  int listensock = initserver(atoi(argv[1]));
  printf("listensock=%d\n",listensock);

  if (listensock < 0)
  {
    printf("initserver() failed.\n"); return -1;
  }

  fd_set readfdset;  // 读事件的集合,包括监听socket和客户端连接上来的socket。
  int maxfd;  // readfdset中socket的最大值。

  // 初始化结构体,把listensock添加到集合中。
  FD_ZERO(&readfdset);

  FD_SET(listensock,&readfdset);
  maxfd = listensock;

  while (1)
  {
    // 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。
    fd_set tmpfdset = readfdset;

    int infds = select(maxfd+1,&tmpfdset,NULL,NULL,NULL);
    // printf("select infds=%d\n",infds);

    // 返回失败。
    if (infds < 0)
    {
      printf("select() failed.\n"); perror("select()"); break;
    }

    // 超时,在本程序中,select函数最后一个参数为空,不存在超时的情况,但以下代码还是留着。
    if (infds == 0)
    {
      printf("select() timeout.\n"); continue;
    }

    // 检查有事情发生的socket,包括监听和客户端连接的socket。
    // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。
    for (int eventfd=0; eventfd <= maxfd; eventfd++)
    {
      if (FD_ISSET(eventfd,&tmpfdset)<=0) continue;

      if (eventfd==listensock)
      { 
        // 如果发生事件的是listensock,表示有新的客户端连上来。
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
        if (clientsock < 0)
        {
          printf("accept() failed.\n"); continue;
        }

        printf ("client(socket=%d) connected ok.\n",clientsock);

        // 把新的客户端socket加入集合。
        FD_SET(clientsock,&readfdset);

        if (maxfd < clientsock) maxfd = clientsock;

        continue;
      }
      else
      {
        // 客户端有数据过来或客户端的socket连接被断开。
        char buffer[1024];
        memset(buffer,0,sizeof(buffer));

        // 读取客户端的数据。
        ssize_t isize=read(eventfd,buffer,sizeof(buffer));

        // 发生了错误或socket被对方关闭。
        if (isize <=0)
        {
          printf("client(eventfd=%d) disconnected.\n",eventfd);

          close(eventfd);  // 关闭客户端的socket。

          FD_CLR(eventfd,&readfdset);  // 从集合中移去客户端的socket。

          // 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
          if (eventfd == maxfd)
          {
            for (int ii=maxfd;ii>0;ii--)
            {
              if (FD_ISSET(ii,&readfdset))
              {
                maxfd = ii; break;
              }
            }

            printf("maxfd=%d\n",maxfd);
          }

          continue;
        }

        printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);

        // 把收到的报文发回给客户端。
        write(eventfd,buffer,strlen(buffer));
      }
    }
  }

  return 0;
}

// 初始化服务端的监听端口。
int initserver(int port)
{
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if (sock < 0)
  {
    printf("socket() failed.\n"); return -1;
  }

  // Linux如下
  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
  setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);

  struct sockaddr_in servaddr;
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(port);

  if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
  {
    printf("bind() failed.\n"); close(sock); return -1;
  }

  if (listen(sock,5) != 0 )
  {
    printf("listen() failed.\n"); close(sock); return -1;
  }

  return sock;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值