diff --git a/README.md b/README.md index e16b2a49b..cbd25f28f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ -# PHP_2023 +# Консольный чат на сокетах -https://otus.ru/lessons/razrabotchik-php/?utm_source=github&utm_medium=free&utm_campaign=otus +## Запуск +``` +php app.php start-server +php app.php start-client +``` \ No newline at end of file diff --git a/code/composer.json b/code/composer.json index 0f497614d..3efd312ea 100644 --- a/code/composer.json +++ b/code/composer.json @@ -1,15 +1,18 @@ { - "name": "propan13/chat", + "name": "propan13/socket-chat", "type": "project", - "autoload": { - "psr-4": { - "App\\": "src/" - } - }, "authors": [ { "name": "Dmitry Ivanov" } ], - "require": {} + "require": { + "ext-sockets": "*", + "vlucas/phpdotenv": "^5.6" + }, + "autoload": { + "psr-4": { + "Propan13\\App\\": "src/" + } + } } diff --git a/code/composer.lock b/code/composer.lock index b4fbc70c3..60775833b 100644 --- a/code/composer.lock +++ b/code/composer.lock @@ -4,15 +4,478 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "996321a89d585eb4a8be94efd8d2caf6", - "packages": [], + "content-hash": "8e726de5e1598d225d0a8a262230faeb", + "packages": [ + { + "name": "graham-campbell/result-type", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:16:48+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.2", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-11-12T21:59:55+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:43:29+00:00" + } + ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "ext-sockets": "*" + }, "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/code/config/.ENV.example b/code/config/.ENV.example new file mode 100644 index 000000000..80d673d3d --- /dev/null +++ b/code/config/.ENV.example @@ -0,0 +1,3 @@ +CHAT_SOCKET_PATH = +CHAT_SOCKET_LENGTH = +ROUTES_PATH = \ No newline at end of file diff --git a/code/config/commands.php b/code/config/commands.php new file mode 100644 index 000000000..a8deb6aa0 --- /dev/null +++ b/code/config/commands.php @@ -0,0 +1,9 @@ + Server::class, + 'start-client' => Client::class +]; diff --git a/code/public/app.php b/code/public/app.php new file mode 100644 index 000000000..b847d4f5e --- /dev/null +++ b/code/public/app.php @@ -0,0 +1,16 @@ +run(); +} catch (\Exception $e) { + echo $e->getMessage(); +} + + diff --git a/code/src/Application.php b/code/src/Application.php new file mode 100644 index 000000000..6de8a1b48 --- /dev/null +++ b/code/src/Application.php @@ -0,0 +1,20 @@ +load(); + $command = new Route($_SERVER['argv'][1]); + $router = new Router(); + $router->dispatch($command); + } +} \ No newline at end of file diff --git a/code/src/Chat/Client.php b/code/src/Chat/Client.php new file mode 100644 index 000000000..c452db0ee --- /dev/null +++ b/code/src/Chat/Client.php @@ -0,0 +1,23 @@ +create(); + $this->connect(); + } + + public function launch(): Generator + { + while (true) { + yield 'Введите сообщение: '; + $this->write(readline()); + yield 'Received ' . $this->read() . PHP_EOL; + } + } +} \ No newline at end of file diff --git a/code/src/Chat/Server.php b/code/src/Chat/Server.php new file mode 100644 index 000000000..c4a90a7ce --- /dev/null +++ b/code/src/Chat/Server.php @@ -0,0 +1,31 @@ +create(); + $this->bind(true); + $this->listen(); + } + + public function launch(): Generator + { + yield('Ожидаем сообщения' . PHP_EOL); + $client = $this->accept(); + while (true) { + $message = $this->receive($client); + if ($message) { + yield $message . PHP_EOL; + $this->write((string)strlen($message), $client); + } + } + } +} diff --git a/code/src/Chat/Socket.php b/code/src/Chat/Socket.php new file mode 100644 index 000000000..f21f035d2 --- /dev/null +++ b/code/src/Chat/Socket.php @@ -0,0 +1,96 @@ +init(); + foreach ($this->launch() as $message) { + echo $message . PHP_EOL; + } + } + + public function create(): void + { + $this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0); + if ($this->socket === false) { + throw new Exception('socket_create() failed: ' . socket_strerror(socket_last_error())); + } + } + + public function bind(bool $isNew = false): void + { + if ($isNew && file_exists($_ENV['CHAT_SOCKET_PATH'])) { + unlink($_ENV['CHAT_SOCKET_PATH']); + } + + if (!socket_bind($this->socket, $_ENV['CHAT_SOCKET_PATH'])) { + throw new Exception('Could not bind to socket'); + } + } + + public function listen(): void + { + $listen = socket_listen($this->socket,1); + if ($listen === false) { + throw new Exception('Could not listen on socket'); + } + } + + public function accept() + { + $accept = socket_accept($this->socket); + if ($accept === false) { + throw new Exception('Could not accept socket'); + } + return $accept; + } + + public function connect(): void + { + $connect = socket_connect($this->socket, $_ENV['CHAT_SOCKET_PATH'], 0); + if ($connect === false) { + throw new Exception('Could not connect to socket'); + } + } + + public function write(string $msg, $socket = null): void + { + if ($socket === null) { + $socket = $this->socket; + } + $sent = socket_write($socket, $msg, strlen($msg)); + if ($sent === false) { + throw new Exception('Could not write to socket'); + } + } + + public function receive($socket): string + { + socket_recv($socket, $message, $_ENV['CHAT_SOCKET_LENGTH'], 0); + return $message; + } + + public function read(): false|string + { + return socket_read($this->socket, $_ENV['CHAT_SOCKET_LENGTH']); + } +} \ No newline at end of file diff --git a/code/src/Client/Client.php b/code/src/Client/Client.php deleted file mode 100644 index 5061c5e76..000000000 --- a/code/src/Client/Client.php +++ /dev/null @@ -1,63 +0,0 @@ -run(); -} catch (Exception $e) { - echo $e->__toString(); -} diff --git a/code/src/Router/Route.php b/code/src/Router/Route.php new file mode 100644 index 000000000..676784ccc --- /dev/null +++ b/code/src/Router/Route.php @@ -0,0 +1,20 @@ +commandName = $commandName; + } + + public function getCommandName(): string + { + return $this->commandName; + } +} \ No newline at end of file diff --git a/code/src/Router/Router.php b/code/src/Router/Router.php new file mode 100644 index 000000000..6ba121229 --- /dev/null +++ b/code/src/Router/Router.php @@ -0,0 +1,43 @@ +routes = $this->getRoutes(); + } + + /** + ** @throws Exception + **/ + public function dispatch(Route $route): void + { + if ($this->issetRoute($route)) { + (new $this->routes[$route->getCommandName()])(); + } else { + throw new Exception('Command not found'); + } + } + + public function getRoutes(): array + { + $routes = include_once $_ENV['ROUTES_PATH']; + if (is_array($routes)) { + return $routes; + } + return []; + } + + private function issetRoute(Route $route): bool + { + return isset($this->routes[$route->getCommandName()]); + } +} \ No newline at end of file diff --git a/code/src/Server/Server.php b/code/src/Server/Server.php deleted file mode 100644 index 86343d163..000000000 --- a/code/src/Server/Server.php +++ /dev/null @@ -1,59 +0,0 @@ -run(); -} catch (Exception $e) { - echo $e->__toString(); -} diff --git a/docker-compose.yaml b/docker-compose.yaml index a4f55417a..cea4ed611 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,28 +1,25 @@ version: '3' - services: - client: + server: build: context: ./fpm dockerfile: Dockerfile - image: myapp/php - container_name: app1 + image: propan13/php + container_name: server volumes: - - ./code:/data/ + - ./code:/data/ networks: - app-network - - server: + client: build: context: ./fpm dockerfile: Dockerfile - image: myapp/php - container_name: app2 + image: propan13/php + container_name: client volumes: - ./code:/data/ networks: - app-network - networks: app-network: driver: bridge \ No newline at end of file diff --git a/fpm/Dockerfile b/fpm/Dockerfile index e6d00f40e..715c89125 100644 --- a/fpm/Dockerfile +++ b/fpm/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2-fpm +FROM php:8.3-fpm RUN apt-get update && apt-get install -y \ build-essential \