PHP之 Socket实践

news2025/4/18 9:33:39

一  Socket简介

1.1 Socket(套接宇),用来描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发送请求或者应答网络请求。

1.2 Socket是支持TCP/IP协议的网络通信的基本操作单元,是对网络通信过程中端点的抽象表示,包含了进行网络通信所必须的五种信息

  • 连接所使用的协议
  • 本地主机的IP地址
  • 本地远程的协议端口
  • 远地主机的IP地址以
  • 远地进程的协议端口

二 PHPSocket服务器端示例

2.1 首先要在PHP安装目录,开启php.ini配置文件里面的socket扩展,extension=sockets;

2.2 本地调试,ip为127.0.0.1或者本机P地址,端口找个未被使用的就可以比如1000

2.3 服务器端源码:server.php

<?php
$host = '127.0.0.1';
$port = '1000';
$null = NULL;

//创建tcp socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socket, 0, $port);

//监听端口
socket_listen($socket);

//连接的client socket 列表
$clients = array($socket);

//设置一个死循环,用来监听连接 ,状态
while (true) {

    $changed = $clients;
    socket_select($changed, $null, $null, 0, 10);

    //如果有新的连接
    if (in_array($socket, $changed)) {
        //接受并加入新的socket连接
        $socket_new = socket_accept($socket);
        $clients[] = $socket_new;

        //通过socket获取数据执行handshake
        $header = socket_read($socket_new, 1024);
        perform_handshaking($header, $socket_new, $host, $port);

        //获取client ip 编码json数据,并发送通知
        socket_getpeername($socket_new, $ip);
        $response = mask(json_encode(array('type' => 'system', 'message' => $ip . ' connected')));
        send_message($response);
        $found_socket = array_search($socket, $changed);
        unset($changed[$found_socket]);
    }

    //轮询 每个client socket 连接
    foreach ($changed as $changed_socket) {

        //如果有client数据发送过来
        while (@socket_recv($changed_socket, $buf, 1024, 0) >= 1) {
            //解码发送过来的数据
            $received_text = unmask($buf);
            $tst_msg = json_decode($received_text);
			if($tst_msg!=null){
				$user_name = $tst_msg->id;
				$user_message = $tst_msg->title;
				//把消息发送回所有连接的 client 上去
				$response_text = mask(json_encode(array('type' => 'usermsg', 'name' => $user_name, 'message' => $user_message)));
				send_message($response_text);
				echo $response_text.PHP_EOL;
			}
        
            break 2;
        }

        //检查offline的client
        $buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ);
        if ($buf === false) {
            $found_socket = array_search($changed_socket, $clients);
            socket_getpeername($changed_socket, $ip);
            unset($clients[$found_socket]);
            $response = mask(json_encode(array('type' => 'system', 'message' => $ip . ' disconnected')));
            send_message($response);
        }
    }
}
// 关闭监听的socket
socket_close($sock);

//发送消息的方法
function send_message($msg)
{
    global $clients;
    foreach ($clients as $changed_socket) {
        @socket_write($changed_socket, $msg, strlen($msg));
    }
    return true;
}


//解码数据
function unmask($text)
{
    $length = ord($text[1]) & 127;
    if ($length == 126) {
        $masks = substr($text, 4, 4);
        $data = substr($text, 8);
    } elseif ($length == 127) {
        $masks = substr($text, 10, 4);
        $data = substr($text, 14);
    } else {
        $masks = substr($text, 2, 4);
        $data = substr($text, 6);
    }
    $text = "";
    for ($i = 0; $i < strlen($data); ++$i) {
        $text .= $data[$i] ^ $masks[$i % 4];
    }
    return $text;
}

//编码数据
function mask($text)
{
    $b1 = 0x80 | (0x1 & 0x0f);
    $length = strlen($text);

    if ($length <= 125)
        $header = pack('CC', $b1, $length);
    elseif ($length > 125 && $length < 65536)
        $header = pack('CCn', $b1, 126, $length);
    elseif ($length >= 65536)
        $header = pack('CCNN', $b1, 127, $length);
    return $header . $text;
}

//握手的逻辑
function perform_handshaking($receved_header, $client_conn, $host, $port)
{
    $headers = array();
    $lines = preg_split("/\r\n/", $receved_header);
    foreach ($lines as $line) {
        $line = chop($line);
        if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {
            $headers[$matches[1]] = $matches[2];
        }
    }

    $secKey = $headers['Sec-WebSocket-Key'];
    $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
    $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
        "Upgrade: websocket\r\n" .
        "Connection: Upgrade\r\n" .
        "WebSocket-Origin: $host\r\n" .
        "WebSocket-Location: ws://$host:$port/demo/shout.php\r\n" .
        "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
    socket_write($client_conn, $upgrade, strlen($upgrade));
}

