c++ - 通过 system() 调用启动另一个程序会阻塞套接字

标签 c++ sockets boost-asio system-calls

我在谷歌和 StackOverflow 上搜索了与此类似的内容,但最接近的是 C 代码,但情况并不相同......

我有一个程序通过 cstdlib 的 system() 调用脚本启动另一个程序并且一切正常,问题是当我必须测试新代码时,所以我停止执行(Crtl+C 和 kill -9 pid produce同样的错误),编译新的可执行文件并尝试再次运行它,那时我收到套接字已在使用中的消息。

我的程序使用接受器并等待连接(它一次处理一个)。它可以通过一个脚本检索/发送文件并启动/停止另一个程序。如果我只是检索/发送文件,则套接字未锁定,但如果执行了任何 system() 调用,则套接字将被锁定,并且在关闭它后我无法运行该程序。

我真的不认为这与实际的 asio 部分有什么关系,因为它工作正常,但这是我定义端点的方式:

ba::ip::tcp::endpoint endpoint(ba::ip::address_v4::any(), listen_port);

其中 ba 是 boost::asio 的缩写。 和 SSL:

ba::ip::tcp::acceptor acceptor_(*io,endpoint);
ba::ssl::context context_(*io, ba::ssl::context::sslv23);

context_.set_options( ba::ssl::context::default_workarounds |
                      ba::ssl::context::no_sslv2 |
                      ba::ssl::context::single_dh_use);
//Rest of context config

//Some more code

stream.reset( new ssl_stream(*io,context_) );

boost::system::error_code ec;
boost::system::error_code no_error; // default error_code = no error

