21xrx.com
2025-03-17 14:25:29 Monday
文章检索 我的文章 写文章
C++ Socket编程之select函数
2023-06-29 22:27:26 深夜i     16     0
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函数,我们建议读者多写一些相关的代码,并深入研究它的底层实现原理。

  
  

评论区