I have the following task, I do not know how to solve it.

We have a file with a button. we click on it starts and with the help of the script starts another file. But he sends us the answer from time to time until the whole is fulfilled.

file 1

<form method="POST" id="formx" action="javascript:void(null);" onsubmit="call()"> <button></button </form> <div id='text'></div> <javascript> call(){ подключает файл 2 } </javascript> 

file 2

 <?php for($i=0; $i < 500; $i++){ $i //постоянно передаём сообщение о значение переменно $i в поле файла 1 id='text' } ?> 
  • It is necessary that each stage of the cycle deduces or the result? Simply, if each stage, then the cycle is moved very quickly, and these 500 cycles in a split second will go over and the eye will not even be noticeable. - Denis
  • Yes, every one. Well, let it be $ i = 10000000000000 - Viktor Vasilyev
  • one
    And why is it necessary? unclear. - And
  • one
    @And a real task, reduced to the minimum of absurdity, which can put as a question)) or maybe it’s just interesting to a person - DaemonHK
  • one
    Describe, without code or files, who-what-causes-and-why exactly what you are trying to achieve. - Daniel Protopopov

4 answers 4

Run the "second file" that does the work in the background on a web server, for example, through exec or cgi (depends on the OS / environment / settings / permissions, but is generally doable). There was a question on this topic , and most likely not one. The task is similar to yours.

Next, transfer the value of $ i from the "second file (script)" through the database, or through a text file (worse and stranger). For example, each cycle $ i will increase by 1 and written to the database (or file). When you need to find out the current value of $ i on the client and show it to the user - make a request to the database (via another php script) or to a file on the server. So you can get the current value of $ i on the client, without interfering with the work.

The display of progress can be done through javascript:setTimeout() through an interval acceptable for your task, 50-500 milliseconds, for example.

Separately, I want to add that the input parameters of work, intermediate results and the final result, as well as the unique number, are the easiest to combine into an object “task / task” and save the whole database. A working script (worker) will be run periodically and:

  1. Search for a job in the list of tasks not occupied
  2. If found, take one, block in the database
  3. Do some part of it
  4. Save intermediate / final result in the database
  5. Unlock task in db
  6. Terminate, or launch a new iteration of yourself

This is a very general algorithm)

UPD: Added a simple example code

The index.php file, in the example, displays the form for the start and the current status of the task:

 <?php ob_start(); $html = ''; /* Структура БД -- -- Структура таблицы `tasks` -- CREATE TABLE IF NOT EXISTS `tasks` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Первичный ключ, номер задачи', `lock` int(11) NOT NULL COMMENT 'Флаг блокировки задачи', `input` int(11) NOT NULL COMMENT 'Входные данные задачи', `output` int(11) NOT NULL COMMENT 'Выходные данные задачи', `current` int(11) NOT NULL COMMENT 'Текущие или промежуточные данные задачи', `progress` double NOT NULL COMMENT 'Поле прогресса задачи, будет полезно для сложных многоступенчатых задач', PRIMARY KEY (`id`), KEY `lock` (`lock`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; */ /* Подключаемся к БД */ $link = mysqli_connect('localhost', 'root', 'root', 'stackoverflow'); $task_id = isset($_GET['task_id']) ? intval($_GET['task_id']) : NULL; $progress = isset($_GET['progress']) && $_GET['progress'] ? true : false; if ($task_id) { /* Отображаем страницу данных по конкретной задаче */ $result = mysqli_query($link, "SELECT * FROM `tasks` WHERE `id` = ".mysqli_real_escape_string($link, $task_id)." LIMIT 1;"); if ($result && $task = mysqli_fetch_assoc($result)) { if ($progress) { /* Если был запрошен только прогресс, возвращаем его */ $html .= sprintf('%.1f', $task['progress']); } else { /* Иначе */ $html .= '<h3>Задача #'.$task_id.'</h3> <p>Прогресс: <span id="progress">'.sprintf('%.1f', $task['progress']).'</span> %</p> '; if (floatval($task['progress']) < 100.0) { /* Также добавляем скрипт для получения прогресса по AJAX */ $html .= ' <script src="http://code.jquery.com/jquery-3.2.1.min.js"></script> <script> var updateProgressIntervalId = null; $(function(){ updateProgressIntervalId = setInterval(function(){ /* Анонимная функция для обновления значения прогресса на странице клиента */ $.get("index.php", {task_id:'.$task_id.',progress:1},function(data, textStatus, jqXHR){ /* Callback для обработки результата ответа */ if (textStatus == "success" && data) { $("#progress").html(data); if (Number(data) == 100.0) { /* Останавливаем опрос прогресса по задаче */ clearInterval(updateProgressIntervalId); } } }); }, 200); }); </script> '; } } } else { $html .= '<p>Задача #'.$task_id.' не найдена</p>'; } } else { /* Отображаем страницу запуска задачи */ $html .= '<h3>Пример</h3> <form action="" method="post"> <input type="submit" name="start" value="Запустить новую задачу и запустить процесс работы"> </form> <form action="" method="post"> <input type="submit" name="worker" value="Запустить процесс работы"> </form> '; /* Проверяем, а не была ли запущена задача */ if (isset($_POST['start'])) { mysqli_query($link, "INSERT INTO `tasks` (`lock`,`input`,`output`,`current`,`progress`) VALUES (0,0,10000,0,0);"); $task_id = mysqli_insert_id($link); header('Location: index.php?task_id='.$task_id); } if (isset($_POST['start']) || isset($_POST['worker'])) { /* Запускаем воркер в фоне. Конкретная строка зависит от вашей ОС/сервера и настроек */ exec('/usr/bin/php -f '.__DIR__.'/worker.php > /dev/null 2>&1 &'); } } /* Отключаемся от БД */ mysqli_close($link); if ($progress) { print $html; } else { print '<html> <head> <title>ru.stackoverflow.com/questions/759003/</title> </head> <body> '.$html.' </body> </html> '; } ob_flush(); ?> 

index.php to start a task

index.php with a single task status form

File worker.php , does work on tasks. It can be run in several instances, for example, one for each active task.

 <?php /* Подключаемся к БД */ $link = mysqli_connect('localhost', 'root', 'root', 'stackoverflow'); /* Флаг наличия работы */ $need_to_restart = false; /* Ищем задачи, которые свободны и не завершены */ $result = mysqli_query($link, "SELECT * FROM `tasks` WHERE `lock` = 0 AND `progress` < 100 LIMIT 1;"); if ($result && $task = mysqli_fetch_assoc($result)) { /* Блокируем задачу */ mysqli_query($link, "UPDATE `tasks` SET `lock` = 1 WHERE `id` = ".mysqli_real_escape_string($link, $task['id']).";"); /* Выполняем некую работу (+1 к промежуточному значения в данном случае) */ $task['current'] += 1; $task['progress'] = 100.0 * $task['current'] / $task['output']; if ($task['progress'] > 100.0) { $task['progress'] = 100.0; } /* Обновляем и разблокируем задачу */ mysqli_query($link, "UPDATE `tasks` SET `lock` = 0, `current` = ".mysqli_real_escape_string($link, $task['current']).", `progress` = ".mysqli_real_escape_string($link, $task['progress'])." WHERE `id` = ".mysqli_real_escape_string($link, $task['id']).";"); /* Немного отладочного вывода */ print 'task #'.$task['id'].' '.sprintf('%.1f', $task['progress']).' %'; /* Проверяем наличие работы в очереди */ $result = mysqli_query($link, "SELECT * FROM `tasks` WHERE `lock` = 0 AND `progress` < 100 LIMIT 1;"); if ($result && $task = mysqli_fetch_assoc($result)) { $need_to_restart = true; } } /* Отключаемся от БД */ mysqli_close($link); if ($need_to_restart) { /* Запускаем воркер в фоне */ exec('/usr/bin/php -f '.__DIR__.'/worker.php > /dev/null 2>&1 &'); } ?> 

The example was written by me, checked on CentOS 7 (kernel 3.10.0) + Apache / 2.4.6 + MariaDB / 5.5.56 + php / 5.4.16

An example of performing several tasks, and how it looks in the database

  • Thanks, wirtwelt. Yes, an interesting option. You can write the code, how would you implement it, then about wrote. Thank! - Victor Vasiliev
  • one
    Attached a simple code sample to your answer. You can copy and check on the Linux server. Apache should have permission to run executable files, as well as to execute the exec function (hosters often prohibit it, because this is a decent potential hole) - wirtwelt

You write that you want the server script to constantly send a message. This is the main problem in your question.

Regular PHP, executed under a web server, does not know how to send answers in parts with such granularity as you want. Typically, the response is partially or fully buffered before being sent to the client on the server side, usually inside nginx.

In other words, it’s just so easy to do what you want.

If you complicate everything, it can be done. To do this, you will need to learn how to work with WebSocket , which makes the whole scheme of transferring numbers very complicated. A simple code example with comments for working on this scheme will not work: everything is very difficult.

Another complication option: refuse to send a permanent send, instead, constantly poll the server for changes. For example, this can be done on the browser side:

 var timerHandle = setInterval(function () { $.getJSON("my_file.php", function(counter) { $("#text").html(counter); }); }, 1000); 

On the server side:

 <?php session_start(); if (empty($_SESSION['counter'])) { $_SESSION['counter'] = 1; } elseif ($_SESSION['counter'] < 500) { $_SESSION['counter'] += 1; } echo $_SESSION['counter']; // для передачи нескольких значений можно использовать json_encode 
  • Here is a lie, is able . There, in the example, they are building just a php file, which is exactly what answers with the frequency as much as you want. WebSocket is really a complicated thing (I haven't tried it myself yet), but here, as it seems to me, SSE will do fine as well. we do not need a full-fledged web application, we just need to receive data from the server without constantly sending them to it. - Alexander Belinsky
  • It is written in the question that “we constantly send a message”, it means that it is the constant sending that is needed. - sanmai
  • from browser to server — you don’t have to transfer anything, I don’t see it in the task. In the opposite direction will always be transmitted through SSE. Read carefully, please) - Alexander Belinsky
  • From the browser to the server is not necessary, and where about it in my answer, sorry? Will SSE work for nginx configured by default? .. - sanmai
  • About nginx I can not say, there is no way to check. If you can - please check. - Alexander Belinsky

The first request to the server is written to the team in the queue (RabbitMQ, for example).
A constantly working worker on the server receives a command, starts a job that performs the work in a loop and writes $i to the database or Redis. Further, at the request of the front, new requests read this $i .

    Option two.

    1. As many have already voiced, the simplest and most boring thing is to write progress in an explicit form into a file, a base, another base, some other base. And most of the time to interrogate (for example, the Ajaxik) another script "how much has already been done there?"

    2. You can use a terrifying and at the same time fascinating (and most importantly, not boring and fun) thing - EventSource . Sobsno something like that.

    The first file is the main one (let's call it temp8.php):

     <form method="POST" id="formx" action="javascript:void(null);" onsubmit="call()"> <button>нажми меня, скорее ;-)</button> </form> <div id='text'></div> <script> function call() { var text = document.getElementById('text'); if(typeof(EventSource) !== "undefined") { var source = new EventSource("temp7.php"); source.onmessage = function(event) { text.innerText = event.data; }; } else { text.innerText = 'EventSource не работает'; } } </script> 

    The second file to perform clever tricks (let's call it temp7.php):

     <?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $i = 0; $time = microtime(true); $period = 2; // каждые $period секунд будем отправлять ответ. // в условии цикла прописано сейчас прописано true, так он будет работать до бесконечности, // если в явном виде не прервать выполнение скрипта // вместо этого лучше написать какое-то выражение, исходя из которого // можно будет положить, что цель скрипта себя исчерпала (уж не знаю, что вы там делаете). // ну либо оставить здесь true, а уже в теле самого цикла вызвать явно exit/break while (true) { /* здесь можете выполнять хитрые махинации, которые собираетесь делать. например, можно увеличивать $i, чтобы потом было что посылать */ $i++; if (microtime(true) - $time >= $period) { // пишем данные echo "data: $i\n\n"; // выплёвываем и сбрасываем системный буфер вывода. ob_end_flush(); flush(); $time = microtime(true); } // эт просто чтобы тестить, в реале её бы убрать к чертям. sleep(1); }