while(true)
{
    ec = no_error;
    stream.reset( new ssl_stream(*io,context_) );
    acceptor_.accept(stream->lowest_layer(), endpoint,ec);
    if( !ec )
    {
        stream->handshake(ba::ssl::stream_base::server,ec);

        if( !ec )
        {
            operations();
        }
        //rest of the code...
}//while(true) end
acceptor_.close();
if(pid == 0)
{
    std::cout << "Child exiting!" << std::endl;
}

流在哪里:

boost::shared_ptr< boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > stream;

无论如何,为了更清楚,这是两种情况:

首先:(确定)

# ./program
(Then, I only retrieve/send files)
# kill -9 program_pid ( or Crtl+C)
# g++ .... -o program
# ./program
(Program starts normally)

第二个:(错误)

#./program
(Use system() to start other program 1+ times)
# kill -9 program_pid (or Crtl+C)
# g++ .... -o program
# ./program
(Program fails to start, since socket "is already being used")

有趣的事实:

  1. 如果我重新启动/停止(通过终端)我在我的程序中启动的其他程序,套接字将被释放。
  2. 即使是 fork() -> execv() 相同的命令,也会导致套接字“锁定”。
  3. 启动程序的父 PID 是 1,不是我程序的 PID...

我想也许其他程序做错了什么,所以我尝试使用我的系统调用来启动另一个程序(Apache,通过它在 init.d 中的脚本),我得到了相同的结果。

所以我有点迷失在“为什么会这样”和“我该如何解决这个问题”......

system() 似乎与它有关,但我不知道为什么,因为调用返回并且套接字与它无关。 即使从子进程(通过 fork())使用 execv() 调用相同的命令也会产生相同的错误...

还没有尝试过 popen(),但我相信我会到达同一个地方。

编辑:刚刚意识到 system() 实际上是一个 fork() 后跟一个 exec,所以没有必要指出这一点......

EDIT2:添加更多细节

按照 Tanner Sansbury 的回答,我更改了我的代码,但结果仍然相同......

关于操作我只是看了操作和调用了相关的函数。

然后我开始执行我的功能:

//Function body:

if (this->existBinary())
{
    io->notify_fork(boost::asio::io_service::fork_prepare);

    pid = fork();

    if( pid == 0 )
    {
        io->notify_fork(boost::asio::io_service::fork_child);

        char binChar[80];

        for(unsigned int i = 0; i < bin.size(); ++i)
        {
            binChar[i] = bin[i];
        }

        binChar[bin.size()] = '\0';

        char* bin_args[] = { binChar, "start" , NULL };
        execv( bin_args[0], bin_args );
    }
    else
    {
        io->notify_fork(boost::asio::io_service::fork_parent);

        if( pid == -1)
        {
            std::cout << "Fork error" << std::endl;
        }
        else
        {
            // running in parent, wait exec to complete
            // and return its exit status.
            int status;
            waitpid( pid, &status, 0 );
            printf("Child %d exited with status %d\n", pid, status );
        }
    }

    // returning true just for testing
    return true;
}

return false;

existBinary() 函数:

bool my_class::existBinary(const std::string& filename) const
{
    std::ifstream file(filename.c_str(),std::ios_base::in | std::ios_base::binary);
    bool file_status = false;

    if(file.is_open())
    {
        file_status = true;
        file.close();
    }

    return file_status;
}

请注意,当 child 退出时我打印了一个 cout,但它什么也没显示... :(

我将尝试将接受器移动为类(class)成员并将其关闭在 child 身上。

同样,当我重新启动/停止其他程序时套接字被释放...

最佳答案

简而言之,根据观察到的行为,问题的根源可能是 fork() 的行为,因为子进程将继承 acceptor 的打开文件描述符的拷贝。从 child 内部关闭接受器应该可以解决问题。


fork()发生时,子进程继承父进程的打开文件描述符集的拷贝。以下是 fork 文档的相关摘录:

The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description as the corresponding file descriptor in the parent. This means that the two descriptors share open file status flags, current file offset, and signal-driven I/O attributes .

但是,Boost.Asio 有内部文件描述符,它们也会被复制。 io_service需要为fork()做准备,并在fork()发生后通知。 Boost.Asio 文档是正确的 fork用法。根据文档,程序有责任在 fork 期间管理可通过 Boost.Asio 的公共(public) API 访问的任何文件描述符:

// Inform the io_service that we are about to fork. The io_service cleans
// up any internal resources, such as threads, that may interfere with
// forking.
io_service_.notify_fork(boost::asio::io_service::fork_prepare);

if (0 == fork())
{
  // Inform the io_service that the fork is finished and that this is the
  // child process. The io_service uses this opportunity to create any
  // internal file descriptors that must be private to the new process.
  io_service_.notify_fork(boost::asio::io_service::fork_child);

  // The child won't be accepting new connections, so we can close the
  // acceptor. It remains open in the parent.
  acceptor_.close();
  system(...);
  ...
}
else
{
  // Inform the io_service that the fork is finished (or failed) and that
  // this is the parent process. The io_service uses this opportunity to
  // recreate any internal resources that were cleaned up during
  // preparation for the fork.
  io_service_.notify_fork(boost::asio::io_service::fork_parent);
  ...
}

参见 Boost.Asio 的 process per connection例如,进程从完成处理程序中 fork 。

关于c++ - 通过 system() 调用启动另一个程序会阻塞套接字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17543922/

相关文章:

c++ - Clang内置矩阵和 vector 扩展: efficient matrix-vector multiplication

c++ - 基数排序算法说明

C++11 多播委托(delegate)实现

c++ - 编译器重新排序和加载操作

java - 为什么我的代码在 while 循环后停止?

mysql - 错误 2002 (HY000) : Can't connect to local MySQL server through socket '/var/mysql/mysql.sock' - Missing files

c - 在 tcp 服务器中,每隔一秒调用一次 select()。如何区分两个或多个到达 "1 second"的数据包?

c++ - boost.asio 和当前的网络 TS 最大的区别是什么?

c++ - 使用 Boost Beast 通过代理发出同步 http 请求时出现问题

c++ - 带有 async_read 的 boost.asio 多线程 I/O 服务器