Socket学习系列之使用php socket扩展实现阻塞TCP Server/Client

[TOC]

Socket简介

Socket: 网上套接字(英语:Network socket),又译网络套接字、网络接口、网上插槽,是计算机网上中进程间数据流的端点。

Socket是一种操作系统提供的进程间通信机制

TCP Server/Client交互流程

server/client流程划分

  • server
    1. 创建socket
    2. 给socket绑定IP:端口
    3. 监听socket
    4. 接收处理请求【这一步是监听和处理请求核心,一般服务端会死循环执行这一步骤,直到退出才会执行5】
      4.1 accept请求
      4.2 read数据 & write数据
      4.3 关闭accept的链接
    5. 关闭socket(关闭服务)
  • client
    1. 创建socket
    2. 使用socket连接指定的server IP:端口【可省略】
    3. connect服务端
    4. 发送请求,接收响应
    5. 关闭socket 连接

代码实现

本篇文章仅仅是为了学习,实现的是一个基础的TCP服务器,如下实现方式只能处理单链接,同步阻塞模式

Server
<?php

// tcp socket服务端分需要实现以下几步
// 1. 创建socket
// 2. 给socket绑定IP:端口
// 3. 监听socket
// 4. 接收处理请求
//      4.1 accept请求
//      4.2 read数据 & write数据
//      4.3 关闭accept的链接
// 5.关闭socket(关闭服务)

// 一、创建socket
// 参数domain: AF_INET => IPv4 网络协议。TCP 和 UDP 都可使用此协议。
// 参数type: SOCK_STREAM => 提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
// 参数protocol: SOL_TCP => tcp协议
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
    die('create server fail');
}

// 二、给套接字绑定ip+port
$ret = socket_bind($socket, '0.0.0.0', 8090);
if (!$ret) {
    die('bind server fail');
}

// 三、监听socket上的连接
$ret = socket_listen($socket, 2);
if (!$ret) {
    die('listen server fail');
}
echo "waiting client...\n";

// 如下死循环实现的accept客户端的模式是同步阻塞的,
// 也就是说这种模式只能处理一个连接,当一个连接未关闭时
// 当前server完全不能处理其他请求
while (true) {
    // 四、阻塞等待accept客户端连接
    $conn = socket_accept($socket);
    if (!$conn) {
        echo 'accept server fail';
        continue;
    }

    echo "client connect succ.\n";
    // 四、接收请求、返回响应、关闭连接
    onRecv($conn);
}

echo 'bye';


// 关闭socket
socket_close($socket);

/**
 * 解析客户端消息
 * 协议:换行符(\n)
 *
 * @param $conn
 */
function onRecv($conn)
{
    //实际接收到的消息
    $recv = '';

    //循环读取消息
    while (true) {
        $buffer = socket_read($conn, 100); //每次读取100byte
        if ($buffer === false || $buffer === '') {
            echo "client closed\n";
            //关闭本次连接
            socket_close($conn);
            break;
        }

        //解析单次消息,协议:换行符
        $pos = strpos($buffer, "\n");

        //消息未读取完毕,继续读取
        if ($pos === false) {
            $recv .= $buffer;
        }

        //消息读取完毕
        else {
            //去除换行符及空格
            $recv .= trim(substr($buffer, 0, $pos + 1));

            //客户端主动端口连接,关闭本次连接
            if ($recv === 'quit') {
                exit('server closed');
                break;
            }

            // 响应数据
            echo "recv: $recv \n";
            socket_write($conn, "hello this is server, you send data:$recv \n");

            //清空消息,准备下一次read(连接不是单次处理完就关闭)
            $recv = '';
        }
    }
}

Client
<?php

// tcp socket客户端分需要实现以下几步
// 1. 创建socket
// 2. 使用socket连接指定的server IP:端口【可省略】
// 3. connect服务端
// 4. 发送请求,接收响应
// 5. 关闭socket 连接

// 创建连接
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
    die('create server fail:');
}

// 绑定socket ip和端口
$ret = socket_bind($socket, '0.0.0.0', 80901);
if (!$ret) {
    die('bind server fail');
}

// 连接server
$response = socket_connect($socket, '127.0.0.1', 8090);
if (!$response) {
    die('connect fail');
}

while (true) {
    // 发送消息
    echo '请输入您要发送的内容' . PHP_EOL;
    $str = trim(fgets(STDIN));
    socket_write($socket, $str . "\n");

    // 读取消息
    $recv = '';
    while (true) {
        $buffer = socket_read($socket, 1024);
        if ($buffer === false) {
            socket_close($socket);
            exit('read socket error' . "\n");
        }

        //解析单次消息,协议:换行符
        $pos = strpos($buffer, "\n");

        // 消息未读取完毕,继续读取
        if ($pos !== false) {
            break;
        }

        $recv .= $buffer;
    }
    echo "server response: {$buffer} \n";

    if (strpos($buffer, 'bye')) {
        break;
    }
}

// 关闭连接
socket_close($socket);

测试

本文源码

https://github.com/SuperHappysir/php-socket-demo

推荐阅读

发表评论

电子邮件地址不会被公开。 必填项已用*标注