MST

星途 面试题库

面试题:网络编程之非阻塞I/O模型下Socket连接管理基础

在非阻塞I/O模型下,简述如何建立一个Socket连接,并且如何处理连接过程中可能出现的EAGAIN或EWOULDBLOCK错误?
18.8万 热度难度
后端开发网络编程

知识考点

AI 面试

面试题答案

一键面试
  1. 建立Socket连接步骤
    • 创建Socket:使用socket()函数创建一个套接字,指定协议族(如AF_INET用于IPv4)、套接字类型(如SOCK_STREAM用于TCP连接)和协议(通常为0)。例如在C语言中:
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        // 处理错误
    }
    
    • 设置非阻塞模式:使用fcntl()函数将套接字设置为非阻塞模式。在C语言中:
    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags == -1) {
        // 处理错误
    }
    if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
        // 处理错误
    }
    
    • 绑定地址:如果是服务器端,使用bind()函数将套接字绑定到特定的地址和端口。在C语言中:
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        // 处理错误
    }
    
    • 监听连接(服务器端):如果是服务器端,使用listen()函数开始监听连接。在C语言中:
    if (listen(sockfd, BACKLOG) == -1) {
        // 处理错误
    }
    
    • 发起连接(客户端):如果是客户端,使用connect()函数发起连接。在C语言中:
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
    int conn_result = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (conn_result == -1 && (errno == EINPROGRESS || errno == EAGAIN || errno == EWOULDBLOCK)) {
        // 处理连接中错误,如下面所述
    }
    
  2. 处理EAGAIN或EWOULDBLOCK错误
    • 客户端:当connect()返回 -1且errnoEAGAINEWOULDBLOCK时,说明连接正在进行中。可以通过以下方式处理:
      • 使用selectpollepoll:这些I/O多路复用机制可以监听套接字的可写事件。例如使用select
      fd_set write_fds;
      FD_ZERO(&write_fds);
      FD_SET(sockfd, &write_fds);
      struct timeval timeout;
      timeout.tv_sec = 5; // 设置超时时间
      timeout.tv_usec = 0;
      int sel_result = select(sockfd + 1, NULL, &write_fds, NULL, &timeout);
      if (sel_result == -1) {
          // 处理select错误
      } else if (sel_result == 0) {
          // 连接超时处理
      } else {
          int err;
          socklen_t errlen = sizeof(err);
          if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
              // 处理getsockopt错误
          }
          if (err == 0) {
              // 连接成功处理
          } else {
              // 连接失败处理,根据错误码处理
          }
      }
      
    • 服务器端:当accept()返回 -1且errnoEAGAINEWOULDBLOCK时,说明当前没有新的连接到达,此时可以继续通过selectpollepoll监听是否有新连接到来,然后再次调用accept()。例如使用epoll
      int epollfd = epoll_create1(0);
      if (epollfd == -1) {
          // 处理epoll_create1错误
      }
      struct epoll_event ev;
      ev.events = EPOLLIN;
      ev.data.fd = sockfd;
      if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
          // 处理epoll_ctl错误
      }
      struct epoll_event events[MAX_EVENTS];
      int num_events = epoll_wait(epollfd, events, MAX_EVENTS, -1);
      for (int i = 0; i < num_events; i++) {
          if (events[i].data.fd == sockfd) {
              int clientfd = accept(sockfd, NULL, NULL);
              if (clientfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
                  continue;
              } else if (clientfd == -1) {
                  // 处理其他accept错误
              } else {
                  // 处理新连接
              }
          }
      }