0

我在 google 和 StackOverflow 上搜索了与此类似的任何内容,但最接近的是 C 代码,情况不一样......

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

我的程序使用一个接受器并等待连接(它一次处理一个)。它可以通过一个脚本检索/发送文件并启动/停止另一个程序。如果我只是检索/发送文件,则套接字不会被锁定,但是如果执行了任何 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;

existsBinary() 函数:

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 来打印,但它什么也没显示...... :(

我将尝试将接受者移动为班级成员并将其关闭在孩子身上。

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

4

1 回答 1

2

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


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

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

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

// 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 的每个连接进程,以获取进程从完成处理程序中分叉的示例。

于 2013-07-09T14:40:38.097 回答