2.4 运行php指令开启该socket服务,光标闪烁说明服务开启成功

三 HTML+JS客户端示例

3.1 新建html脚本文件client.html,填写服务器的ip和端口,连接服务器socket

<!DOCTYPE html>
<html>
<head>
<title>Test - PHP Push WebSocket</title>
<meta charset="utf-8" />

<script type="text/javascript">
var ws, url = 'ws://127.0.0.1:1000';

window.onbeforeunload = function() {
	ws.send('quit');
};
window.onload = function() {
	try {
		ws = new WebSocket(url);
		write('Connecting... (readyState '+ws.readyState+')');
		ws.onopen = function(msg) {
			write('Connection successfully opened (readyState ' + this.readyState+')');
		
		   var message = {
	           id: 1,
	           title: 'hahahahahahah'
           }
           ws.send(JSON.stringify(message)); 
		
		};
		ws.onmessage = function(msg) {
			write('Server says: '+msg.data);
		};
		ws.onclose = function(msg) {
			if(this.readyState == 2)
				write('Closing... The connection is going throught the closing handshake (readyState '+this.readyState+')');
			else if(this.readyState == 3)
				write('Connection closed... The connection has been closed or could not be opened (readyState '+this.readyState+')');
			else
				write('Connection closed... (unhandled readyState '+this.readyState+')');
		};
		ws.onerror = function(event) {
			terminal.innerHTML = '<li style="color: red;">'+event.data+'</li>'+terminal.innerHTML;
		};
	}
	catch(exception) {
		write(exception);
	}
};

function write(text) {
	var date = new Date();
	var dateText = '['+date.getFullYear()+'-'+(date.getMonth()+1 > 9 ? date.getMonth()+1 : '0'+date.getMonth()+1)+'-'+(date.getDate() > 9 ? date.getDate() : '0'+date.getDate())+' '+(date.getHours() > 9 ? date.getHours() : '0'+date.getHours())+':'+(date.getMinutes() > 9 ? date.getMinutes() : '0'+date.getMinutes())+':'+(date.getSeconds() > 9 ? date.getSeconds() : '0'+date.getSeconds())+']';
	var terminal = document.getElementById('terminal');
	terminal.innerHTML = '<li>'+dateText+' '+text+'</li>'+terminal.innerHTML;
}
</script>

</head>
<body>
	<a href="client.html" target="_blank">Add another client</a>
	<ul id="terminal"></ul>
</body>
</html>

3.2 如下浏览器地址栏输入 http://localhost/client.html,新建客户端,连接成功会收到服务器发送过来的消息

 3.3 同时有新客户端连接,服务器端也会收到客户端的发来的消息

 四 Android端Socket客户端示例

4.1 直接用OkHttp里面的Websocket来连接socket,同时添加心跳机制

public class MainActivity extends AppCompatActivity {
    private WebSocket mWebSocket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }



    public void openSocket(View view) {
        initSocket();
    }


    private void initSocket() {
        String sw_url = "ws://192.168.81.1:1000";
        OkHttpClient client = new OkHttpClient.Builder().readTimeout(5000, TimeUnit.MILLISECONDS).build();
        Request request = new Request.Builder().url(sw_url).build();
        client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {//开启长连接成功的回调
                super.onOpen(webSocket, response);
                mWebSocket = webSocket;
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {//接收消息的回调
                super.onMessage(webSocket, text);
                //收到服务器端传过来的消息text
                Log.e("TAG", "接收消息的回调--------------" + text);
                try {
                    //这个是解析你的回调数据
                    JSONObject jsonObject = new JSONObject(text);
                    //通过事件总线修改
                    String type = jsonObject.getString("type");
                    String message = jsonObject.getString("message");
                    Log.e("TAG", "message:" + message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
                Log.e("TAG", "onMessage:" + bytes.toString());
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                Log.e("TAG", "onClosing:" + reason);

            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                Log.e("TAG", "onClosed:" + reason);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {//长连接连接失败的回调
                super.onFailure(webSocket, t, response);
                Log.e("TAG", "onFailure:" + response);

            }
        });
        client.dispatcher().executorService().shutdown();
        mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测
    }



    class InitSocketThread extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                initSocket();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 心跳检测时间
     */
    private static final long HEART_BEAT_RATE = 10 * 1000;//每隔10秒进行一次对长连接的心跳检测

    private long sendTime = 0L;
    // 发送心跳包
    private Handler mHandler = new Handler();
    private Runnable heartBeatRunnable = new Runnable() {
        @Override
        public void run() {
            if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) {
                if (mWebSocket != null) {
                    String msg="android-心跳";
                    try {
                        JSONObject jsonObject=new JSONObject();
                        jsonObject.put("id","111");
                        jsonObject.put("title","EEEEEEEEEEEEEEEE");
                        msg=jsonObject.toString();
                    }catch (Exception e){
                        e.printStackTrace();
                    }

                    boolean isSuccess = mWebSocket.send(msg);//发送一个消息给服务器,通过发送消息的成功失败来判断长连接的连接状态
                    if (!isSuccess) {//长连接已断开,
                        Log.e("TAG", "发送心跳包-------------长连接已断开");
                        mHandler.removeCallbacks(heartBeatRunnable);
                        mWebSocket.cancel();//取消掉以前的长连接
                        new InitSocketThread().start();//创建一个新的连接
                    } else {//长连接处于连接状态---
                        Log.e("TAG", "发送心跳包-------------长连接处于连接状态");
                    }
                }

                sendTime = System.currentTimeMillis();
            }
            mHandler.postDelayed(this, HEART_BEAT_RATE);//每隔一定的时间,对长连接进行一次心跳检测
        }
    };

    //关闭长连接
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mWebSocket != null) {
            mWebSocket.close(1000, null);
        }
    }

}

