-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Akolosova/hw6 #1191
base: main
Are you sure you want to change the base?
Akolosova/hw6 #1191
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,42 @@ | ||
# PHP_2023 | ||
|
||
https://otus.ru/lessons/razrabotchik-php/?utm_source=github&utm_medium=free&utm_campaign=otus | ||
###Описание/Пошаговая инструкция выполнения домашнего задания: | ||
|
||
Консольный чат на сокетах | ||
Создать логику, размещаемую в двух php-контейнерах (server и client), объединённых общим volume. | ||
Скрипты запускаются в режиме прослушивания STDIN и обмениваются друг с другом вводимыми сообщениями через unix-сокеты. | ||
|
||
- сервер поднимается всегда первым | ||
- клиент ожидает ввод из STDIN и отправляет сообщения серверу | ||
- сервер выводит полученное сообщение в STDOUT и отправляет клиенту подтверждение (например, "Received 24 bytes") | ||
- клиент выводит полученное подтверждение в STDOUT и начинает новую итерацию цикла | ||
|
||
###Критерии оценки: | ||
1. Конструкции @ и die неприемлемы. Вместо них используйте исключения | ||
2. Принимается только Unix-сокет | ||
3. Код здесь и далее мы пишем с применением ООП | ||
4. Код здесь и далее должен быть конфигурируем через файлы настроек типа config.ini | ||
5. Обратите внимание на паттерн FrontController (он же - единая точка доступа). Все приложения, которые Вы создаёте здесь и далее должны вызываться через один файл, в котором есть ТОЛЬКО | ||
6. Точка входа - app.php | ||
7. Сервер и клиент запускаются командами | ||
php app.php server | ||
php app.php client | ||
В app.php только строки | ||
require_once('/path/to/composer/autoload.php'); | ||
try { | ||
$app = new App(); | ||
$app->run(); | ||
} | ||
catch(Exception $e){ | ||
} | ||
8. Логика чтения конфигураций и работы с сокетами - только в классах. | ||
|
||
|
||
|
||
Порядок действий для запуска app | ||
|
||
1. docker-compose up --build -d | ||
2. docker-compose run server composer install | ||
3. docker-compose run server php app.php server | ||
4. docker-compose run server php app.php client | ||
5. exit |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
.idea/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
version: "3" | ||
|
||
services: | ||
server: | ||
build: | ||
context: ./fpm | ||
dockerfile: Dockerfile | ||
image: hw6/server | ||
container_name: server | ||
volumes: | ||
- ./www:/var/www | ||
- ./socket:/var/www/application.local/socket/ | ||
networks: | ||
- app-network | ||
|
||
client: | ||
build: | ||
context: ./fpm | ||
dockerfile: Dockerfile | ||
image: hw6/client | ||
container_name: client | ||
volumes: | ||
- ./www:/var/www | ||
- ./socket:/var/www/application.local/socket/ | ||
networks: | ||
- app-network | ||
|
||
networks: | ||
app-network: | ||
driver: bridge | ||
|
||
volumes: | ||
socket: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM php:8.2-fpm | ||
|
||
RUN apt-get update \ | ||
&& apt-get install -y \ | ||
&& docker-php-ext-install sockets \ | ||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer | ||
|
||
WORKDIR /var/www/application.local | ||
|
||
CMD ["php-fpm"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Вместо запуска FPM нужно сделать контейнер с сервером, который при старте сразу запустится. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.idea/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Если Вы в корневом .gitignore указали папку без '/' перед ней, то она будет игнорироваться во всех подпапках. Можно не повторять. |
||
/vendor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use App\App; | ||
|
||
require __DIR__ . "/../vendor/autoload.php"; | ||
|
||
try { | ||
$app = new App($argv, $argc); | ||
$app->run(); | ||
} catch (Exception $e) { | ||
echo $e->getMessage(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[socket] | ||
file = /var/www/application.local/socket/socket_hw6.sock |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App; | ||
|
||
use InvalidArgumentException; | ||
|
||
class App | ||
{ | ||
private string $mode; | ||
private Socket $socket; | ||
|
||
public function __construct($argv, $argc) | ||
{ | ||
if ($argc < 2) { | ||
throw new InvalidArgumentException('Usage: php app.php server|client'); | ||
} | ||
|
||
$this->mode = $argv[1] ?? ''; | ||
|
||
if (!in_array($this->mode, ['server', 'client'])) { | ||
throw new InvalidArgumentException('Invalid mode. Use "server" or "client".'); | ||
} | ||
} | ||
|
||
public function run() | ||
{ | ||
$config = parse_ini_file('config/config.ini', true); | ||
$file = $config['socket']['file']; | ||
|
||
$this->socket = new Socket($file); | ||
|
||
switch ($this->mode) { | ||
case 'server': | ||
$server = new Server($this->socket); | ||
$server->start(); | ||
break; | ||
case 'client': | ||
$client = new Client($this->socket); | ||
$client->start(); | ||
break; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App; | ||
|
||
class Client | ||
{ | ||
protected Socket $socket; | ||
|
||
public function __construct(Socket $socket) | ||
{ | ||
$this->socket = $socket; | ||
} | ||
|
||
public function start() | ||
{ | ||
$this->socket->connect(); | ||
|
||
echo "To exit, type 'exit'\n"; | ||
|
||
while (true) { | ||
$message = readline("Enter message: "); | ||
$this->socket->write($message); | ||
|
||
if ($message === 'exit') { | ||
$this->socket->write($message); | ||
$this->socket->close(); | ||
break; | ||
} | ||
|
||
$confirmation = $this->socket->read(); | ||
echo "Server confirmation: {$confirmation}\n"; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App; | ||
|
||
class Server | ||
{ | ||
protected Socket $socket; | ||
private $isRunning = true; | ||
|
||
public function __construct(Socket $socket) | ||
{ | ||
$this->socket = $socket; | ||
} | ||
|
||
public function start() | ||
{ | ||
$this->socket->bind(); | ||
$this->socket->listen(); | ||
$this->socket->accept(); | ||
|
||
while ($this->isRunning) { | ||
$message = $this->socket->receive(); | ||
|
||
if ($message === 'exit') { | ||
$this->isRunning = false; | ||
$this->socket->close(); | ||
} else { | ||
echo "Received: {$message}\n"; | ||
$this->socket->write("Received " . strlen($message) . " bytes"); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App; | ||
|
||
class Socket | ||
{ | ||
protected \Socket $socket; | ||
private string $path; | ||
const BACKLOG_SIZE = 5; | ||
const MAX_SIZE_MESSAGE = 2048; | ||
|
||
public function __construct($path) | ||
{ | ||
$this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0); | ||
$this->path = $path; | ||
} | ||
|
||
public function read() | ||
{ | ||
$result = socket_read($this->socket, 2048); | ||
|
||
if ($result === false) { | ||
$error = socket_last_error(); | ||
echo "Socket read error: " . socket_strerror($error); | ||
$this->close(); | ||
} | ||
return $result; | ||
} | ||
|
||
public function write($data) | ||
{ | ||
socket_write($this->socket, $data, strlen($data)); | ||
} | ||
|
||
public function listen() | ||
{ | ||
socket_listen($this->socket, self::BACKLOG_SIZE); | ||
} | ||
|
||
public function accept() | ||
{ | ||
$_socket = socket_accept($this->socket); | ||
|
||
if ($_socket === false) { | ||
$error = socket_last_error(); | ||
throw new \RuntimeException("Socket accept error: " . socket_strerror($error)); | ||
} | ||
|
||
$this->socket = $_socket; | ||
} | ||
|
||
public function connect() | ||
{ | ||
socket_connect($this->socket, $this->path); | ||
} | ||
|
||
public function bind() | ||
{ | ||
socket_bind($this->socket, $this->path); | ||
} | ||
|
||
public function close() | ||
{ | ||
socket_close($this->socket); | ||
} | ||
|
||
public function receive(): string | ||
{ | ||
socket_recv($this->socket, $message, self::MAX_SIZE_MESSAGE, 0); | ||
return $message ?? ''; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "alisa/hw6", | ||
"type": "project", | ||
"autoload": { | ||
"psr-4": { | ||
"App\\": "application.local/src/" | ||
} | ||
}, | ||
"authors": [ | ||
{ | ||
"name": "Alisa", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"require": { | ||
"ext-sockets": "*", | ||
"ext-readline": "*" | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
У Вас консольное приложение. Ему FPM не нужен.