Centos7+PHP7+Swoole

Dockerfile

FROM centos:7
MAINTAINER wenqidong
RUN yum install -y epel-release
RUN yum install -y libmcrypt-devel
RUN yum install -y pcre pcre-devel zlib \
    zlib-devel openssl openssl-devel gd gd-devel \
    libjpeg libjpeg-devel libpng libpng-devel \
    freetype freetype-devel e2fsprogs e2fsprogs-devel \
    krb5 krb5-devel lua-devel autoconf libxml2 libxml2-devel \
    glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel \
    ncurses ncurses-devel curl curl-devel libidn libidn-devel \
    libtool libtool-libs libevent-devel libevent openldap \
    openldap-devel nss_ldap openldap-clients openldap-servers \
    libtool-ltdl libtool-ltdl-devel bison libgcrypt php-mcrypt \
    libmcrypt libmcrypt-devel cmake gcc gcc-c++ ncurses-devel \
    perl-Data-Dumper libicu-devel libquadmath-devel python-devel bzip2-devel
#RUN yum install -y pcre pcre-devel libxml2 libxml2-devel zlib curl curl-devel libgcrypt php-mcrypt libmcrypt libmcrypt-devel cmake gcc gcc-c++ ncurses-devel python-devel bzip2-devel
RUN yum install -y wget
RUN wget http://hk1.php.net/distributions/php-7.1.27.tar.gz
RUN tar -zxvf php-7.1.27.tar.gz
RUN cd php-7.1.27 &&\
    #下面带有mysql扩展,由在docker centos7中没有装mysql,导致在configure时找不到mysql而报错;
    #./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-mcrypt=/usr/include --with-mysql=/usr/local/mysql --with-mysqli=/usr/local/mysql/bin/mysql_config --with-pdo-mysql=/usr/local/mysql --with-gd --with-iconv --with-zlib --enable-xml --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --enable-mbregex --enable-fpm --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --without-pear --with-gettext --enable-session --with-curl --with-jpeg-dir --with-freetype-dir --enable-opcache --with-png-dir --with-libxml-dir --with-mcrypt --with-mhash
    #去掉mysql扩展后再configure
 ./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-mcrypt=/usr/include --with-gettext --enable-opcache --with-mcrypt && \
make && make install
#/usr/local/php/bin/php

RUN yum install -y git
RUN git clone https://github.com/swoole/swoole-src.git && \
cd swoole-src/ && \
git checkout v4.3.1 && \
/usr/local/php/bin/phpize && \
./configure --with-php-config=/usr/local/php/bin/php-config && \
make && make install
RUN cp /etc/php.ini /usr/local/php/etc/
RUN sed -i '$a extension =swoole.so' /usr/local/php/etc/php.ini

VOLUME ["/var/app"]
COPY ./ws_server.php /var/app/

EXPOSE 9573
CMD ["/usr/local/php/bin/php","/var/app/ws_server.php"]

在Dockerfile同级目录下建一个ws_server.php文件,代码如下:

<?php

/**
 * Class AcePush
 *
 * 消息推送
 *
 */
class AcePush
{

    public $server;

    public function __construct()
    {
        $this->server = new Swoole\Websocket\Server("0.0.0.0", 9573);

        $this->server->set([
            'dispatch_mode' => 5,
            'worker_num' => 1,
            'heartbeat_check_interval' => 30,
            'heartbeat_idle_time' => 62,
        ]);

        $this->server->on('open', [$this, 'onOpen']);

        $this->server->on('message', [$this, 'onMessage']);

        $this->server->on('close', [$this, 'onClose']);

        $this->server->on('request', [$this, 'onRequest']);
    }

    public function onOpen(swoole_websocket_server $server, $request)
    {
        // get不存在或者uid和token有一项不存在,关闭当前连接
        if (!isset($request->get) || !isset($request->get['uid']) || !isset($request->get['token']) || !isset($request->get['client'])) {
            $this->server->close($request->fd);
            return false;
        }
        $token = $request->get['token'];
        $client = $request->get['client'];
        $uid = $request->get['uid'];
        if (false == $this->checkAccess($token, $client)) {
            $this->server->close($request->fd);
            return false;
        }
        // 把 client, uid , fd 存入mysql或redis,以便两个用户问互发消息
        $this->server->bind($request->fd, $uid);
        return true;
    }

    public function onMessage(Swoole\WebSocket\Server $server, \Swoole\WebSocket\Frame $frame)
    {
        echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";

        $data = json_decode($frame->data, true);
        if (!$data) {
            $this->invalidMessageResponse($frame, "Invalid data format");
            return false;
        }
        if (!isset($data['act']) || !isset($data['message']) || !isset($data['to_uid'])) {
            $this->invalidMessageResponse($frame, "Invalid data parameter");
            return false;
        }
        // 根据act 判断是否特殊处理
        switch ($data['act']) {
            case 'ping':
                $this->server->push($frame->fd, json_encode(['act' => 'pong', 'status' => 1]));
                return true;
                break;
            default:
        }

        if ($data['to_uid'] == 'all') {
            $this->broadcastAll($data['message'], $data['act']);
            return true;
        } else {
            $this->server->push($data['to_uid'], json_encode($data, JSON_UNESCAPED_UNICODE));
            return true;
        }
    }

    public function onClose($ser, $fd)
    {
        echo "client {$fd} closed\n";
    }

    public function onRequest($request, \Swoole\Http\Response $response)
    {
        if (!isset($request->get) || !isset($request->get['act'])) {
            $this->invalidHttpRequest();
            return false;
        }
        $act = $request->get['act'];
        if ($act == 'shutdown') {
            $this->server->shutdown();
        } elseif ($act == 'stats') {
            $data['connections'] = [];
            foreach ($this->server->connections as $fd) {
                if ($this->server->exist($fd)) {
                    $this->server->push($fd, $request->get['message']);
                }
                $client = $this->server->getClientInfo($fd);
                $client['is_connect'] = $this->server->isEstablished($fd);
                $data['connections'][] = $client;
            }
            $data['stats'] = $this->server->stats();
            $response->end(json_encode($data));
        } elseif ($act == 'push') {
            $act = 'message';
            $message = [
                'time' => date('Y-m-d H:i:s'),
                'message' => $request->get['message'] ?? '',
                'voice' => $request->get['voice'] ?? ''
            ];
            $this->broadcastAll($message, $act);
            $response->end('ok');
        } else {
            $response->end(json_encode('Undefined action'));
        }

        return true;
    }

    public function invalidHttpRequest($message = "Invalid request")
    {
        $data = [
            'status' => 403,
            'message' => $message
        ];
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
    }

    public function checkAccess($token, $client = 'admin')
    {
        if ($token) {
            $url = '';
            $resp = $this->httpRequest($url);
            return true;
        } else {
            return false;
        }
    }

    public function broadcastAll($message, $act)
    {
        $data = [
            'status' => 1,
            'message' => $message,
            'act' => $act
        ];
        $resp = json_encode($data, JSON_UNESCAPED_UNICODE);
        foreach ($this->server->connections as $fd) {
            if ($this->server->isEstablished($fd)) {
                $this->server->push($fd, $resp);
            }
        }
    }

    public function parsePushRequest($request)
    {

    }

    public function invalidMessageResponse(\Swoole\WebSocket\Frame $request, $message = "Invalid Message")
    {
        $resp = [
            'status' => 0,
            'message' => $message,
            'act' => 'none'
        ];
        $this->server->push($request->fd, json_encode($resp));
    }

    public function httpRequest($url, $data = null)
    {
        return '';
    }

    public function start()
    {
        $this->server->start();
    }

}

(new AcePush())->start();

找开bash,进入Dockerfile目录并执行:

docker build -t ws_server:v1.0 .
  • 最后一个点.表示当前目录
  • 执行后,会生成一个 ws_server v1.0的镜像,可通过docker images查看
  • v1.0表示tag,如果不指定则默认为latest