Maison  >  Article  >  développement back-end  >  Explication détaillée de la communication socket en php

Explication détaillée de la communication socket en php

小云云
小云云original
2018-03-16 11:34:011809parcourir

Parfois, notre programme PHP doit communiquer avec d'autres systèmes. Par exemple, le site Web officiel d'une entreprise fournit des informations sur la traçabilité des produits. Le site Web backend doit communiquer avec le système de traçabilité ou le système ERP de l'entreprise. . Pour la programmation, PHP fournit une extension sockets. L'adresse officielle du site Web est :

http://nl3.php.net/manual/zh/intro.sockets.php
.

Cette extension nous donne la possibilité de manipuler directement les sockets via php, afin que nous puissions communiquer avec d'autres systèmes. Nous utilisons des sockets pour travailler directement au-dessus de la couche de transport du modèle de réseau OSI. en utilisant TCP, le service fourni par UDP, vous pouvez donc l'utiliser comme client pour d'autres protocoles de couche d'application, tels que la simulation de clients HTTP (navigateurs), smtp, pop et ftp courants peuvent être simulés avec. que vous pouvez l'utiliser et permettre l'interaction du serveur TELNET, ces protocoles sont tous des protocoles de couche application, ils peuvent donc tous interagir. Une communication personnalisée entre les systèmes nécessite des protocoles personnalisés.

Voici un exemple de développement d'un serveur de réception de fichiers simple utilisant PHP. Un autre programme client PHP montre comment communiquer avec ce programme serveur. Le code est le suivant.

Programme serveur :

<?php
/**
 * 本程序演示php网络编程:socket通讯 需要php开启php_sockets扩展
 * 这是一个简易的服务器程序,接收客户端发送的文件,保存后关闭
 * 通讯协议约定为:文件大小::文件扩展名::有效数据
 * 通讯结束标识符为:“-end-”
 * 在命令行或浏览器中执行均可,推荐命令行
 * 作者:云客【云游天下,作客四方】
 */

/****配置****/
//要绑定监听的本机ip地址和端口
$address = &#39;127.0.0.1&#39;;
$port = 81;

error_reporting(E_ALL);

//防止超时
set_time_limit(0);

//开启绝对刷送,禁止缓冲内容
ob_implicit_flush();

//创建套接字资源
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}
//绑定套接字到端口
if (socket_bind($sock, $address, $port) === false) {
    echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}
//开始监听端口,参数5表示可以让5个连接请求在缓冲中排队
//排队的链接请求会在前一个连接断掉后才开始执行,该处缓冲排队数满五个后,后面的链接请求将直接忽略,客户端显示无法链接
//注意这个5并不是指可以并发进行5个链接,而是允许让5个后续链接进入排队
if (socket_listen($sock, 5) === false) {
    echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}

do {
    //程序运行到此处进行阻塞,就像暂停执行一样,一旦有请求进入,该函数停止阻塞,返回链接资源
    if (($client_sock = socket_accept($sock)) === false) {
        echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
        break;
    }
    echo "[client connect start]\n";
    $data = ""; //接收的数据
    $data_size = -1; //接收的数据大小
    $received_size = 0; //实际接收的数据
    $ext_name = "txt"; //默认文件扩展名

    //开始与客户端交互
    do {
        //运行到该处产生阻塞,一旦有内容则停止阻塞状态,返回内容
        if (false === ($buf = socket_read($client_sock, 2048))) {
            echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($client_sock)) . "\n";
            break 2; //读取不了就返回失败,此处的2会让客户端断掉链接后停止本模拟服务器
        }
        if ($data_size === -1) {//第一次接受数据,解析通讯协议
            $arr = explode("::", $buf, 3);
            $data_size = (int)$arr[0];
            $ext_name = $arr[1];
            $data .= $arr[2];
            $received_size += strlen($data);
            continue;
        }
        $data .= $buf;
        $received_size += strlen($buf);
        if ($data_size <= $received_size) {
            if ($data_size < $received_size) {
                $data = substr($data, 0, $data_size);
            }

            echo "received:" . $received_size . "/" . "total:" . $data_size;
            $talkback = "-end-"; //发送通讯结束标志
            socket_write($client_sock, $talkback, strlen($talkback));
            file_put_contents("servertest." . $ext_name, $data);
            break 2; //关闭服务器
        }

        echo $talkback = "received:" . $received_size . "\n";
        socket_write($client_sock, $talkback, strlen($talkback));


    } while (true);
    socket_close($client_sock);

} while (true);

socket_close($sock);
?>

Programme client :

<?php
/**
 * 本程序演示php网络编程:socket通讯 需要php开启php_sockets扩展
 * 这是一个简易的客户端程序,向服务器发送文件
 * 通讯协议约定为:文件大小::文件扩展名::有效数据
 * 通讯结束标识符为:“-end-”
 * 在命令行或浏览器中执行均可,推荐命令行,需先开启服务器端
 * 作者:云客【云游天下,作客四方】
 */

/****配置****/
$file = "yunke.jpg"; //待发送的文件
//服务器ip和端口号
$address = &#39;127.0.0.1&#39;;
$port = 81;


error_reporting(E_ALL);

// 防止超时 
set_time_limit(0);

// 开启绝对刷送,不要缓冲输出
ob_implicit_flush(true);

//创建套接字资源
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
    exit();
}
//打开到远程主机的链接
if (socket_connect($sock, $address, $port) === false) {
    echo "socket_connect() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
    exit();
}

//构造一个很大的待发送数据 大约10MB
/*
$data = "";
for ($i = 1; $i <= 10000000; $i++) {
    $data .= "data:" . $i;
}
$data = $data . $data . $data;
*/

$data = file_get_contents($file);
$data_size = strlen($data);
$arr_temp = explode(&#39;.&#39;, $file);
$ext_name = end($arr_temp);
$sock_data = $data_size . "::" . $ext_name . "::" . $data;

//发送数据给远程主机
while (true) {
    $sock_data_size = strlen($sock_data);
    $send_size = socket_write($sock, $sock_data, $sock_data_size);
    if ($send_size === false) {
        echo "send false:" . socket_strerror(socket_last_error($sock)) . "\n";
        socket_close($sock);
        echo "[client shutdown]\n";
        exit();
    }
    if ($send_size == $sock_data_size) {
        break;
    }
    $sock_data = substr($sock_data, $send_size);
}

//读返回数据
while (true) {
    if (false === ($out = socket_read($sock, 2048))) {
        echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
        break 1;
    }

    echo "server: " . $out . "\n";
    if (substr($out, -5) == "-end-") { //约定结束标志
        break;
    }
}
//关闭套接字资源
socket_close($sock);
echo "[client shutdown]\nsend data:" . format($data_size);

function format($byte = 0)
{
    if ($byte > 1024 * 1024) {
        return ceil($byte / (1024 * 1024)) . "MB";
    } elseif ($byte > 1024) {
        return ceil($byte / 1024) . "KB";
    } else {
        return $byte . " Byte";
    }
}


Cet exemple montre la transmission d'un fichier au serveur via un lien court TCP Dans les projets réels, le module de communication ne sera pas si simple et doit être pris en compte davantage. Questions

Par exemple :

Si une méthode de connexion longue est nécessaire (les données sont transmises plusieurs fois dans un lien TCP), vérification des données à éviter les dommages, les différents problèmes d'endianité entre les systèmes, les protocoles de communication personnalisés, les problèmes de rétention de paquets, le traitement des délais d'attente, l'accès simultané, le contrôle de flux, le déballage des paquets TCP, etc.

Si vous souhaitez en savoir plus La programmation réseau PHP mentionnée ci-dessus nécessite un apprentissage systématique. Il est recommandé de jeter un œil à l'implémentation de Workerman

Il s'agit d'un framework de serveur socket écrit en PHP pour aider à résoudre les problèmes de communication des sockets. it Vous pouvez créer un serveur personnalisé, etc.
workermanL'adresse officielle du site Web est : http://www.workerman.net/

Les éléments suivants seront fournis Deux exemples de programmes pour afficher les en-têtes HTTP du serveur et les en-têtes du navigateur :

L'exemple suivant affiche les informations d'en-tête renvoyées par le serveur, modifie l'adresse du serveur qui doit être visualisé, puis dans le navigateur Accédez simplement au script :

<?php
error_reporting(E_ALL);

// 防止超时 
set_time_limit(0);

// 开启绝对刷送,不要缓冲输出
ob_implicit_flush(true);

$address = &#39;www.qq.com&#39;; //要查看的服务器
$port=80;

//创建套接字资源
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
    exit();
}
//打开到远程主机的链接
if (socket_connect($sock, gethostbyname($address), $port) === false) {
    echo "socket_connect() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
    exit();
}

