原创

php实现socket网络编程

温馨提示:
本文最后更新于 2018年12月07日,已超过 1,928 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

之前本人其实写过一个tcp多进程服务器了http://www.php20.cn/article/139,本文将总结以及完善php实现网络服务器相关代码

php实现tcp服务器

tcp服务器的实现,其实和c语言实现的步骤差不多,大概为:

创建一个socket

绑定socket为tcp,到网卡中

监听socket(将socket改为可接受其他进程的请求)

阻塞/非阻塞,循环获取连接事件

<?php
$listen_host = '0.0.0.0';
$port = '8080';
$tcp_socket = socket_create(AF_INET/\*ipv4\*/,SOCK_STREAM/\*tcp类型\*/,SOL_TCP/\*tcp\*/);
if ($tcp_socket===false){
    die("socket_create() fail:" . socket_strerror(socket_last_error()) . "/n");
}
socket_bind($tcp_socket,$listen_host ,$port)||die('bind error');//绑定
socket_listen($tcp_socket)||die('listen error');//监听
socket_set_nonblock($tcp_socket);//设置非阻塞
socket_set_option($tcp_socket,SOL_SOCKET,SO_REUSEADDR,1);//SO_REUSEADDR 可避免程序意外退出,导致端口绑定失败
$client_arr = [];
while(1){
    $connection = socket_accept($tcp_socket);
    if($connection){//如果有新链接,则处理新链接
        echo $connection."连接成功\n";
        $client_arr[$connection] = $connection;
        socket_set_nonblock($connection);//设置客户端非阻塞
    }

    //处理客户端
    foreach ($client_arr as $client){
        $data='';
        $result = @socket_recv($client,$data, 1024,MSG_DONTWAIT);
        if($result===false){//出错
            $error_id = socket_last_error();
            if($error_id!==11){
                socket_clear_error();
                throw new Exception(socket_strerror($error_id));
            }
        }elseif ($result===0){//客户端关闭
            echo $client."客户端关闭\n";
            unset($client_arr[$client]);
        }else {
            echo $client."客户端发送消息:".$data."\n";
            socket_write($client,$data,strlen($data));
        }
    }
    usleep(1);//避免while true死循环卡死
}

除了使用socket函数可以创建一个tcp服务器外,我们还可以通过steam流函数创建:

<?php
$listen_host = '0.0.0.0';
$port = '8080';
$tcp_socket = stream_socket_server("tcp://{$listen_host }:$port", $errno, $errstr);
//直接创建一个监听tcp的套接字
$tcp_socket || die("$errstr ($errno)\n");
stream_set_blocking($tcp_socket, 0);//设置非阻塞
$client_arr = [];
while (1) {
    set_error_handler(function(){});//socket错误拦截
    $connection = stream_socket_accept($tcp_socket,0,$remote_address);
    restore_error_handler();
    if ($connection) {//如果有新链接,则处理新链接
        echo $connection . "连接成功\n";
        $client_arr[$connection] = $connection;
        stream_set_blocking($connection, 0);//设置客户端非阻塞
    }
    //处理客户端
    foreach ($client_arr as $client){
        $data = fread($client, 65535);
        if ($data === '' || $data === false) {
            if ( (feof($client)  || $buffer === false)) {//客户端关闭
                echo $client."客户端关闭\n";
                unset($client_arr[$client]);
            }
        }
        if(!empty($data)){
           fwrite($client,$data);//发送给客户端 
           echo $client."客户端发送消息:".$data."\n";
        }else{
            continue;
        }
    }
    usleep(1);//避免while true死循环卡死
}

php实现tcp客户端

tcp客户端的实现步骤是:

先创建一个tcpsocket

通过socket_connect连接

接收/发送消息

<?php
$host='192.168.159.1';
$port='8080';
$client_socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);//创建一个tcp的socket
$connection = socket_connect($client_socket,$host,$port);//连接
socket_write($client_socket, "hello socket") or die("Write failed\n"); // 数据传送 向服务器发送消息
while(1){
    $buffer = socket_read($client_socket, 1024, PHP_BINARY_READ);//默认阻塞类型,没有消息会一直阻塞
    if (empty($buffer)){
        die("已断开");
    }

    echo "服务端发送:".$buffer.PHP_EOL;
}

同样,我们可以通过流函数进行创建一个tcp客户端:

<?php
$host='192.168.159.1';
//$host = '127.0.0.1';
$port = '8080';
$client_socket = stream_socket_client("tcp://$host:$port", $errno, $errstr, 30);
fwrite($client_socket, "hello socket");
while (!feof($client_socket)) {
    $buffer = fread($client_socket,255);//默认阻塞类型,没有消息会一直阻塞
    if(empty($buffer)){
        echo "客户端关闭\n";
        fclose($client_socket);
        break;
    }
    echo "服务端发送:" . $buffer . PHP_EOL;
    sleep(1);
}

php实现udp服务端

udp是无连接的协议,我们不需要去额外的创建客户端的socket进行一对一的传输,直接可通过创建udp服务端的socket,接收/发送所有的客户端:

<?php
$listen_host = '0.0.0.0';
$port = '8080';
if (($udp_socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) == FALSE) {
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);
    die("创建udp socekt失败: [$errorcode] $errormsg");
}
socket_bind($udp_socket,$listen_host,$port)||die('bind error');

while(1){
    $data = socket_recvfrom($udp_socket, $buf, 512, 0, $remote_ip, $remote_port);
    var_dump(base64_decode($buf));//udp直接发送中文将会出现乱码问题,可通过传输pack二进制包或者base64等方法解决
    $msg="客户端发送的是:".$buf;
    $msg.="客户端ip:".$remote_ip;
    $msg.="客户端端口:".$remote_port;
    //Send back
    socket_sendto($udp_socket, $msg, strlen($msg), 0, $remote_ip, $remote_port);
}

同样,我们可以通过流函数创建udp服务端:

<?php
$listen_host = '0.0.0.0';
$port = '8080';
$socket = stream_socket_server("udp://{$listen_host }:{$port}", $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
    die("$errstr ($errno)");
}
do {
    //接收客户端发来的信息
    $inMsg = stream_socket_recvfrom($socket, 1024, 0, $peer);//udp无需握手,不需要获取客户端一对一获取数据,直接接收
    //服务端打印出相关信息
    echo "Client : $peer\n";
    echo "Receive : {$inMsg}\n";
    //给客户端发送信息
    $outMsg = "$inMsg";
    stream_socket_sendto($socket, $outMsg, 0, $peer);
} while ($inMsg !== false)

在上面的实现过程中,recvfrom都是阻塞的,这种情况会造成我们无法主动给客户端发送消息,我们可以参考tcp服务器的非阻塞实现,进行修改代码

php实现udp客户端

通过udp实现服务端的代码,我们可以发现:

只要有服务端的ip和端口(废话,没端口怎么通信),我们就可以用udp服务端的代码当成udp客户端使用(需要注意recvfrom阻塞问题).

<?php
$listen_host = '0.0.0.0';
$port = '8080';
if (($udp_socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) == FALSE) {
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);
    die("创建udp socekt失败: [$errorcode] $errormsg");
}
socket_bind($udp_socket,$listen_host,$port)||die('bind error');

socket_set_nonblock($udp_socket);//设置非阻塞
while(1){
    $remote_ip='192.168.159.1';
    $remote_port="8080";
    $msg="I'm client";
    //Send back
    socket_sendto($udp_socket, $msg, strlen($msg), 0, $remote_ip, $remote_port);//每隔一秒给192.168.159.1发送数据
    sleep(1);
}

除了使用服务端代码可以实现,我们也可以用客户端代码实现:

<?php
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$msg = 'hello';
$len = strlen($msg);
socket_sendto($socket, $msg, $len, 0, '192.168.159.1', 8080);
while(1){
    $data = socket_recvfrom($socket, $buf, 512, 0, $remote_ip, $remote_port);
    var_dump(base64_decode($buf));//udp直接发送中文将会出现乱码问题,可通过传输pack二进制包或者base64等方法解决
    $msg="客户端发送的是:".$buf;
    $msg.="客户端ip:".$remote_ip;
    $msg.="客户端端口:".$remote_port;
    //Send back
    socket_sendto($socket, $msg, strlen($msg), 0, $remote_ip, $remote_port);
    sleep(1);
}

流函数创建:

<?php
$host='192.168.159.1';
$port = '8080';
$client_socket = stream_socket_client("udp://$host:$port", $errno, $errstr, 30);
fwrite($client_socket, "hello socket");
while (!feof($client_socket)) {
    $buffer = fread($client_socket,255);//默认阻塞类型,没有消息会一直阻塞
    echo "服务端发送:" . $buffer . PHP_EOL;
    sleep(1);
}
正文到此结束
本文目录