C++ socket通信遇到的问题及解决方法

 更新时间:2023年08月28日 09:04:23   作者:BoBo玩ROS  
这篇文章主要介绍了C++ socket通信遇到的问题,通过代码修改来解决这个问题,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下

为了解决这两个问题,你可以对代码进行以下修改:

1.处理socket断开的情况:

在recv函数返回0的判断分支中,你可以在断开连接时进行一些处理,例如打印提示信息或进行重连操作。

if (ret == 0) {
  printf("Remote socket close!!!\n");
  // 处理socket断开,例如进行重连操作
  Reconnect();
  break;
}

你可以在Reconnect函数中实现重新连接的逻辑。

2.处理socket连接没有发送消息的情况:

在接收数据失败的判断分支中,你可以添加一个超时机制,如果在一定时间内没有接收到数据,就认为连接没有发送消息。

if (ret < 0) {
  if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR)) {
    std::this_thread::sleep_for(std::chrono::microseconds(10));
    continue;
  }
  if (errno == EWOULDBLOCK || errno == EAGAIN) {
    // 没有数据可读,可以继续执行其他任务
    printf("Failed to receive data!!!\n");
    // 处理连接没有发送消息的情况,例如重新发送消息或断开连接
    HandleNoMessage();
    continue;
  } else {
    printf("Remote socket error!!!\n");
    break;
  }
}

你可以在HandleNoMessage函数中实现处理连接没有发送消息的逻辑。

请注意,以上只是一种修改代码的思路,具体的实现方式可能需要根据你的需求和业务逻辑来进行调整。

要将这段C++代码改为非阻塞执行,需要使用非阻塞的套接字操作和事件循环机制。下面是修改后的代码:

void NtripServer::ThreadHandler(void) {
  int ret;
  char recv_buffer[1024] = {};
  thread_is_running_ = true;
  // 将socket_fd_设置为非阻塞模式
  if (SetNonBlocking(socket_fd_) == -1) {
    printf("Failed to set socket to non-blocking mode\n");
    return;
  }
  while (thread_is_running_) {
    ret = recv(socket_fd_, recv_buffer, sizeof(recv_buffer), 0);
    if (ret == 0) {
      printf("Remote socket close!!!\n");
      break;
    } else if (ret < 0) {
      if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
        printf("Remote socket error!!!\n");
        break;
      }
    } else {
      // 处理接收到的数据
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
  }
  close(socket_fd_);
  socket_fd_ = -1;
  thread_is_running_ = false;
  service_is_running_ = false;
}

在修改后的代码中,我们首先调用SetNonBlocking函数将socket_fd_设置为非阻塞模式。然后,在while循环中,我们使用recv函数接收数据,并根据返回值进行处理。

如果recv返回值为0,表示远程socket关闭,我们打印相应的信息并跳出循环。

如果recv返回值小于0,我们首先判断errno是否为EAGAIN、EWOULDBLOCK或EINTR。如果不是这些错误码,表示发生了其他错误,我们打印相应的信息并跳出循环。

如果recv返回值大于0,表示成功接收到数据,我们可以在代码中添加处理接收到的数据的逻辑。

在每次循环结束后,我们使用std::this_thread::sleep_for函数暂停10毫秒,以避免忙等待。

最后,在循环结束后,我们关闭socket_fd_并将其置为-1,同时将thread_is_running_和service_is_running_标志置为false,表示线程已停止。

是的,SetNonBlocking函数需要自定义。下面是一个示例的SetNonBlocking函数的实现:

int SetNonBlocking(int socket_fd) {
  int flags = fcntl(socket_fd, F_GETFL, 0);
  if (flags == -1) {
    return -1;
  }
  if (fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
    return -1;
  }
  return 0;
}

这个函数的实现与之前提到的设置socket为非阻塞模式的代码相同。它首先使用fcntl函数获取socket_fd的文件描述符标志,并将返回值保存在变量flags中。如果获取失败,函数返回-1。

