(一)如何实现一个单进程阻塞的网络服务器

图片描述

概述

想要更好的理解,网络编程,写出一个高性能的服务,我们需要花点时间来理解下对于服务器处理客户端的整个流程并且理解一些关键的术语,本来想在本文中补充一些基础理论知识,担心篇幅过长不利于阅读,所以以后补发一些基础知识,接下来进入正题。

理论

主要介绍下实现一个网络服务器的基本步骤,代码会在实践环节复现一次。

clipboard.png

第一步

我们需要创建一个socket,绑定服务器端口(bind),监听端口(listen),在PHP中用stream_socket_server一个函数就能完成上面3个步骤。

第二步

进入while循环,阻塞在accept操作上,等待客户端连接进入。此时程序会进入睡眠状态,直到有新的客户端发起connect到服务器,操作系统会唤醒此进程。accept函数返回客户端连接的socket

第三步

利用fread读取客户端socket当中的数据收到数据后服务器程序进行处理然后使用fwrite向客户端发送响应。长连接的服务会持续与客户端交互,而短连接服务一般收到响应就会close。

实践

在这里我们用代码来实现下基本一个流程,在开始写代码之前介绍介几个php函数,是我们代码中可能会用到的,方便大家理解。

函数

stream_socket_server
stream_socket_accept
call_user_func
is_callable
fread

点击函数了解用法

代码

废话少说直接开撸~

<?php
 class Worker{
     //监听socket
     protected $socket = NULL;
     //连接事件回调
     public $onConnect = NULL;
     //接收消息事件回调
     public $onMessage = NULL;
     public function __construct($socket_address) {

     }

     public function run(){

     }
 }



$worker = new Worker('tcp://0.0.0.0:9810');
//提前注册了一个连接事件回调
$worker->onConnect = function ($data) {
    echo '新的连接来了', $data, PHP_EOL;
};
//提前注册了一个接收消息事件回调
$worker->onMessage = function ($conn, $message) {
};
$worker->run();

按照之前的流程我们需要监听端口+地址

public function __construct($socket_address) {
         //监听地址+端口
         $this->socket=stream_socket_server($socket_address);
     }

下一步就需要阻塞在accept操作,等待客户端连接进入。此时程序会进入睡眠状态,直到有新的客户端发起connect到服务器,操作系统会唤醒此进程

public function run(){
        while (true) { //循环监听
         $client = stream_socket_accept($this->socket);//在服务端阻塞监听
        }
     }

当新的连接进入唤醒进程并且触发连接事件回调

 public function run(){
        while (true) { //循环监听
         $client = stream_socket_accept($this->socket);//在服务端阻塞监听
         if(!empty($client) && is_callable($this->onConnect)){//socket连接成功并且是我们的回调
             //触发事件的连接的回调
             call_user_func($this->onConnect,$client);
         }
        }
     }

这里的连接回调实际上触发的就是之前准备好类库的这里下面这段代码

$worker->onConnect = function ($data) {
    echo '连接事件:', $data, PHP_EOL;
};

当连接成功后利用fread获取到客户端的内容,并触发接收消息事件

     public function run(){
      while (true) { //循环监听
         $client = stream_socket_accept($this->socket);//在服务端阻塞监听
         if(!empty($client) && is_callable($this->onConnect)){//socket连接成功并且是我们的回调
             //触发事件的连接的回调
             call_user_func($this->onConnect,$client);
         }
         //从连接中读取客户端内容
         $buffer=fread($client,65535);//参数2:在缓冲区当中读取的最大字节数
         //正常读取到数据。触发消息接收事件,进行响应
         if(!empty($buffer) && is_callable($this->onMessage)){
             //触发时间的消息接收事件
             call_user_func($this->onMessage,$this,$client,$buffer);//传递到接收消息事件》当前对象、当前连接、接收到的消息
         }
       }
     }

到此处基本的一个网络服务接收基本完成,还需要对请求做出一个响应,以HTTP请求为例,这里封装了一个http响应的方法(http://127.0.0.1:9810)

 class Worker{
    ...
    ...
    ...
     public function  send($conn,$content){
         $http_resonse = "HTTP/1.1 200 OK\r\n";
         $http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
         $http_resonse .= "Connection: keep-alive\r\n";
         $http_resonse .= "Server: php socket server\r\n";
         $http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";
         $http_resonse .= $content;
         fwrite($conn, $http_resonse);
     }
 }

当触发接收消息事件时对http请求做出响应

$worker->onMessage = function ($server,$conn, $message) {
    echo '来自客户端消息:',$message,PHP_EOL;
    $server->send($conn,'来自服务端消息');
};

到这就结束了~,完整代码直通车

缺点

一次只能处理一个连接,不支持多个连接同时处理

阅读 1.5k

推荐阅读
Grace development
用户专栏

记录分享开发、学习中的点点滴滴

4654 人关注
90 篇文章
专栏主页