workerman学习笔记

分享于:2020-09-26 16:06:30

  从事PHP开发已经有三年的时间了,大部分的时间都是在写业务逻辑。从最初的web端开发,到后期的为app提供接口,绝大部分时间都有接触到php底层,虽然在开发中能切身感受到php作为弱类型脚本语言在性能上的缺陷,但是往往的优化方向还是停留在nginx和mysql上。

  从2017年下半年开始,因为需要开发一个小型的即时聊天系统,我萌发了想要自己从php底层出发,学习更深层次的技术原理的想法,通过workerman扩展的学习,向架构方面努力。

首先,workerman是什么,Workerman是一款纯PHP开发的开源高性能的PHP socket 服务框架。它不是一个MVC框架,而是一个更底层更通用的socket服务框架,你可以用它开发tcp代理、梯子代理、做游戏服务器、邮件服务器、ftp服务器。

  实际上Workerman类似一个PHP版本的nginx,核心也是多进程+Epoll+非阻塞IO。Workerman每个进程能维持上万并发连接。由于本身常住内存,不依赖Apache、nginx、php-fpm这些容器,拥有超高的性能。同时支持TCP、UDP、UNIXSOCKET,支持长连接,支持Websocket、HTTP、WSS、HTTPS等通讯协以及各种自定义协议。拥有定时器、异步socket客户端、异步Mysql、异步Redis、异步Http、异步消息队列等众多高性能组件。

  首先需要了解一下几个核心概念,1.多进程  2.非阻塞IO 3.Epoll

1 多进程:
首先什么是进程呢,一个进程包括了代码、数据和分配给进程的资源(内存),在计算机系统里直观地说一个进程就是一个PID。操作系统保护进程空间不受外部进程干扰,即一个进程不能访问到另一个进程的内存。有时候进程间需要进行通信,这时可以使用操作系统提供进程间通信机制。通常情况下,执行一个可执行文件操作系统会为其创建一个进程以供它运行。但如果该执行文件是基于多进程设计的话,操作系统会在最初的进程上创建出多个进程出来,这些进程间执行的代码是一样,但执行结果可能是一样的,也可能是不一样的。
为什么需要多进程?最直观的想法是,如果操作系统支持多核的话,那么一个执行文件可以在不同的核心上跑;即使是非多核的,在一个进程在等待I/O操作时另一个进程也可以在CPU上跑,提高CPU利用率、程序的效率。
廖雪峰的博客中有讲到:在Linux系统上可以通过fork()来在父进程中创建出子进程。一个进程调用fork()后,系统会先给新进程分配资源,例如存储数据和代码空间。然后把原来进程的所有值、状态都复制到新的进程里,只有少数的值与原来的进程不同,以区分不同的进程。fork()函数会返回两次,一次给父进程(返回子进程的pid或者fork失败信息),一次给子进程(返回0)。至此,两个进程分道扬镳,各自运行在系统里。

2 非阻塞IO:

首先什么是IO,即input与output的操作。网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

所以说,当一个read操作发生时,它会经历两个阶段:

 

第一阶段(等待数据):等待数据准备 (Waiting for the data to be ready)。
第二阶段(拷贝数据):将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

对于socket流(即IO)而言,

第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
第二步:把数据从内核缓冲区复制到应用进程缓冲区。

网络IO的模型大致有如下几种:

同步模型(synchronous IO)


阻塞IO(bloking IO)资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)。在linux中,默认情况下所有的socket都是blocking,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。


非阻塞IO(non-blocking IO)资源不可用时,IO请求离开返回,返回数据标识资源不可用。在linux中,如果数据还没有准备好,那么它并不会block用户进程,内核马上返回给进程,说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK)。因此非阻塞就是使用轮询的(polling)方式来实现。


多路复用IO(multiplexing IO) IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。所以IO多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。


信号驱动式IO(signal-driven IO)


异步IO(asynchronous IO)用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。


 3 Epoll : epoll现在就很好理解了,epoll就是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。


  Worker是WorkerMan中最基本容器,Worker可以开启多个进程监听端口并使用特定协议通讯,类似nginx监听某个端口。每个Worker进程独立运作,采用Epoll(需要装event扩展)+非阻塞IO,每个Worker进程都能上万的客户端连接,并处理这些连接上发来的数据。主进程为了保持稳定性,只负责监控子进程,不负责接收数据也不做任何业务逻辑。

请看下图:


2020-07-16_134238.png


  从此图中我们我可以清晰的看到,不同的worker进程都负责着一定数量的客户端连接,彼此之间互不干扰。每个worker进程的运行情况都受到master进程(主进程)的监控。


2020-07-16_134238.png



  从这张图中我们我可以看到,图1中的所有worker进程都被放在worker进程池中统一管理,而主进程的工作就是监视这片worker进程池。

  PS.下面介绍一下手册上关于开发必须知道的几个问题:

  1.workerman是通过命令行启动的:一般网页空间无法使用workerman。

  2.客户端和服务端协议一定要对应才能通讯:以websocket为例,客户端和服务端必须同时遵守websocket协议才能完成通讯。另外需要注意:不要尝试在浏览器地址栏访问websocket协议端口,不要尝试用webscoket协议访问裸tcp协议端口,协议一定要对应。因此可以考虑通过html页面的js来测试websocket协议端口,下面贴上一段我自己的测试代码: 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<script>
function connect(){
  // 假设服务端ip为127.0.0.1,端口为2121
  var ws = new WebSocket("ws://127.0.0.1:2121");
  ws.onopen = function() {
    alert("连接成功");
    ws.send('tom');
    };
  ws.onmessage = function(e) {
    alert("收到服务端的消息:" + e.data);
    };
   }
</script>
<body>
  <button id="test" onclick="connect()">请单击</button>
</body>
</html>


  3.长连接必须加心跳:先来区分一下长连接和短连接,长连接也叫持久连接,在TCP层握手成功后,不立即断开连接,并在此连接的基础上进行多次消息(包括心跳)交互,直至连接的任意一方(客户端OR服务端)主动断开连接,此过程称为一次完整的长连接。短连接,顾名思义,与长连接的区别就是,客户端收到服务端的响应后,立刻发送FIN消息,主动释放连接。也有服务端主动断连的情况,凡是在一次消息交互(发请求-收响应)之后立刻断开连接的情况都称为短连接。因为长连接的保持会消耗系统资源,所以长时间未通讯的长连接会被某些节点的防火墙关闭而强行断开。手册上关于心跳的介绍如下:  

心跳作用主要有两个:

1、客户端定时给服务端发送点数据,防止连接由于长时间没有通讯而被某些节点的防火墙关闭导致连接断开的情况。

2、服务端可以通过心跳来判断客户端是否在线,如果客户端在规定时间内没有发来任何数据,就认为客户端下线。这样可以检测到客户端由于极端情况(断电、断网等)下线的事件。

建议值:

建议心跳间隔小于60秒

  4.不要使用exit die语句,否则进程会退出。当然,进程退出了会立刻重启一个新的进程继续服务。如果需要返回,可以调用return。


来源:https://www.cnblogs.com/lfchoo/p/8484658.html