然后,它使用fcntl函数的F_SETFL命令来修改socket_fd的文件描述符标志。修改的方式是将O_NONBLOCK标志添加到原有标志中,使用位运算符|进行操作。如果修改失败,函数返回-1。

如果设置成功,函数返回0。

下面是修改后的完整代码示例:

#include <iostream>
#include <thread>
#include <chrono>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
class NtripServer {
public:
  NtripServer() : socket_fd_(-1), thread_is_running_(false), service_is_running_(false) {}
  ~NtripServer() {}
  void Start() {
    if (service_is_running_) {
      std::cout << "Service is already running!" << std::endl;
      return;
    }
    // 创建socket
    socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd_ == -1) {
      std::cout << "Failed to create socket!" << std::endl;
      return;
    }
    // 设置socket选项
    int optval = 1;
    setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    // 绑定地址和端口
    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(1234);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(socket_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
      std::cout << "Failed to bind address!" << std::endl;
      close(socket_fd_);
      socket_fd_ = -1;
      return;
    }
    // 监听连接
    if (listen(socket_fd_, 5) == -1) {
      std::cout << "Failed to listen!" << std::endl;
      close(socket_fd_);
      socket_fd_ = -1;
      return;
    }
    // 启动线程处理连接
    service_is_running_ = true;
    std::thread thread(&NtripServer::ThreadHandler, this);
    thread.detach();
  }
  void Stop() {
    if (!service_is_running_) {
      std::cout << "Service is not running!" << std::endl;
      return;
    }
    service_is_running_ = false;
  }
private:
  int socket_fd_;
  bool thread_is_running_;
  bool service_is_running_;
  void ThreadHandler() {
    int ret;
    char recv_buffer[1024] = {};
    thread_is_running_ = true;
    // 将socket_fd_设置为非阻塞模式
    if (SetNonBlocking(socket_fd_) == -1) {
      std::cout << "Failed to set socket to non-blocking mode" << std::endl;
      return;
    }
    while (thread_is_running_) {
      ret = recv(socket_fd_, recv_buffer, sizeof(recv_buffer), 0);
      if (ret == 0) {
        std::cout << "Remote socket close!!!" << std::endl;
        break;
      } else if (ret < 0) {
        if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
          std::cout << "Remote socket error!!!" << std::endl;
          break;
        }
      } else {
        // 处理接收到的数据
        std::cout << "Received data: " << recv_buffer << std::endl;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    close(socket_fd_);
    socket_fd_ = -1;
    thread_is_running_ = false;
    service_is_running_ = false;
  }
  int SetNonBlocking(int socket_fd) {
    int flags = fcntl(socket_fd, F_GETFL, 0);
    if (flags == -1) {
      return -1;
    }
    if (fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
      return -1;
    }
    return 0;
  }
};
int main() {
  NtripServer server;
  server.Start();
  // 服务运行一段时间后停止
  std::this_thread::sleep_for(std::chrono::seconds(10));
  server.Stop();
  return 0;
}

在这个示例代码中,我们创建了一个NtripServer类来管理Ntrip服务。在Start函数中,我们创建了一个socket,并设置选项、绑定地址和端口,然后开始监听连接。接着,我们启动一个线程来处理连接,线程的入口函数是ThreadHandler。

在ThreadHandler函数中,我们将socket_fd_设置为非阻塞模式,并使用recv函数接收数据。根据返回值进行处理,如果返回值为0,表示远程socket关闭;如果返回值小于0,并且errno不是EAGAIN、EWOULDBLOCK或EINTR,表示发生了其他错误;如果返回值大于0,表示成功接收到数据,我们可以在代码中添加处理接收到的数据的逻辑。

在主函数中,我们创建了一个NtripServer对象,并调用Start函数启动服务。然后,让服务运行一段时间后调用Stop函数停止服务。

请注意,这只是一个简单的示例代码,实际应用中可能需要进行更多的错误处理和逻辑处理。对于Linux系统,可以使用fcntl函数来设置socket为非阻塞模式。在上述代码中,SetNonBlocking函数就是用来设置socket为非阻塞的。具体来说,它通过调用fcntl函数,使用F_GETFL命令获取socket的当前标志位,然后将O_NONBLOCK标志位加入到标志位中,再次调用fcntl函数,使用F_SETFL命令将新的标志位设置回socket。

