предисловие
С более длительным сроком службы чувство срочности в этой строке по-прежнему такое же, как и тогда, когда я начал работу, нет никаких сомнений в том, что как разработчик серверовсетевое программированиеЭто одно из мест, куда мне нужно проникнуть дальше:
идеи обучения
Вот мои идеи для простого обучения сетевому программированию, затем я буду следовать этому плану, чтобы постепенно изучать сетевое программирование.
step 1. 原生php实现TCP Server -> 原生php实现http协议 -> 掌握tcpdump的使用 -> 深刻理解tcp连接过程
step 2. 原生php实现多进程webserver
2.1 引入I/O多路复用
2.2 引入php协程(yield)
2.3 对比 I/O多路复用版本 和 协程版本的性能差异
step 3. 实现简单的go web框架
step 4. php c扩展实现简单的webserver
Почему я выберу использование PHP для изучения сетевого программирования? Потому что для меня наиболее привычным является PHP, второй PHP относительно прост, а сам PHP имеет соответствующую поддержку функций.
Давайте начнем первую часть исследования сегодня.
шаг 1. Собственный php реализует TCP-сервер -> собственный php реализует протокол http -> осваивает использование tcpdump -> глубоко понимает процесс подключения tcp
текст
Кратко рассмотрим общий процесс взаимодействия php как внутреннего языка:
клиент –(протокол:http) –> nginx –(протокол:fastcgi) –> php-fpm –(интерфейс:sapi) –> php
Здесь nginx выступает в роли веб-сервера и обратного прокси-сервера, преобразуя протокол http в протокол fastcgi. Смотрите здесь, некоторые друзья могут сказать:«Если PHP обрабатывает HTTP-запрос напрямую, не может вам не нужен nginx & php-fpm?"К сожалению, родной PHP не реализует протокол HTTP (да, добро пожаловать на правильные ошибки).
Тогда другой друг может сказать:«Разве собственный php не поддерживает протокол tcp? nginx может проксировать http-запросы на протокол tcp, чтобы php-fpm не использовался»., эм, да, верно. Процесс взаимодействия, описанный этим маленьким другом, выглядит следующим образом:
клиент –(протокол:http) –> nginx –(протокол:tcp) –> php
Это вроде бы не проблема, очень хорошая идея, но по идее протокол http все еще не реализован, и полученный контент все равно должен быть кучей строк. Давайте попробуем это сейчас:
шаг 1: nginx из сервиса
шаг 2: php просто реализует TCP-сервер, простой код выглядит следующим образом
<?php
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '127.0.0.1', '8889');
socket_listen($server);
while (true) {
$client = socket_accept($server);
if (! $client) {
continue;
}
$request = socket_read($client, 1024);
// 查看接收到的内容
var_dump($request);
socket_close($client);
}
Шаг 3: HTTP-запрос обратного прокси-сервера nginx к указанному выше серверу TCP, конфигурация выглядит следующим образом.
upstream tcp_server {
ip_hash;
server 127.0.0.1:8889 max_fails=3 fail_timeout=5;
}
server {
listen 80;
server_name test.local;
access_log /tmp/logs/nginx/test.access.log main;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://tcp_server;
}
}
Наконец мы посещаемtest.local/?aaa=1/Посмотрите на распечатанный результат, и предыдущее предположение соответствует:
string(127) "GET /?aaa=1 HTTP/1.0
X-Forwarded-For: 127.0.0.1
Host: test.local
Connection: close
User-Agent: curl/7.54.0
Accept: */*
"
Поэтому нам нужно реализовать протокол http.Поскольку протокол http реализован, мы можем напрямую использовать http в качестве веб-сервера.
клиент – (протокол:http) –> php
правильно!之后nginx的角色就是负载均衡,其实过分点你自己也可以用php做负载均衡。
Собственный php реализует TCP-сервер
Тогда давайте посмотрим, как создать простой TCP-сервер с помощью php Процесс выглядит следующим образом:
Основные задействованные функции PHP следующие:
socket_create
socket_listen
socket_accept
socket_recv || socket_read
socket_write
socket_close
Код:
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '127.0.0.1', '8889');
socket_listen($socket);
while (true) {
// accept
$client = socket_accept($server);
if (! $client) {
continue;
}
$request = socket_read($client, 1024);
socket_close($client);
echo socket_strerror(socket_last_error($server)) . "\n";
}
Запустите приведенный выше код в командной строке, а затем используйте команду nc, чтобы проверить, успешно ли установлено маленькое TCP-соединение:
(tigerb) ➜ demo git:(master) ✗ nc -z -v 127.0.0.1 8889
found 0 associations
found 1 connections:
1: flags=82<CONNECTED,PREFERRED>
outif lo0
src 127.0.0.1 port 60668
dst 127.0.0.1 port 8889
rank info not available
TCP aux info available
Connection to 127.0.0.1 port 8889 [tcp/ddi-tcp-2] succeeded!
Нет проблем, TCP-сервер работает.
Собственный php реализует HTTP-протокол
Приведенный выше простой TCP-сервер в основном вышел.Нам нужно сделать php непосредственно веб-сервером.Учтите, что веб-сервер основан на протоколе HTTP, а протокол HTTP реализован на основе протокола TCP. То есть мы можем реализовать HTTP-протокол на основе вышеупомянутого TCP-сервера. Мы улучшили блок-схему, добавив часть HTTP (оранжевая), как показано ниже.
Процесс реализации HTTP-протокола на самом деле таков:
- Может читать информацию, отправленную в запрос
- Может возвращать информацию клиентам, таким как браузеры, которые они могут понять
Протокол представляет собой не что иное, как спецификацию, согласованную обеими сторонами, и в HTTP/1.1 формат запроса и ответа в основном выглядит следующим образом.
просить:
<HTTP Method> <url> <HTTP Version>
<KEY>:<VALUE>\r\n
...
\r\n
отклик:
<HTTP Version> <HTTP Status> <HTTP Status Description>
<KEY>:<VALUE>\r\n
...
\r\n
Так что просто поставить, наш PHP требуется только для следующих спецификацийРазобратьивозвращениеПросто выведите соответствующий контент.Простой пример кода выглядит следующим образом:
/**
* php实现简单的http协议
*/
class HttpProtocol
{
/**
* 原始请求字符串
*
* @var string
*/
public $originRequestContentString = '';
/**
* 原始请求字符串拆得的列表
*
* @var array
*/
private $originRequestContentList = [];
/**
* 原始请求字符串拆得的键值对
*
* @var array
*/
private $originRequestContentMap = [];
/**
* 定义响应头信息
*
* @var array
*/
private $responseHead = [
'http' => 'HTTP/1.1 200 OK',
'content-type' => 'Content-Type: text/html',
'server' => 'Server: php/0.0.1',
];
/**
* 定义响应体信息
*
* @var string
*/
private $responseBody = '';
/**
* 响应内容
*
* @var string
*/
public $responseData = '';
/**
* 解析请求信息
*
* @param string $content
* @return void
*/
public function request($content = '')
{
if (empty($content)) {
// exception
}
$this->originRequestContentList = explode("\r\n", $this->originRequestContentString);
if (empty($this->originRequestContentList)) {
// exception
}
foreach ($this->originRequestContentList as $k => $v) {
if ($v === '') {
// 过滤空
continue;
}
if ($k === 0) {
// 解析http method/request_uri/version
list($http_method, $http_request_uri, $http_version) = explode(' ', $v);
$this->originRequestContentMap['Method'] = $http_method;
$this->originRequestContentMap['Request-Uri'] = $http_request_uri;
$this->originRequestContentMap['Version'] = $http_version;
continue;
}
list($key, $val) = explode(': ', $v);
$this->originRequestContentMap[$key] = $val;
}
}
/**
* 组装响应内容
*
* @param [type] $responseBody
* @return void
*/
public function response($responseBody)
{
$count = count($this->responseHead);
$finalHead = '';
foreach ($this->responseHead as $v) {
$finalHead .= $v . "\r\n";
}
$this->responseData = $finalHead . "\r\n" . $responseBody;
}
}
Мы можем вставить код после socket_read
while (true) {
// accept
$client = socket_accept($server);
if (! $client) {
continue;
}
$request = socket_read($client, 1024);
/**
* HTTP
*/
$http = new HttpProtocol;
$http->originRequestContentString = $request;
$http->request($request);
$http->response("Hello World");
socket_write($client, $http->responseData);
socket_close($client);
echo socket_strerror(socket_last_error($server)) . "\n";
}
последнее посещениеhttp://127.0.0.1:8889/Результат такой или браузер открывает страницу и выводит "Hello World"
(tigerb) ➜ demo git:(master) ✗ curl "http://127.0.0.1:8889/" -vv
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8889 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8889
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Server: php/0.0.1
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0
Hello World%
Эпилог
До сих пор мы просто построили веб-сервер с php, и на этой основе php может напрямую взаимодействовать с клиентом. Наконец, мы будем использовать этот простой веб-сервер для захвата пакетов через tcpdump для анализа процесса соединения tcp. подожди~