>  기사  >  백엔드 개발  >  PHP로 동시성 높은 서버 구현

PHP로 동시성 높은 서버 구현

步履不停
步履不停원래의
2019-06-19 10:55:234022검색

PHP로 동시성 높은 서버 구현

높은 동시성에서는 I/O 다중화를 우회할 방법이 없으며 특정 플랫폼 Linux의 경우 epoll을 우회할 방법이 없습니다. 효율적으로 설명되지 않으므로 관심 있는 학생들이 스스로 검색하고 조사할 수 있습니다.

PHP에서 epoll을 어떻게 플레이하나요? 먼저 libevent 라이브러리를 설치한 후 이벤트 확장이나 libevent 확장을 설치해야 즐겁게 플레이할 수 있습니다.

libevent 라이브러리와 libevent 확장의 차이점을 혼동하는 사람들도 있습니다. epoll의 C 언어 버전입니다. 캡슐화는 PHP와 관련이 없습니다. libevent 확장은 PHP와 libevent 라이브러리 간의 통신 브리지입니다. 실제로, 많은 PHP 확장이 이를 수행합니다. 몇 가지 훌륭한 C 언어 라이브러리가 있습니다. PHP가 이를 직접 사용하려면 PHP 확장을 통해 PHP에 연결할 수 있습니다.

libevent 확장이나 이벤트 확장 중 하나를 선택하세요. 저는 개인적으로 이벤트 확장이 더 객체 지향적이기 때문에 선호합니다. http://pecl.php.net으로 이동하여 PHP 버전에 해당하는 확장 기능을 검색하고 다운로드한 후 컴파일하고 설치하면 컴퓨터에 설치된 여러 버전의 PHP로 컴파일할 때 주의하세요. phpize 버전과 일치합니다. 일반적인 5단계는 다음과 같습니다.

phpize
./configure
make
make install
php -m | grep event #看看装上了没

우리가 구현하려는 서버, 전송 계층은 TCP 프로토콜, 애플리케이션 계층 프로토콜은 너무 많고 복잡하며 공간이 제한됩니다. , 우리는 단순히 HTTP 서버를 예로 사용하겠습니다. HTTP 프로토콜 자체는 매우 복잡하고 구현해야 할 세부 사항이 많으며 HTTP 프로토콜을 완전히 구현하지는 않습니다.

먼저 소켓을 생성합니다. 3단계, 소켓_생성, 소켓_바인드, 소켓_리스닝이 왜 3단계인가요? 매우 간단합니다. 전송 계층 프로토콜이 무엇이든 아래 네트워크 계층 프로토콜 버전(IPV4 또는 IPV6)을 선택해야 합니다. 전송 계층 작동 방법(전이중, 반이중 또는 단순, TCP)을 선택해야 합니다. 또는 UDP 중 하나를 선택해야 합니다. 소켓_생성에는 다음 세 가지 옵션이 있습니다. 네트워크 계층과 전송 계층을 결정한 후 모니터링할 포트를 알려주어야 하며, 이는 소켓_바인드에 해당합니다. 그런 다음 모니터링을 활성화하고 클라이언트 대기열을 지정해야 합니다. 길이, 이것이 Socket_listen이 하는 일입니다.

생성 후 프로세스는 한 번에 최대 하나의 연결을 보유할 수 있습니다. 동시에 더 많은 연결을 요청하는 경우 소켓_리스닝에 지정된 대기열 길이를 초과하면 기다려야 합니다. 반환됩니다. 여러 프로세스의 경우에도 마찬가지입니다. 여러 프로세스에는 여러 개의 동시 프로세스가 있으며 프로세스의 컨텍스트 전환에는 시간이 많이 걸리고 힘들기 때문에 전체 시스템이 비효율적입니다.