这样设置之后,socket的recv函数将变为非阻塞模式,即不会一直等待数据到达,而是立即返回。如果没有数据到达,recv函数将返回-1,并且errno会被设置为EAGAIN或EWOULDBLOCK。这样,我们可以在代码中根据返回值和errno来判断是否有数据到达,并进行相应的处理。如果在SetNonBlocking函数中设置socket为非阻塞模式失败,可以在调用该函数的地方进行错误处理。可以根据具体情况选择合适的错误处理方式,例如打印错误信息、记录日志、关闭socket等。以下是一个示例的错误处理代码:

if (SetNonBlocking(socket_fd) == -1) {
  std::cout << "Failed to set socket to non-blocking mode" << std::endl;
  // 处理错误,例如关闭socket
  close(socket_fd);
  return;
}

在上述代码中,如果SetNonBlocking函数返回-1,表示设置socket为非阻塞模式失败。我们可以打印错误信息,然后进行相应的错误处理,例如关闭socket。这样可以确保在设置失败的情况下,不会继续使用这个socket进行后续操作。

相关文章

  • C++ 读取文件内容到指定类型的变量方法

    C++ 读取文件内容到指定类型的变量方法

    今天小编就为大家分享一篇C++ 读取文件内容到指定类型的变量方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • 详解C++数组和数组名问题(指针、解引用)

    详解C++数组和数组名问题(指针、解引用)

    这篇文章主要介绍了详解C++数组和数组名问题(指针、解引用),指针的实质就是个变量,它跟普通变量没有任何本质区别,指针本身是一个对象,同时指针无需在定义的时候赋值,具体内容详情跟随小编一起看看吧
    2021-09-09
  • C++中构造函数的参数缺省的详解

    C++中构造函数的参数缺省的详解

    这篇文章主要介绍了C++中构造函数的参数缺省的详解的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下
    2017-10-10
  • C++ 实现高性能HTTP客户端

    C++ 实现高性能HTTP客户端

    HttpClient可以实现所有HTTP的方法,通过API传输接收HTTP消息。本文详细讲解了HttpClient,以及如何运用C++实现HTTP客户端,感兴趣的朋友可以参考一下
    2021-08-08
  • C语言rand和srand函数使用方法介绍

    C语言rand和srand函数使用方法介绍

    rand()函数用来产生随机数,但是,rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。srand()用来设置rand()产生随机数时的随机数种子。参数seed是整数,通常可以利用time(0)或geypid(0)的返回值作为seed
    2023-02-02
  • C语言 if else 语句详细讲解

    C语言 if else 语句详细讲解

    本文主要介绍C语言中的if else,这里详细介绍了if else 语句并提供了简单的示例代码,希望能帮助编程入门的小伙伴学习
    2016-07-07
  • c++基础算法动态DP解决CoinChange问题

    c++基础算法动态DP解决CoinChange问题

    这篇文章主要为大家介绍了c++基础算法如何利用动态DP来解决Coin Change的问题示例过程,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-10-10
  • Dev C++ 安装及使用方法(图文教程)

    Dev C++ 安装及使用方法(图文教程)

    Dev C++ 是一款非常好用,简约的C/C++开发工具,本文主要介绍了Dev C++ 安装及使用方法(图文教程),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • C++Lambda表达式详解

    C++Lambda表达式详解

    这篇文章主要介绍了C++中的Lambda表达式详解,本文讲解了基本语法、Lambda的使用等内容,需要的朋友可以参考下,希望能够给你带来帮助
    2021-10-10
  • C++聚合体初始化aggregate initialization详细介绍

    C++聚合体初始化aggregate initialization详细介绍

    这篇文章主要介绍了C++聚合体初始化aggregate initialization,C++有很多初始化对象的方法。其中之一叫做 聚合体初始化(aggregate initialization) ,这是聚合体专有的一种初始化方法
    2023-02-02

最新评论