Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
41 changes: 40 additions & 1 deletion README.md
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
2 changes: 2 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
.idea/
33 changes: 33 additions & 0 deletions app/docker-compose.yaml
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:
10 changes: 10 additions & 0 deletions app/fpm/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM php:8.2-fpm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У Вас консольное приложение. Ему 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"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вместо запуска FPM нужно сделать контейнер с сервером, который при старте сразу запустится.

Binary file added app/result/2024-04-04_15-30-02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/result/2024-04-04_15-30-24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions app/www/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если Вы в корневом .gitignore указали папку без '/' перед ней, то она будет игнорироваться во всех подпапках. Можно не повторять.

/vendor
14 changes: 14 additions & 0 deletions app/www/application.local/app.php
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();
}
2 changes: 2 additions & 0 deletions app/www/application.local/config/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[socket]
file = /var/www/application.local/socket/socket_hw6.sock
45 changes: 45 additions & 0 deletions app/www/application.local/src/App.php
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;
}
}
}
36 changes: 36 additions & 0 deletions app/www/application.local/src/Client.php
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";
}
}
}
35 changes: 35 additions & 0 deletions app/www/application.local/src/Server.php
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");
}
}
}
}
74 changes: 74 additions & 0 deletions app/www/application.local/src/Socket.php
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 ?? '';
}
}
19 changes: 19 additions & 0 deletions app/www/composer.json
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": "*"
}
}
21 changes: 21 additions & 0 deletions app/www/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading