swoole深入学习 2. tcp Server和tcp Client

这节来学习Swoole最基础的ServerClient。会通过创建一个tcp Server来讲解。

server

<?php
class Server
{
    private $serv;

    public function __construct()
    {
        $this->serv = new Swoole\Server(‘127.0.0.1‘, 9501);

        //当启动一个Swoole应用时,一共会创建2 + n + m个进程,2为一个Master进程和一个Manager进程,其中n为Worker进程数。m为TaskWorker进程数。

        //默认如果不设置,swoole底层会根据当前机器有多少CPU核数,启动对应数量的Reactor线程和Worker进程。我机器为4核的。Worker为4。TaskWorker为0。 

        //下面我来设置worker_num = 10。看下启动了多少个进程

        $this->serv->set([
            ‘worker_num‘ => 10,
            //‘task_worker_num‘ => 2,
            ‘deamonize‘ => true,
        ]);

        //启动10个work,总共12个进程。
        /*
        ?  Event git:(master) pstree |grep server.php
    |   \-+= 54172 yangyi php server.php  #Master进程
    |     \-+- 54173 yangyi php server.php  # Manager 进程
    |       |--- 54174 yangyi php server.php  #Work 进程
    |       |--- 54175 yangyi php server.php
    |       |--- 54176 yangyi php server.php
    |       |--- 54177 yangyi php server.php
    |       |--- 54178 yangyi php server.php
    |       |--- 54179 yangyi php server.php
    |       |--- 54180 yangyi php server.php
    |       |--- 54181 yangyi php server.php
    |       |--- 54182 yangyi php server.php
    |       \--- 54183 yangyi php server.php
         *
         */

        //增加新的监控的ip:post:mode
        $this->serv->addlistener("::1", 9500, SWOOLE_SOCK_TCP);

        //监听事件
        /*
         *
         * - onStart
         * - onShutdown
         * - onWorkerStart
         * - onWorkerStop
         * - onTimer
         * - onConnect
         * - onReceive
         * - onClose
         * - onTask
         * - onFinish
         * - onPipeMessage
         * - onWorkerError
         * - onManagerStart
         * - onManagerStop
         */

        $this->serv->on(‘Start‘, array($this, ‘onStart‘));
        $this->serv->on(‘Connect‘, array($this, ‘onConnect‘));
        $this->serv->on(‘Receive‘, array($this, ‘onReceive‘));
        $this->serv->on(‘Close‘, array($this, ‘onClose‘));

        //master进程启动后, fork出Manager进程, 然后触发ManagerStart
        $this->serv->on(‘ManagerStart‘, function (\swoole_server $server){
            echo "On manager start.";
        });

        //manager进程启动,启动work进程的时候调用 workid表示第几个id, 从0开始。
        $this->serv->on(‘WorkerStart‘, function($serv, $workerId) {
            echo $workerId . ‘---‘;
        });

        //当一个work进程死掉后,会触发
        $this->serv->on(‘WorkerStop‘, function() {
            echo ‘--stop‘;
        });

        //启动
        $this->serv->start();
    }

    //启动server时候会触发。
    public function onStart( $serv ) {
        echo "Start\n";
    }

    //client连接成功后触发。
    public function onConnect( $serv, $fd, $from_id ) {
        $a = $serv->send( $fd, "Hello {$fd}!" );
        //var_dump($a); //成功返回true
    }

    //接收client发过来的请求
    public function onReceive( swoole_server $serv, $fd, $from_id, $data ) {
        echo "Get Message From Client {$fd}:{$data}\n";
        //$serv->send($fd, $data);
        //关闭该work进程
        //$serv->stop();
        //宕机
        //$serv->shutdown();

        //主动关闭 客户端连接,也会触发onClose事件
        //$serv->close($fd);

            $serv->send($fd, $data);

            //$list = $serv->connection_list();
//           foreach ($list as $fd) {
//               $serv->send($fd, $data);
//           }
        }
    }

    //客户端断开触发
    public function onClose( $serv, $fd, $from_id ) {
        echo "Client {$fd} close connection\n";
    }

}

//输出swoole的版本
echo swoole_version(); // 1.9.0

//输出本机iP
var_dump(swoole_get_local_ip()); 

/**
    array(1) {
      ‘en4‘ =>
      string(13) "172.16.71.149"
}
*/

// 启动服务器
$server = new Server();

我们启动服务端server:

$ php server.php
0--start
2--start
Start
1--start
3--start
4--start
5--start
6--start
manager start.
7--start
9--start
8--start

我们来分析整个server 启动的步骤:

  1. 启动php server.php后,当前进程fork出Master进程,然后退出。
  2. Master进程启动成功之后,fork出Manager进程,并触发OnManagerStart事件。
  3. Manager进程启动成功时候,fork出Worker进程,并触发OnWorkerStart事件。

同步client

server端好了,那么就会需要client端来连接,swoole里面client分为同步和异步,先来一个同步clent客户端。

<?php

// sync 同步客户端
class client
{
    private $client;

    public function __construct()
    {
        $this->client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP);
        $this->client->connect(‘127.0.0.1‘, 9501, 1);
    }

    public function connect()
    {
        //fwrite(STDOUT, "请输入消息:");
        //$msg = trim(fgets(STDIN));
        $msg = rand(1,12);

        //发送给消息到服务端
        $this->client->send( $msg );

        //接受服务端发来的信息
        $message = $this->client->recv();
        echo "Get Message From Server:{$message}\n";

        //关闭客户端
        $this->client->close();

    }
}
$client = new Client();
$client->connect();

同步client是同步阻塞的。一整套connect->send()->rev()->close()是同步进行的。

所以,如果是大量的循环数据,就不适合同步client了:

比如下面:

<?php

// sync 同步客户端
class client
{
    private $client;

    public function __construct()
    {
        var_dump(swoole_get_local_ip());
        $this->client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP);
        $this->client->connect(‘127.0.0.1‘, 9501, 1);

        $i = 0;
        while ($i < 100) {
            $this->client->send($i."\n");
            $message = $this->client->recv();
            echo "Get Message From Server:{$message}\n";
            $i++;
        }
    }
}
$client = new Client();

打印的结果就是顺序执行。要是想要异步了。

异步client

<?php

//异步客户端

$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client->on("connect", function($cli) {

    var_dump($cli->isConnected()); // true
    var_dump($cli->getsockname()); //[‘port‘ => 57305, ‘host‘=> ‘127.0.0.1‘]
    var_dump($cli->sock); // 5

    $i = 0;
    while ($i < 100) {
        $cli->send($i."\n");
        $i++;
    }
    //关闭
    //$cli->close();
});

$client->on("receive", function($cli, $data){
    echo "Receive: $data";
});

$client->on("error", function(swoole_client $cli){
    echo "error\n" . $cli->errCode;
});

$client->on("close", function(swoole_client $cli){
    echo "Connection close\n";
});

$client->connect(‘127.0.0.1‘, 9501);

这样就是一个异步的client了,处理更快,但是只支持php的cli模式

server与client交互

总结一下client与server的连接过程:

  1. Client主动Connect的时候,Client实际上是与Master进程中的某个Reactor线程发生了连接。
  2. 当TCP的三次握手成功了以后,由这个Reactor线程将连接成功的消息告诉Manager进程,再由Manager进程转交给Worker进程。
  3. 在这个Worker进程中触发了OnConnect的方法。
  4. 当Client向Server发送了一个数据包的时候,首先收到数据包的是Reactor线程,同时Reactor线程会完成组包,再将组好的包交给Manager进程,由Manager进程转交给Worker。
  5. 此时Worker进程触发OnReceive事件。
  6. 如果在Worker进程中做了什么处理,然后再用Send方法将数据发回给客户端时,数据则会沿着这个路径逆流而上。

关于上面说到的几个进程,解释下:

Master进程是一个多线程进程,其中有一组非常重要的线程,叫做Reactor线程(组),每当一个客户端连接上服务器的时候,都会由Master进程从已有的Reactor线程中,根据一定规则挑选一个,专门负责向这个客户端提供维持链接、处理网络IO与收发数据等服务。

而Manager进程,某种意义上可以看做一个代理层,它本身并不直接处理业务,其主要工作是将Master进程中收到的数据转交给Worker进程,或者将Worker进程中希望发给客户端的数据转交给Master进程进行发送。另外,Manager进程还负责监控Worker进程,如果Worker进程因为某些意外挂了,Manager进程会重新拉起新的Worker进程,有点像Supervisor的工作。

Worker进程了,顾名思义,Worker进程其实就是处理各种业务工作的进程,Manager将数据包转交给Worker进程,然后Worker进程进行具体的处理,并根据实际情况将结果反馈给客户端。

task_worker

在swoole中work进程分为EventWorker和TaskWorker,对应的配置文件设置为:

$this->serv->set([
    ‘worker_num‘ => 10,  #EventWorker
    ‘task_worker_num‘ => 2,  #TaskWorker
    ‘deamonize‘ => true,
]);

worker是基于event触发,而task则是manager直接生成的子进程。那么他们有什么区别呢?

共同点是:他们都是最底层负责处理业务的进程。

Swoole的业务逻辑部分是同步阻塞运行的,如果遇到一些耗时较大的操作,例如访问数据库、广播消息等,就会影响服务器的响应速度。因此Swoole提供了Task功能,将这些耗时操作放到另外的进程去处理,当前woker进程继续执行后面的逻辑。运行Task,需要在swoole服务中配置参数task_worker_num,即可开启task功能。此外,必须给swoole_server绑定两个回调函数:onTaskonFinish。这两个回调函数分别用于执行Task任务和处理Task任务的返回结果。

先来写一个demo,来如何用 taskWoker来处理业务。

taskServer.php

<?php
/**
 * Created by PhpStorm.
 * User: yangyi
 * Date: 2016/12/7
 * Time: 16:16
 */

class taskServer
{
    private $serv;

    /**
     * [__construct description]
     * 构造方法中,初始化 $serv 服务
     */
    public function __construct() {
        $this->serv = new Swoole\Server(‘0.0.0.0‘, 9501);
        //初始化swoole服务
        $this->serv->set(array(
            ‘worker_num‘  => 8,
            ‘daemonize‘   => false, //是否作为守护进程,此配置一般配合log_file使用
            ‘max_request‘ => 1000,
            ‘log_file‘    => ‘./swoole.log‘,
            ‘task_worker_num‘ => 8
        ));

        //设置监听
        $this->serv->on(‘Start‘, array($this, ‘onStart‘));
        $this->serv->on(‘Connect‘, array($this, ‘onConnect‘));
        $this->serv->on("Receive", array($this, ‘onReceive‘));
        $this->serv->on("Close", array($this, ‘onClose‘));
        $this->serv->on("Task", array($this, ‘onTask‘));
        $this->serv->on("Finish", array($this, ‘onFinish‘));

        //开启
        $this->serv->start();
    }

    public function onStart($serv) {
        echo SWOOLE_VERSION . " onStart\n";
    }

    public function onConnect($serv, $fd) {
        echo $fd."Client Connect.\n";
    }

    public function onReceive($serv, $fd, $from_id, $data) {
        echo "Get Message From Client {$fd}:{$data}\n";
        // send a task to task worker.
        $param = array(
            ‘fd‘ => $fd
        );
        // start a task
        $serv->task(json_encode($param));

        echo "Continue Handle Worker\n";
    }

    public function onClose($serv, $fd) {
        echo "Client Close.\n";
    }

    public function onTask($serv, $task_id, $from_id, $data) {
        echo "This Task {$task_id} from Worker {$from_id}\n";
        echo "Data: {$data}\n";
        for($i = 0 ; $i < 200 ; $i ++ ) {
            sleep(1);
            echo "Task {$task_id} Handle {$i} times...\n";
        }
        $fd = json_decode($data, true);
        $serv->send($fd[‘fd‘] , "Data in Task {$task_id}");
        return "Task {$task_id}‘s result";
    }

    public function onFinish($serv,$task_id, $data) {
        echo "Task {$task_id} finish\n";
        echo "Result: {$data}\n";
    }
}

$server = new taskServer();

taskClient.php 异步的客户端

<?php
/**
 * Created by PhpStorm.
 * User: yangyi
 * Date: 2016/12/7
 * Time: 16:18
 */
class taskClient
{
    private $client;

    public function __construct() {
        $this->client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
        $this->client->on(‘Connect‘, array($this, ‘onConnect‘));
        $this->client->on(‘Receive‘, array($this, ‘onReceive‘));
        $this->client->on(‘Close‘, array($this, ‘onClose‘));
        $this->client->on(‘Error‘, array($this, ‘onError‘));
    }

    public function connect() {
        if(!$fp = $this->client->connect("127.0.0.1", 9501 , 1)) {
            echo "Error: {$fp->errMsg}[{$fp->errCode}]\n";
            return;
        }
    }

    //connect之后,会调用onConnect方法
    public function onConnect($cli) {
        fwrite(STDOUT, "Enter Msg:");
        swoole_event_add(STDIN,function(){
            fwrite(STDOUT, "Enter Msg:");
            $msg = trim(fgets(STDIN));
            $this->send($msg);
        });
    }

    public function onClose($cli) {
        echo "Client close connection\n";
    }

    public function onError() {

    }

    public function onReceive($cli, $data) {
        echo "Received: ".$data."\n";
    }

    public function send($data) {
        $this->client->send($data);
    }

    public function isConnected($cli) {
        return $this->client->isConnected();
    }

}

$client = new taskClient();
$client->connect();

运行一下:

php taskServer.php 
php taskClient.php

服务端打印:

$ php task_server.php
1.9.0 onStart
1Client Connect.
Get Message From Client 1:12345
Continue Handle Worker
This Task 0 from Worker 3
Data: {"fd":1}
Task 0 Handle 0 times...
Task 0 Handle 1 times...
Task 0 finish
Result: Task 0‘s result

客户端打印:

$ php task_client.php
Enter Msg:12345
Enter Msg:Received: Data in Task 0

这里面有几点需要注意:

1. 运行Task,必须要在swoole服务中配置参数task_worker_num,此外,必须给swoole_server绑定两个回调函数:onTaskonFinish。 
2. onTash 要return 数据 
3. onFinish 会接收到onTash的数据,标记成完成。 
4. swoole_event_add 把输入绑定成事件,这个后续将,这样client就可以连续的多次输入。

swoole的架构

上面说了这么,图表总结一下swoole结构:

swoole采用 多线程Reactor+多进程Worker

swoole的处理连接流程图如下:

当请求到达时,swoole是这样处理的:

请求到达 Main Reactor 


Main Reactor根据Reactor的情况,将请求注册给对应的Reactor 
(每个Reactor都有epoll。用来监听客户端的变化) 


客户端有变化时,交给worker来处理 


worker处理完毕,通过进程间通信(比如管道、共享内存、消息队列)发给对应的reactor。 


reactor将响应结果发给相应的连接 


请求处理完成

因为reactor基于epoll,所以每个reactor可以处理无数个连接请求。 如此,swoole就轻松的处理了高并发。

参考资料:

http://rango.swoole.com/archives/305

https://github.com/szyhf/swoole_study/blob/master/Swoole%E7%9A%84%E8%BF%9B%E7%A8%8B%E6%A8%A1%E5%9E%8B.md

http://happyliu.blog.51cto.com/501986/1574923

https://segmentfault.com/a/1190000007614502

原文地址:https://www.cnblogs.com/sunsky303/p/9255200.html

时间: 2024-07-31 03:34:40

swoole深入学习 2. tcp Server和tcp Client的相关文章

[转] 3个学习Socket编程的简单例子:TCP Server/Client, Select

以前都是采用ACE的编写网络应用,最近由于工作需要,需要直接只用socket接口编写CS的代码,重新学习这方面的知识,给出自己所用到的3个简单例子,都是拷贝别人的程序.如果你能完全理解这3个例子,估计socket编程就已经基本入门了. 建议:1) 多多查查所用到的网络接口; 2) 最好有一本书,如UNIX环境高级编程,UNIX网络编程,可查询:3) 可以直接使用书上的例子更好. http://blog.csdn.net/zhenjing/article/details/4770490 TCP C

1.socket编程:socket编程,网络字节序,函数介绍,IP地址转换函数,sockaddr数据结构,网络套接字函数,socket相关函数,TCP server和client

 1  Socket编程 socket这个词可以表示很多概念: 在TCP/IP协议中,"IP地址+TCP或UDP端口号"唯一标识网络通讯中的一个进程,"IP 地址+端口号"就称为socket. 在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接.socket本身有"插座"的意思,因此用来描述网络连 接的一对一关系. TCP/IP协议最早在BSD UNIX上实现,

Modbus库开发笔记之三:Modbus TCP Server开发

在完成了前面的工作后,我们就可以实现有针对性的应用了,首先我们来实现Modbus TCP的服务器端应用.当然我们不是做具体的应用,而是对Modbus TCP的服务器端应用进行封装以供有需要时调用. 这里我们不涉及TCP的协议,这部分与Modbus没有必然联系,我们只是在其应用层运行Modbus协议而已. 对于Modbus TCP的服务器我们需要实现几个功能:首先是对接收到客户端命令进行解析,我们只实现前面提到的8中常用的功能吗的支持.其次在解析完成后,我们要实现对应各种功能码的操作.具体架构如下

计算机网络 学习笔记-传输层:TCP协议简介

概述: TCP传输前先要建立连接 TCP在传输层 点对点,一条TCP只能连接两个端点 可靠传输.无差错.不丢失.不重复.按顺序 全双工 字节流 TCP报文段 TCP报文段的报头前20字节是固定的,后面4n字节是根据需要而添加的. 20字节的固定部分: 源端口和目的端口:分别写入源端口号和目的端口号 序号:0-(2^32-1),本报文段数据的第一个字节的序号,用来解决乱序问题 确认序号:期望收到对方下一个报文段的第一个数据字节的序号,用来解决丢包问题 数据偏移:TCP报头长度,包括固定的20字节和

linux tcp server demo

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <sys/types.h>  #include <sys/socket.h>  #include <string.h>  #include <netin

【转载】C# Tutorial - Simple Threaded TCP Server

http://tech.pro/tutorial/704/csharp-tutorial-simple-threaded-tcp-server In this tutorial I'm going to show you how to build a threaded tcp server with C#. If you've ever worked with Window's sockets, you know how difficult this can sometimes be. Howe

TCP server和client的一些测试

一.TCP server和client测试   socket设置 测试项/测试情景 send recv 测             server block           client block                                                                                              

TCP server 为什么一个端口可以建立多个连接?

https://segmentfault.com/q/1010000003101541 如果是tcp client用同一个本地端口去连不同的两个服务器ip,连第二个时就会提示端口已被占用.但服务器的监听端口,可以accept多次,建立多个socket:我的问题是服务器一个端口为什么能建立多个连接而客户端却不行呢? 2015年08月16日提问 评论 邀请回答 编辑 更多 默认排序时间排序 5个回答 答案对人有帮助,有参考价值8答案没帮助,是错误的答案,答非所问 已采纳 TCP server 可以,

W5500EVB TCP Server演示

之前给大家展示了W5500EVB TCP Client是怎样实现的,本文介绍一下W5500EVB TCP Server演示过程. 一.程序编译 使用IAR打开W5500EVB例程中TCP Server的app.eww文件,在main函数中改动ip和gw与PC在同一网段.然后进行Compile.Make,使Debug\Exe目录更新app.bin并记录模组的IP及打开的Port. 二.程序下载 1.打开设备管理器.查询Mini USBport号. 2.打开Flash loader Demonstr