상관없습니다. epoll이 있습니다. 수천 건의 요청을 처리하는 것은 꿈이 아닙니다. 먼저 Reactor를 구현해야 합니다. libevent 라이브러리는 Reactor 모드입니다. 함수를 직접 호출하는 것은 Reactor 모드를 사용하는 것이므로 PHP에서 Reactor 모드를 어떻게 구현해야 할지 고민할 필요가 없습니다.

<?php

use Event;
use EventBase;

class Reactor
{
    protected $reactor;

    protected $events;

    public static $instance = null;

    const READ = Event::READ | Event::PERSIST;

    const WRITE = Event::WRITE | Event::PERSIST;

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
            self::$instance->reactor = new EventBase;
        }

        return self::$instance;
    }

    public function add($fd, $what, $cb, $arg = null)
    {
        switch ($what) {
            case self::READ:
                $event = new Event($this->reactor, $fd, self::READ, $cb, $arg);
                break;
            case self::WRITE:
                $event = new Event($this->reactor, $fd, self::WRITE, $cb, $arg);
                break;
            default:
                $event = new Event($this->reactor, $fd, $what, $cb, $arg);
                break;
        }

        $event->add();
        $this->events[(int) $fd][$what] = $event;
    }

    public function del($fd, $what = 'all')
    {
        $events = $this->events[(int) $fd];
        if ($what == 'all') {
            foreach ($events as $event) {
                $event->free();
            }
        } else {
            if ($what != self::READ && $what != self::WRITE) {
                throw new \Exception('不存在的事件');
            }

            $events[$what]->free();
        }
    }

    public function run()
    {
        $this->reactor->loop();
    }

    public function stop()
    {
        foreach ($this->events as $events) {
            foreach ($events as $event) {
                $event->free();
            }
        }
        $this->reactor->stop();
    }
}

위 코드는 매우 간단합니다. 개념을 간략하게 설명하겠습니다. EventBase는 Event 인스턴스를 포함하는 컨테이너이므로 위 코드는 매우 이해하기 쉽습니다. 그런 다음 Server.

<?php use Throwable;use Monolog\Handler\StreamHandler;class Server{	protected $ip;	protected $port;	protected $socket;	protected $reactor;	public function __construct($ip, $port)
	{		$this->ip = $ip;		$this->port = $port;
	}	public function start()
	{
	    $socket = $this->createTcpConnection();
	    stream_set_blocking($socket, false);

	    Reactor::getInstance()->add($socket, Reactor::READ, function($socket) {
                $conn = stream_socket_accept($socket);
                stream_set_blocking($conn, false);
                (new Connection($conn))->handle();
        });

            Reactor::getInstance()->run();
	}	public function createTcpConnection()
	{
		$schema = sprintf("tcp://%s:%d", $this->ip, $this->port);
		$socket = stream_socket_server($schema, $errno, $errstr);		if ($errno) {			throw new \Exception($errstr);
		}		return $socket;
	}
}

Connection

<?phpclass Connection{    protected $conn;    protected $read_buffer = &#39;&#39;;    protected $write_buffer = &#39;&#39;;    public function __construct($conn)
    {        $this->conn = $conn;
    }    public function handle()
    {
        Reactor::getInstance()->add($this->conn, Reactor::READ, \Closure::fromCallable([$this, 'read']));
    }    private function read($conn)
    {        $this->read_buffer = '';        if (is_resource($conn)) {            while ($content = fread($conn, 65535)) {                $this->read_buffer .= $content;
            }
        }        if ($this->read_buffer) {
            Reactor::getInstance()->add($conn, Reactor::WRITE, \Closure::fromCallable([$this, 'write']));
        } else {
            Reactor::getInstance()->del($conn);
            fclose($conn);
        }
    }    private function write($conn)
    {        if (is_resource($conn)) {
            fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=utf8\r\nContent-Length:11\r\nConnection: keep-alive\r\n\r\nHello!world");
        }
    }

}

먼저 소켓의 3단계 프로세스를 생성하고 비차단 모드로 설정합니다. 그런 다음 읽기 가능한 이벤트를 수신하기 위해 소켓을 Reactor에 추가합니다. 읽기 가능은 데이터가 있을 때만 버퍼를 읽을 수 있음을 의미합니다. 새로운 연결이 다가오고 있음을 나타내는 readable 이벤트가 발생합니다. stream_socket_accept를 사용하여 새 연결 Conn을 수신하고 Conn을 Reactor에 넣어 Readable 이벤트가 발생하면 클라이언트가 이를 나타냅니다. 데이터가 전송되지 않을 때까지 루프에서 읽은 다음 쓰기 가능한 이벤트를 수신하기 위해 Conn을 넣습니다. 이는 클라이언트 데이터가 전송되었음을 의미합니다. 응답. stream_socket_accept接收新连接Conn,把Conn放到Reactor中监听可读事件,可读事件发生,说明客户端有数据发送过来了,循环读直到没数据,然后把Conn放到Reactor中监听可写事件,可写事件发生,说明客户端数据发送完了,把协议组装一下写入响应。

应用层如果是HTTP协议要注意一下Connection: keep-alive头,因为要复用连接,不要一写完就关闭连接。

撸完收工,用ab测一下并发,加-k

애플리케이션 레이어가 HTTP 프로토콜인 경우 Connection: keep-alive 헤더에 주의하세요. 연결을 재사용해야 하므로 연결이 완료되자마자 닫지 마세요.

작업을 마친 후 ab를 사용하여 동시성을 테스트하고 -k 매개변수를 추가하여 연결을 재사용합니다. i5+8G, 3W 동시성은 물론 문제가 되지 않습니다. 여기에는 디스크 I/O가 없습니다. 실제 상황에서는 디스크에서 파일을 읽어야 하며, 파일을 읽으려면 Linux 시스템 호출이 필요하며, 상대적으로 비용이 많이 드는 몇 가지 파일 복사 작업이 있습니다. 일반적으로 사용되는 솔루션은 sendfile입니다. FD에서 다른 FD로 직접 복사하는 것이 더 효율적입니다. 단점은 PHP에 미리 만들어진 sendfile 확장이 없으며 이를 직접 수행해야 하며 개발 비용이 약간 높다는 것입니다.

PHP로 동시성 높은 서버 구현ab 테스트 PO 사진:

🎜🎜🎜

이것은 PHP에서 동시성이 높은 서버를 구현하는 아이디어입니다. EPOLL로 해결하는 한 아이디어는 동일합니다. FD 이벤트를 모니터링하려면 Reactor 아래에 두십시오. . 물론 이는 가장 간단한 모델일 뿐이며 개선할 수 있는 부분이 많습니다. 예를 들어 멀티 프로세스, nginx 복사, 하나의 메인 프로세스 + N 작업자 프로세스, 멀티 프로세스의 목적은 멀티 코어를 사용하는 것입니다. 병렬로 작업합니다. C 언어 구현에서도 마찬가지이지만 libevent 라이브러리를 사용하고 EPOLL을 직접 캡슐화할 수는 없습니다. 결국 libevent 라이브러리는 약간 무겁고 libevent에서는 많은 것을 사용할 수 없습니다. 많은 데이터 구조와 데이터 구조에 정의된 함수가 있습니다. 작업을 작성해야 하고, GC가 없으며, 메모리를 직접 관리해야 하며, 여러 프로세스를 실행할 때 좋은 디자인이 있어야 합니다. IPC 프로세스 간 통신에 대해서는 PHP보다 개발 난이도가 훨씬 높으며 개발 주기도 매우 깁니다. 관심이 있는 경우 학생들이 직접 사용해 볼 수도 있습니다.

PHP 관련 기술 기사를 더 보려면 PHP Tutorial 칼럼을 방문하여 알아보세요!

위 내용은 PHP로 동시성 높은 서버 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.