0

There is a small script that listens to the port. When somebody connected, the process forks.
The problem is that when the child process close, it does not close correctly, but becomes a zombie process.

I also want to add that it is important for me that the script work asynchronously and can support several incoming connections.

How to properly terminate child processes in my case?
Below is the script code:

$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($serverSocket=== false) {
    echo 'socket_create() failed: reason: ' . socket_strerror(socket_last_error()) . "\n";
}

if (!socket_bind($serverSocket, $address, $port)) {
    echo 'socket_bind() failed: reason: ' . socket_strerror(socket_last_error($serverSocket)) . "\n";
}

if (!socket_listen($serverSocket)) {
    echo 'socket_listen() failed: reason: ' . socket_strerror(socket_last_error($serverSocket)) . "\n";
}


do {
    $clientSocket = socket_accept($serverSocket);
    if ($clientSocket === false) {
        echo 'socket_accept() failed: reason: ' . socket_strerror(socket_last_error($serverSocket)) . "\n";
        break;
    }

    $pid = pcntl_fork();

    if ($pid === -1) {
        die('Can\'t fork process');
    }

    if ($pid) {
        echo "Someone connected $pid \n";
        pcntl_waitpid($pid, $status, WNOHANG);
        echo "Someone disconnected \n";
    } else {
        /* Send instructions. */
        $msg = "\nWelcome to the brackets analyzer!. \n" .
            "Enter a string to analyze. \n" .
            "To quit, type 'quit'\n";
        socket_write($clientSocket, $msg, strlen($msg));

        do {
            $buf = socket_read($clientSocket, 2048, PHP_NORMAL_READ);
            if ($buf === false) {
                echo 'socket_read() failed: reason: ' . socket_strerror(socket_last_error($clientSocket)) . "\n";
                break;
            }
            if (!$buf = trim($buf)) {
                continue;
            }
            if ($buf === 'quit') {
                $msg = "Goodbye!\n";
                socket_write($clientSocket, $msg, strlen($msg));
                exit();
            }

            $talkback = "PHP: You said '$buf'.\n";
            socket_write($clientSocket, $talkback, strlen($talkback));
        } while (true);
    }

    socket_close($clientSocket);

} while (true);

socket_close($serverSocket);

UPD: I tried to add SIGCHLD handler by calling pcntl_signal (full code below).
The problem is that when someone exits the child process, the process ends and becomes a zombie process, but the next connection to the server immediately deletes all zombie processes.
I really don't understand how it works.
How to achieve the child process correctly closed immediately?

declare(ticks = 1);

set_time_limit(0);

/* Turn on implicit output flushing so we see what we're getting
 * as it comes in. */
ob_implicit_flush();

declare(ticks = 1);

pcntl_signal(SIGCHLD, function () {
    pcntl_waitpid(-1, $status);
    echo "Someone disconnected \n";
});

$address = '192.168.0.100';
$port = 7888;

$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($serverSocket=== false) {
    echo 'socket_create() failed: reason: ' . socket_strerror(socket_last_error()) . "\n";
}

if (!socket_bind($serverSocket, $address, $port)) {
    echo 'socket_bind() failed: reason: ' . socket_strerror(socket_last_error($serverSocket)) . "\n";
}

if (!socket_listen($serverSocket)) {
    echo 'socket_listen() failed: reason: ' . socket_strerror(socket_last_error($serverSocket)) . "\n";
}


do {
    $clientSocket = socket_accept($serverSocket);
    if ($clientSocket === false) {
        echo 'socket_accept() failed: reason: ' . socket_strerror(socket_last_error($serverSocket)) . "\n";
        break;
    }

    $pid = pcntl_fork();

    if ($pid === -1) {
        die('Can\'t fork process');
    }

    if ($pid) {
        echo "Someone connected $pid \n";
    } else {
        /* Send instructions. */
        $msg = "\nWelcome to the brackets analyzer!. \n" .
            "Enter a string to analyze. \n" .
            "To quit, type 'quit'\n";
        socket_write($clientSocket, $msg, strlen($msg));

        do {
            $buf = socket_read($clientSocket, 2048, PHP_NORMAL_READ);
            if ($buf === false) {
                echo 'socket_read() failed: reason: ' . socket_strerror(socket_last_error($clientSocket)) . "\n";
                break;
            }
            if (!$buf = trim($buf)) {
                continue;
            }
            if ($buf === 'quit') {
                $msg = "Goodbye!\n";
                socket_write($clientSocket, $msg, strlen($msg));
                exit();
            }

            $talkback = "PHP: You said '$buf'.\n";
            socket_write($clientSocket, $talkback, strlen($talkback));
        } while (true);
    }

    socket_close($clientSocket);

} while (true);

socket_close($serverSocket);
Roman Andreev
  • 444
  • 3
  • 6
  • 18
  • Its the parent's duty to clean the child up, not the childs. – tkausl Apr 01 '19 at 17:46
  • @tkausl Well, but my problem is that as a result of the script, I have a lot of zombie processes (after each connection). Do you really think that's good? – Roman Andreev Apr 01 '19 at 17:48
  • Your parent never cleans them up then. You should probably listen for the SIGCHLD signal and then `wait` for the child. – tkausl Apr 01 '19 at 17:51
  • @tkausl I was able to achieve this only synchronously waiting for a response from the child. For this, I called `pcntl_waitpid ($ pid, $ status)`. But in this case, my script can handle only one connection at a time. – Roman Andreev Apr 01 '19 at 17:57
  • Thats why you need to catch the SIGCHLD signal. – tkausl Apr 01 '19 at 17:58
  • @tkausl But how can I do this asynchronously? – Roman Andreev Apr 01 '19 at 18:00
  • @RomanAndreev do you know what "asynchronous" means at all? Your approach is entirely wrong - you fork when a request comes in instead of pre-fork and copy the model of php-fpm. Since you went into the whole problem of running php from command line, why not resort to [swoole](http://www.swoole.com)? It literally solves your problem in less than 10 lines of code (execute the request work in a coroutine, no need for forking). – Mjh Apr 01 '19 at 19:26
  • @Mjh I know that there are quite a few ready-made solutions for my task, but I forgot to mention that this is a learning task. I need to solve it "at a low level" using pcntl_fork() – Roman Andreev Apr 02 '19 at 04:18
  • If you're in learning phase, then alter your approach slightly and use the model that provides the best experience: prefork N workers and dispatch workload to each worker as the parent gets the request. What you're doing now is literally pointless as you fork and then do the work - which is slower than just doing the work. If you want to stick with the current model and just "clean" the zombies, just SIGKILL them from the parent then - that's quick, dirty and it works. – Mjh Apr 02 '19 at 07:55
  • @Mjh Thank you very much for your answer! I'm trying to do as you said(second way), but I can't kill all the processes immediately. Could you see an update of my start message? – Roman Andreev Apr 02 '19 at 08:00
  • I found the solution! The problem was `socket_accept ()` runs in an infinite loop, and blocks signal processing. In order to avoid this, you need to make the server socket non-blocking. For this, the `socket_set_nonblock` function exists. Full code of the working solution: pastebin.com/BdzFeqGA – Roman Andreev Apr 02 '19 at 10:21

0 Answers0