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

时间:2013-07-09 08:53:40

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

我搜索谷歌和StackOverflow的任何类似的东西,但最接近的是C代码,情况不一样......

我有一个程序通过cstdlib的system()调用脚本启动另一个程序,一切正常,问题是我必须测试新代码,所以我停止执行(Crtl + C并杀死-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;
}

其中stream是:

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() - &gt; execv()使用相同的命令,导致套接字被锁定&#34;。
  3. 已启动程序的父PID是1,而不是我程序的PID ...
  4. 我想也许其他程序做错了,所以我尝试使用我的系统调用启动另一个程序(Apache,通过它在init.d中的脚本),我得到了相同的结果。

    所以我有点迷失在这里&#34;为什么会发生这种情况&#34;和&#34;我如何解决这个问题&#34; ...

    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;
    }
    

    请注意,当孩子退出时我会打印一个cout,但它没有显示...... :(

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

    再次,当我重新启动/停止其他程序时,套接字是自由...

1 个答案:

答案 0 :(得分:2)

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


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

  

子进程继承了父进程打开文件描述符的副本。子节点中的每个文件描述符都指向与父节点中相应文件描述符相同的打开文件描述。这意味着两个描述符共享打开文件状态标志,当前文件偏移和信号驱动的I / O属性。

但是,Boost.Asio具有内部文件描述符,也将被复制。需要为io_service准备fork(),并在fork()发生后通知。 Boost.Asio文档是正确的fork用法。根据文档,程序有责任在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的process per connection以获取完成处理程序中进程分叉的示例。