21xrx.com
2024-09-20 05:46:40 Friday
登录
文章检索 我的文章 写文章
C++ Socket编程之select函数
2023-06-29 22:27:26 深夜i     --     --
C++ Socket编程 select函数

在C++ Socket编程中,select函数是一个非常常用的函数,它可以让程序实现多路复用的功能,即同时处理多个客户端的请求。本文将介绍select函数的使用方法和原理。

一、select函数介绍

select函数是一个用于多路复用的系统调用,它可以等待一个或多个文件描述符(套接字)的状态发生变化,一旦有变化就可以立即返回。它常用于同时监听多个套接字的读写事件,从而实现高并发的网络程序。

select函数的原型为:

int select(int nfds, fd_set *readfds, fd_set *writefds,

      fd_set *exceptfds, struct timeval *timeout);

参数说明:

- nfds:被监视的文件描述符的范围,即最大的文件描述符+1;

- readfds:可读的文件描述符集合;

- writefds:可写的文件描述符集合;

- exceptfds:出错的文件描述符集合;

- timeout:select函数的超时时间,如果超时时间为NULL,则表示永久等待。

函数返回:

- 如果有文件描述符状态发生变化或者超时,则返回发生变化的文件描述符的数量,如果出错则返回-1;

- 如果返回0,则表示超时。

二、select函数使用方法

下面,我们通过一个简单的例子来演示select函数的使用方法。


#include <stdio.h>

#include <string.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>

int main(int argc , char *argv[])

{

  int socket_desc , client_sock , c , read_size;

  struct sockaddr_in server , client;

  char client_message[2000];

  // 创建套接字

  socket_desc = socket(AF_INET , SOCK_STREAM , 0);

  if (socket_desc == -1)

  {

    printf("Could not create socket");

  }

  // 准备服务器地址

  server.sin_family = AF_INET;

  server.sin_addr.s_addr = INADDR_ANY;

  server.sin_port = htons( 8888 );

  // 绑定套接字到指定的IP和端口号

  if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)

  {

    perror("bind failed. Error");

    return 1;

  }

  // 开始监听连接请求

  listen(socket_desc , 3);

  // 接受连接请求

  c = sizeof(struct sockaddr_in);

  client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);

  if (client_sock < 0)

  {

    perror("accept failed");

    return 1;

  }

  // 循环等待客户端发送消息

  while(1)

  {

    fd_set rfds;

    struct timeval tv;

    FD_ZERO(&rfds);

    FD_SET(client_sock, &rfds);

    tv.tv_sec = 5;

    tv.tv_usec = 0;

    // 等待事件发生

    int retval = select(client_sock+1, &rfds, NULL, NULL, &tv);

    if (retval == -1)

    {

      perror("select() failed");

      break;

    }

    else if (retval == 0)

    {

      printf("Timeout occurred! No data after 5 seconds.\n");

      break;

    }

    else

    {

      // 读取客户端发送来的消息

      read_size = recv(client_sock , client_message , 2000 , 0);

      if(read_size == -1)

      {

        perror("recv failed");

        return 1;

      }

      printf("Client reply: %s\n", client_message);

    }

  }

  return 0;

}

这个程序实现了一个简单的服务端,在接受到客户端连接之后,循环等待客户端发送消息,并在5秒钟内没有收到消息时跳出循环。

在程序中,我们创建了一个fd_set类型的变量rfds,这个变量用于存储待检测的文件描述符集合。我们使用FD_ZERO宏清零变量,使它初始为空集合,然后使用FD_SET宏将客户端套接字client_sock加入待检测的文件描述符集合rfds中。

接下来,我们在select函数中等待事件发生,如下所示:


// 等待事件发生

int retval = select(client_sock+1, &rfds, NULL, NULL, &tv);

if (retval == -1)

{

  perror("select() failed");

  break;

}

else if (retval == 0)

{

  printf("Timeout occurred! No data after 5 seconds.\n");

  break;

}

else

{

  // 读取客户端发送来的消息

  read_size = recv(client_sock , client_message , 2000 , 0);

  if(read_size == -1)

  {

    perror("recv failed");

    return 1;

  }

  printf("Client reply: %s\n", client_message);

}

在select函数的第一个参数中,我们将待检测的文件描述符范围设置为client_sock+1,这个参数通常设为所有待检测的文件描述符中的最大值加1。在第二个参数中,我们传入待检测的文件描述符集合rfds。在第三和第四个参数中,我们传入了可读和可写的文件描述符集合,因为我们只关注可读事件。在最后一个参数中,我们传入了超时时间结构体。如果在超时时间内没有事件发生,则select函数会返回0。

当select函数返回时,如果返回值小于0,则表示出错;如果返回值为0,则表示超时;如果返回值大于0,则表示有事件发生。在本程序中,我们只关注一个套接字client_sock的可读事件发生情况,如果有可读事件,则使用recv函数读取客户端发送过来的消息。

三、select函数原理

select函数的实现主要依赖于内核的文件描述符表和等待队列,同时使用了轮询方式扫描文件描述符表。

select函数调用时,先将文件描述符集合拷贝到内核中,同时阻塞等待事件的发生。当文件描述符的状态发生变化时,内核会及时通知select函数,使得它不会阻塞在这里。在有事件发生时,select函数返回,通常返回的是待检测文件描述符集合中的套接字数量。接下来,程序可以通过fd_set中的宏来判断那些套接字发生了事件,进而进行响应的处理。

四、总结

通过本文的介绍,我们了解了select函数在C++ Socket编程中的应用,以及它的使用方法和原理。在实际的开发中,我们经常需要使用select函数来实现高并发的网络程序,同时它也是其他更高级的多路复用函数,比如epoll和kqueue的实现基础之一。为了更好的理解和掌握select函数,我们建议读者多写一些相关的代码,并深入研究它的底层实现原理。

  
  

评论区

{{item['qq_nickname']}}
()
回复
回复