4.2 可以看到控制台连接成功会收到服务器端消息,同时心跳打印

 4.3 服务器端也会打印心跳客户端发送的消息

五 内网穿透,外网访问内网

5.1 上面只是本地调试,外网无法连接本地socket,当然如果有服务器就可以忽略下面过程了

5.2 我们可以利用花生壳的内网穿透能力来实现外网访问内网,所谓内网穿透即即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。下面就相互通信的主机在网络中与 NAT 设备的相对位置介绍内网穿透方法

5.3 在花生壳上新建内网穿透映射

5.4 我们用网络调试工具NetAssist,来开启本地端口

5.5 这样就能通过花生壳的外网域名来访问本机的的地址

本地网址:http://localhost/tang/index.html

 外网访问:https://ip/tang/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/741389.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue之v-for(包含key内部原理讲解)

文章目录 前言一、v-for二、key1.介绍2.使用3.原理4.总结 总结 前言 v-for&#xff1a;列表渲染 一、v-for v-for将JSON数据中的数组或对象渲染出列表的样式呈现。 直接见代码实例&#xff0c;如下&#xff1a; <!DOCTYPE html> <html lang"en"><h…

多肽试剂84211-54-1,Cyclo(7-aminoheptanoyl-Phe-D-Trp-Lys-Thr[Bzl]),特点说明

----------资料编辑|陕西新研博美生物科技有限公司小编MISSwu--------- 多肽试剂 | 基础知识概述&#xff08;部分&#xff09;: 英文名称&#xff1a;Cyclo(7-aminoheptanoyl-Phe-D-Trp-Lys-Thr[Bzl]) CAS号&#xff1a;84211-54-1 分子式&#xff1a;C44H57N7O6 分子量&…

UE5_ 地编_siki海岛

1.开启水插件 制作流体 2.开启大陆地形的插件

《Kali渗透基础》08. 弱点扫描(二)

kali渗透 1&#xff1a;OpenVAS / GVM1.1&#xff1a;介绍1.2&#xff1a;安装1.3&#xff1a;使用 2&#xff1a;Nessus2.1&#xff1a;介绍2.2&#xff1a;安装2.3&#xff1a;使用 3&#xff1a;Nexpose 本系列侧重方法论&#xff0c;各工具只是实现目标的载体。 命令与工具…

JAVA并发专题(2)之JMMsynchronizedvolatile详解

一、什么是JMM模型 Java内存模型(Java Memory Model简称JMM)是一种抽象的概念&#xff0c;并不真实存在&#xff0c;它描述的是一组规则或规范&#xff0c;通过这组规范定义了程序中各个变量&#xff08;包括实例字段&#xff0c;静态字段和构成数组对象的元素&#xff09;的访…

Chapter 5: Loops and Iterations | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介Loops and IterationsUpdating variablesThe while statementInfinite loopsFinishing iterations with continueDefinite loops using forLoop patternsCounting and summing loopsMaximum and minimum loopsDebuggingGlossary Python f…

Linux命令简单学习

