原创

php实现socket网络编程

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

之前本人其实写过一个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);
}
正文到此结束
本文目录