Основные понятия
В начале этой статьи в мозг добавляются некоторые знания, которые полезны для чтения. В этой статье основное внимание уделяется select, вводится принцип реализации select и используется select для реализации одного процесса, блокирующего сетевой сервер мультиплексирования.
Мультиплексирование ввода-вывода означает, что, как только ядро обнаружит, что одно или несколько условий ввода-вывода, заданных процессом, готовы для чтения, оно уведомит процесс. В настоящее время мультиплексирование ввода-вывода поддерживается с помощью мультиплексирования select, poll, epoll и ввода-вывода. Процесс может отслеживать несколько дескрипторов. Как только дескриптор готов (обычно для чтения или записи), он может уведомить программу о необходимости ввода строки, мультиплексирование ввода-вывода применимо в следующих ситуациях:
- Когда клиент обрабатывает несколько дескрипторов (обычно интерактивный ввод и интерфейс сетевых сокетов), необходимо использовать мультиплексирование ввода-вывода.
- Это возможно, но редко, когда клиент имеет дело с несколькими интерфейсами сокетов одновременно.
- Если TCP-серверу приходится иметь дело как с интерфейсом сокета монитора, так и с интерфейсом подключенного сокета, обычно используется мультиплексирование ввода-вывода.
- Если сервер хочет обрабатывать как TCP, так и UDP, обычно используется мультиплексирование ввода-вывода.
- Если серверу необходимо обрабатывать несколько служб или несколько протоколов, обычно используется мультиплексирование ввода-вывода.
По сравнению с технологией многопроцессорной и многопоточной обработки, самым большим преимуществом технологии мультиплексирования ввода-вывода является то, что системе не нужно создавать процессы/потоки или поддерживать эти процессы/потоки, что значительно снижает стоимость системы.
выбирать
описывать
Отслеживает и ожидает изменения атрибутов (доступных для чтения, записи или исключений ошибок) для нескольких файловых дескрипторов. Файловые дескрипторы, отслеживаемые функцией select, делятся на три категории: файлы записи, файлы чтения и файлы исключений. После вызова выберите заблокируется, и функция не вернется, пока дескриптор не будет готов (данные доступны для чтения, записи или исключения ошибок) или время ожидания (время ожидания определяет время ожидания). Когда функция select() возвращает, готовый дескриптор можно найти, пройдя fdset, и максимальный дескриптор не может превышать 1024
опрос
описывать
Механизм опроса аналогичен select, который по сути мало чем отличается от select. Управление несколькими дескрипторами также является опросом, который обрабатывается в соответствии с состоянием дескрипторов, но опрос не ограничивает максимальное количество файловых дескрипторов. Недостатком опроса и выбора является то, что массив, содержащий большое количество файловых дескрипторов, копируется между адресным пространством состояния пользователя и ядром в целом. Независимо от того, готовы эти файловые дескрипторы или нет, его накладные расходы линейно возрастают с увеличением числа файловых дескрипторов.
Выберите и опросите
Проблема выбора/опроса очевидна, и им нужно выполнить цикл, чтобы определить, есть ли события в соединении. Если сервер имеет миллионы подключений, и только одно соединение отправляет данные на сервер в определенное время, для выбора/опроса необходимо выполнить цикл в 1 миллион раз, только одно из которых выполняется, а остальные 999999 раз недействительны, что приводит к потере ресурсов процессора.
epoll
описывать
Epoll предлагается в ядре 2.6, которое является расширенной версией select и poll. По сравнению с select и poll, epoll является более гибким, не имеет ограничений по дескрипторам и не нуждается в опросе. Epoll использует один файловый дескриптор для управления несколькими дескрипторами и сохраняет события файловых дескрипторов отношений с пользователем в таблице событий в ядре. Проще говоря, когда для соединения генерируется событие потока ввода-вывода, epoll сообщит процессу, какое соединение имеет событие потока ввода-вывода, а затем процесс обработает это событие.
Сетевой сервер
Один процесс, блокирующий сетевой сервер мультиплексирования, как показано на рисунке ниже
описывать
Процесс мониторинга службы выглядит следующим образом 1. Сохраните все сокеты и прослушайте читаемые события дескриптора сокета с помощью системного вызова select 2. Выберите будет прослушиваться в пространстве ядра. Как только сокет будет найден читаемым, он будет перенесен из пространства ядра в пространство пользователя. В пользовательском пространстве можно логически определить, является ли сокет на сервере читаемым или сокет на клиенте читаемым 3. Если сокет сервера доступен для чтения, это означает, что установлен новый клиент и сокет зарезервирован в массиве прослушивания 4. Если сокет клиента читаем, это означает, что он может считывать содержимое, отправленное клиентом, считывать содержимое, а затем отвечать клиенту. Недостатки: 1. Недостатки самого режима выбора (1. События обработки обхода цикла, 2. Потребление данных о передаче пространства ядра) 2. Один процесс слаб при решении большого количества задач
реализация кода
class Worker{
//Monitor socket
protected $socket = NULL;
//Connection event callback
public $onConnect = NULL;
//Receive message event callback
public $onMessage = NULL;
Public $workernum = 4; // number of subprocesses
Public $allsocket; // store all sockets
public function __construct($socket_address) {
//Listening address + port
$this->socket=stream_socket_server($socket_address);
Stream_set_blocking ($this - > socket, 0); // set non blocking
$this->allSocket[(int)$this->socket]=$this->socket;
}
public function start() {
//Get profile
$this->fork();
}
public function fork(){
$this - > accept(); // the subprocess is responsible for receiving client requests
}
public function accept(){
//Create multiple subprocesses to block receiving server socket
while (true){
$write=$except=[];
//Need to monitor socket
$read=$this->allSocket;
//Who changes the state
stream_select($read,$write,$except,60);
//How to distinguish between server and client
foreach ($read as $index=>$val){
//Currently, the server is changed, with connection access
if($val === $this->socket){
$ClientSocket = stream \ socket \ accept ($this - > socket); // block listening
//Callback of the connection that triggered the event
if(!empty($clientSocket) && is_callable($this->onConnect)){
call_user_func($this->onConnect,$clientSocket);
}
$this->allSocket[(int)$clientSocket]=$clientSocket;
}else{
//Read the client's content from the connection
$buffer=fread($val,1024);
//If the data is empty or false, it is not a resource type
if(empty($buffer)){
if(feof($val) || !is_resource($val)){
//Trigger close event
fclose($val);
unset($this->allSocket[(int)$val]);
continue;
}
}
//Normally read the data, trigger the message receiving event and respond to the content
if(!empty($buffer) && is_callable($this->onMessage)){
call_user_func($this->onMessage,$val,$buffer);
}
}
}
}
}
}
$worker = new Worker('tcp://0.0.0.0:9805');
//Connection event
$worker->onConnect = function ($fd) {
//Echo 'connection event trigger', (int) $FD, php_eol;
};
//Message reception
$worker->onMessage = function ($conn, $message) {
//Write business logic in event callback
$content = "replied message";
$http_resonse = "HTTP/1.1 200 OK\r\n";
$http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
$http_reconnect. = "connection: keep alive \ R \ n"; // keep the connection
$http_resonse .= "Server: php socket server\r\n";
$http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";
$http_resonse .= $content;
fwrite($conn, $http_resonse);
};
$worker - > start(); // startфункция
stream_socket_server
В PHP предусмотрена очень удобная функция для одновременного создания, привязки и мониторинга портов
stream_set_blocking ( ресурс $поток , режим int $): bool
Установите режим блокировки или блокировки для потока ресурсов, $режим 0 без блокировки, 1 блокировка
stream_socket_accept ( ресурс $server_socket \[, float(“default_socket_timeout”) [, строка и имя пользователя ]] ): ресурс
Принимать соединения сокетов, созданные сервером сокетов потока()