//构造http头
$msg = "GET / HTTP/1.1 \r\n";
$msg .= "Host: {$address}\r\n";
$msg .= "Connection: Close\r\n\r\n";  
//指示服务器发回完毕就断开,不必等等其他资源的链接
//如果没有该行会,服务器会一直等待我方继续发送数据,直到超时关闭
//而我方不会再发送,这个会让下面的socket_read函数互为等待,等很久

//发送给远程主机
socket_write($sock, $msg, strlen($msg));

$str="";
while ($out = @socket_read($sock, 2048*4)) {
    $str.=$out;
}
$str=explode("\r\n\r\n", $str);
$str=$str[0];
if($str)
{
    echo "<pre class="brush:php;toolbar:false">\r\n".$str."\r\n
"; }else{ echo "nothing"; } //关闭套接字资源 socket_close($sock);

Ce qui suit est un exemple de visualisation du en-tête du navigateur. Vous pouvez facilement visualiser ce que le navigateur envoie. Informations de session
Assurez-vous d'abord que le port 80 est fermé, puis modifiez le fichier hôte de la machine, dirigez l'URL à laquelle vous souhaitez accéder vers l'adresse 127.0.0.1 de la machine. machine, utilisez le mode ligne de commande php pour démarrer ce script, puis utilisez le navigateur pour accéder aux paramètres. Cliquez simplement sur l'URL dirigée. Le programme sera toujours ouvert. Si vous devez le fermer, veuillez utiliser la combinaison de touches ctrl+c. sur la console Le programme est le suivant :

<?php
error_reporting(E_ALL);

//防止超时
set_time_limit(0);

//开启绝对刷送,禁止缓冲内容
ob_implicit_flush();

$address = &#39;127.0.0.1&#39;;
$port = 80;

//创建套接字资源
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}
//绑定套接字到端口
if (socket_bind($sock, $address, $port) === false) {
    echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}
//开始监听端口,参数5表示可以让5个连接请求在缓冲中排队
//排队的链接请求会在前一个连接断掉后才开始执行,该处缓冲排队数满五个后,后面的链接请求将显示无法链接
//注意这个5并不是指可以并发进行5个链接,而是允许让5个后续链接进入排队
if (socket_listen($sock, 5) === false) {
    echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}

do {
    //程序运行到此处进行阻塞,就像暂停执行一样,一旦有请求进入,该函数停止阻塞,返回链接资源
    //可以使用socket_set_nonblock函数设置非阻塞模式
    if (($msgsock = socket_accept($sock)) === false) {
        echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
        break;
    }
    
    //链接成功后在控制台显示提示
    echo "[client start ".date("Y-m-d H:i:s")."]\n\n";
    
    do {
        //运行到该处产生阻塞,一旦有内容则停止阻塞状态,返回内容
        if (false === ($buf = socket_read($msgsock, 2048*6))) {
            echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($msgsock)) . "\n";
            break 2; //读取不了就返回失败,此处的2会让客户端断掉链接后停止本模拟服务器
        }
        $talkback ="HTTP/1.1 200 OK\r\n\r\n";
        $talkback .= date("Y-m-d H:i:s")."\nBrowser HTTP Headers:\n\n".$buf."\n";
        //向客户端显示该内容
        socket_write($msgsock, $talkback, strlen($talkback));
        //向服务器控制台显示该内容
        echo "$buf\n";
        break ; //中断本次与浏览器的链接
    } while (true);
    socket_close($msgsock);
} while (true);

socket_close($sock);
?>

Recommandations associées :

Comment pour obtenir le numéro de port source local pour la communication socket sous Linux

Construction de serveur PHP Socket et partage d'exemples de test

Méthode simple d'utilisation de Socket dans PHP

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn