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

Starting another program via system() call blocks the socket

本文关键字:程序 套接字 另一个 启动 system 调用 通过      更新时间:2023-10-16

我在谷歌和StackOverflow上搜索了类似的东西,但最接近的是C代码,情况不一样。。。

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

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

我真的不认为这与实际的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;

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

第一:(OK)

# ./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中的脚本),我得到了同样的结果。

所以我有点迷失在"为什么会发生这种事"answers"我如何解决这个问题"。。。

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

仍然没有尝试popen(),但我相信我会到达同样的地方。

编辑:我刚刚意识到system()实际上是一个后面跟着exec的fork(),所以指出这一点毫无意义。。。

编辑2:添加更多详细信息

根据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()] = '';
        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 %dn", 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;
}

请注意,当孩子离开时,我放了一个cout打印,但它什么也没显示…:(

我将尝试将接受器移动为类成员,并在子对象上关闭它。

再次,当我重新启动/停止其他程序时,套接字被释放。。。

简而言之,根据观察到的行为,问题的根源很可能是由fork()的行为引起的,因为子进程将继承受体的打开文件描述符的副本。从子级内部关闭接收器应该可以解决问题。


当出现fork()时,子进程将继承父进程的一组打开文件描述符的副本。以下是fork文档的相关摘录:

子项继承父项的一组打开文件描述符的副本。子级中的每个文件描述符引用与父级中相应文件描述符相同的打开文件描述。这意味着这两个描述符共享打开文件状态标志、当前文件偏移量和信号驱动的I/O属性。

然而,Boost.Asio有内部文件描述符,这些描述符也将被复制。io_service需要为fork()准备,并在fork()发生后通知。Boost.Asio文档是正确的分叉用法。根据文档,程序有责任在fork:期间管理通过Boost.Asio的公共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的每个连接的进程,以获取进程从完成处理程序中分叉的示例。

相关文章: