Для конкретной проблемы нормальная работа завершения TCP (close()
) входит в состояние FIN_WAIT_1
, что может привести к тому, что открытый сокет останется открытым, пока не получит FIN
.Чтобы решить эту проблему, вы можете указать сокету не ждать, установив SO_LINGER
в 0
.Однако это считается "плохой практикой".Для получения более подробной информации см .: Параметр TCP SO_LINGER (ноль) - когда это необходимо
Если вы используете PHP в качестве клиента для отправки запросов в эту Команду, обновите свой вопрос следующим образом:он может неправильно завершать запрос (ы).
Похоже, что у вас могут быть некоторые проблемы с обработкой сокетов.В основном с проверкой используемых ресурсов, а не с закрытием сокетов в случае исключения.
Вы должны добавить finally
к вашему try/catch
для обработки обычного завершения или исключения, которое может закрывать открытые сокеты.Начиная с PHP 7.0 вы должны ловить \Throwable
, а не только \Exception
.Например, использование intdiv(1, 0)
или 1 << -1
не сможет быть перехвачено вашим кодом.
При условии, что супервизор правильно контролирует процесс.
protected function execute(InputInterface $input, OutputInterface $output)
{
try {
$this->io = new SymfonyStyle($input, $output);
$this->logger->info('Start TCP socket: Setup websocket server for detections on port 8086...');
$now = new \DateTime();
$this->io->title('Start server ' . $now->format('d-m-Y G:i:s') . '...');
if (!$localSocket = socket_create_listen(8086)) {
throw new \RuntimeException('Could not create socket.');
}
//force PHP to close the socket (do not linger waiting for FIN)
socket_set_option($localSocket, SOL_SOCKET, SO_LINGER, [
'l_linger' => 0,
'l_onoff' => 1,
]);
if (!socket_getsockname($localSocket, $addr, $port)) {
throw new \RuntimeException('Unable to retrieve local socket.');
}
$message = sprintf('Start TCP socket: Ok. TCP socket opened on: %s:%s.', $addr, $port);
$this->io->success($message . PHP_EOL);
$this->logger->info($message);
$listening = true;
while ($listening) {
if (!$remoteSocket = socket_accept($localSocket)) {
throw new \RuntimeException('Unable to accept incoming connections');
}
if (!socket_getpeername($remoteSocket, $raddr, $rport)) {
throw new \RuntimeException('Unable to retrieve remote socket');
}
$this->io->writeln(sprintf('Received Connection from %s:%s%s', $raddr, $rport, PHP_EOL));
$data = '';
$bytesRec = 0;
while ($bytes = socket_recv($remoteSocket, $r_data, 128, MSG_WAITALL)) {
$data .= $r_data;
$bytesRec += $bytes;
}
//force PHP to close the socket (do not linger waiting for FIN)
socket_set_option($remoteSocket, SOL_SOCKET, SO_LINGER, [
'l_linger' => 0,
'l_onoff' => 1
]);
//clear memory of remoteSocket resource before processing the data
socket_close($remoteSocket);
$remoteSocket = null;
unset($remoteSocket);
//Method Call to process data here...
$message = sprintf('Finish processing data. Total Data Received: %s %s', $bytesRec, PHP_EOL);
$this->io->writeln($message);
$this->logger->info($message);
if ($condition = false) {
//add a condition to terminate listening, such as $i++ >= 1000
$listening = false;
}
//force PHP to take a break
usleep(100);
}
} catch (\Throwable $e) {
$message = sprintf('Start TCP socket: Ko. Exception detail: %s',
$e->getMessage());
$this->logger->critical($message);
$this->io->error($message);
} finally {
//ensure the socket resources are closed
if (isset($remoteSocket) && is_resource($remoteSocket)) {
//force PHP to close the socket (do not linger waiting for FIN)
socket_set_option($remoteSocket, SOL_SOCKET, SO_LINGER, [
'l_linger' => 0,
'l_onoff' => 1
]);
socket_close($remoteSocket);
$remoteSocket = null;
unset($remoteSocket);
}
if (isset($localSocket) && is_resource($localSocket)) {
socket_close($localSocket);
$localSocket = null;
unset($localSocket);
}
}
}
Как примечание;использование службы Doctrine в конечном итоге приведет к потере соединения с базой данных, когда ваш сервер БД превысит исходное соединение, созданное Symfony.Это приведет к неожиданному завершению работы сценария при попытке выполнить запрос к БД.
Вам необходимо будет закрыть соединение при каждом вызове службы базы данных или, если она введена в вашу команду, немедленно закройтеподключение во время execute
.
class YourCommand
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em; //or $this->container->get('doctrine.orm.entity_manager')
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->em->getConnection()->close();
//...
while ($listening) {
//...
//Method Call to process data here...
$this->em->getConnection()->connect();
//... execute query
$this->em->getConnection()->close();
//...
}
}
}
Стоит также отметить, что PHP не предназначен для запуска в качестве долго запущенного процесса демона и имеет известных проблем с этим ,такие как утечки памяти.Настоятельно рекомендуется найти другой подходящий подход, например NodeJS, для посредничества запросов TCP от удаленных подключений к PHP (как это делают Apache и NGINX).