文件 输出并覆盖到哪个文件夹 1>文件 正常输出并覆盖 2>文件 输出错误流到某个文件夹 nohup 不强杀不退出 软连接,注意这里需要绝对路径 %% 一个文字的 %%a 当前locale 的星期名缩写(例如&#xff1a; 日&#xff0c;代表星期日)%A 当前locale 的星期名全称 (如&…

wsl 1和wsl 2在形式上的区别

完整的比较请参考&#xff1a; 比较 WSL 版本 | Microsoft Learn 如果是已经安装完成&#xff0c;但安装的是wsl &#xff0c;之后想由 wsl 1升级到wsl 2&#xff0c;请参考&#xff1a;WSL1升级至WSL2_wsl1升级wsl2_goldVitaminC的博客-CSDN博客 在形式上&#xff0c;wsl 1是…

Android AccessibilityService 实现《李跳跳》功能

AccessibilityService&#xff08;无障碍服务&#xff09;是 Android 操作系统中的一个功能&#xff0c;旨在帮助用户具有视觉、听觉或运动上的障碍更轻松地使用设备。它是 Android 提供的一种特殊服务&#xff0c;可以接收设备上发生的各种事件&#xff0c;并提供自定义的反馈…

UWB的技术特点

近年来&#xff0c;超宽带(UWB)无线通信成为短距离、高速无线网络最热门的物理层技术之一。 UWB的产生与发展 超宽带(UWB)有着悠久的发展历史&#xff0c;但在1989年之前&#xff0c;超宽带这一术语并不常用&#xff0c;在信号的带宽和频谱结构方面也没有明确的规定。1989年&a…

2023年了,v-if和v-for的优先级千万别怼错了,可尴尬的...

前言 v-if和v-for到底是谁的优先级更高呢&#xff1f;在vue3版本出来之前你直接说v-for更高&#xff0c;我无法反驳你&#xff0c;但是老哥现在是2023年了&#xff0c;咱可不兴这样回答了&#xff0c;可尴尬的... 剖析 我们都知道&#xff0c;这个v-if是条件渲染&#xff0c;…

flutter开发实战-Running Gradle task ‘assembleDebug‘ 的解决方法

flutter开发实战-Running Gradle task ‘assembleDebug‘ 的解决方法 使用Android studio经常出现Running Gradle task ‘assembleDebug‘问题&#xff0c;记录一下解决方法。 一、在Android目录下更改build.gradle 将repositories中的google(), mavenCentral() repositori…

一、简单的Spring Authorization Server示例代码

需要有一定的OAuth2的基础 需要有一定的Spring Security基础 Spring Authorization Server 官方简介&#xff1a;Spring Authorization Server is a framework that provides implementations of the OAuth 2.1 and OpenID Connect 1.0 个人理解为OAuth 2.1 and OpenID Conne…

用技术指标伦敦金行情走势图

经常有投资者说&#xff0c;伦敦金行情走势图老是涨跌涨跌&#xff0c;抓不准它涨跌的规律&#xff0c;老是被它弄得头昏脑胀。其实看伦敦金行情走势图的方法有很多&#xff0c;最直接的就是使用技术指标。技术指标本来就是投资者为了避免伦敦金行情走势图上价格干扰性波动&…

玩转AI二维码:分享我的漂亮二维码生成秘诀

这几天我又生成了很多漂亮的二维码图片&#xff0c;有了一些感受和想法&#xff0c;特总结此文&#xff0c;分享给大家 先看效果&#xff0c;喜欢的可以继续读下去&#xff08;遵守平台规则&#xff0c;图片已阉割&#xff0c;需要更多图片参数的同学可直接看文章最后&#xf…

Leap AI + Python 开发绘图应用

使用python语言&#xff0c;并借助Leap AI网站的api key&#xff0c;可以轻松实现AI绘图功能。使用时&#xff0c;用户只要输入prompt提示词&#xff0c;几秒钟之内服务器就能生成图片并返回图片的链接地址。开发人员可以利用这个功能开发个性化的绘图软件&#xff0c;或者整合…

后端Linux软件安装大全[JDK、Tomcat、MySQL、Irzsz...持续更新中]

文章目录 前言1.软件安装方式2.安装jdk3.安装Tomcat4.安装MySQL5.安装lrzsz 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。当然&#xff0c;如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚&…

Webkit浏览器内核探究——Webkit简介

文章目录 1、什么是Webkit2、Webkit做了什么3、Webkit组成4、应用程序如何利用Webkit的 1、什么是Webkit Wekbit是一个开源的Web浏览器引擎&#xff0c;也就是浏览器的内核。 Apple的Safari, Google的Chrome, Nokia S60平台的默认浏览器&#xff0c;Apple手机的默认浏览器&…

如何监测电路中恶性负载

随着社会的发展和科技的进步&#xff0c;人们对于用电的安全性和稳定性要求越来越高。电路中的恶性负载往往会导致电路故障&#xff0c;甚至引发火灾等严重事故。因此&#xff0c;如何监测电路中的恶性负载成为了一个重要的课题。本文将从恶性负载的定义、监测方法、防范措施等…

【新版系统架构】第十八章-安全架构设计理论与实践

信息安全体系架构设计 信息系统安全设计重点考虑&#xff1a;系统安全保障体系&#xff0c;信息安全体系架构 系统安全保障体系&#xff1a; 安全区域策略的确定&#xff0c;根据安全区域的划分&#xff0c;主管部门应制定针对性的安全策略统一配置和管理防病毒系统&#xff…