PHP之 Socket实践

一  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+')');elsewrite('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;@Overrideprotected 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() {@Overridepublic void onOpen(WebSocket webSocket, Response response) {//开启长连接成功的回调super.onOpen(webSocket, response);mWebSocket = webSocket;}@Overridepublic void onMessage(WebSocket webSocket, String text) {//接收消息的回调super.onMessage(webSocket, text);//收到服务器端传过来的消息textLog.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();}}@Overridepublic void onMessage(WebSocket webSocket, ByteString bytes) {super.onMessage(webSocket, bytes);Log.e("TAG", "onMessage:" + bytes.toString());}@Overridepublic void onClosing(WebSocket webSocket, int code, String reason) {super.onClosing(webSocket, code, reason);Log.e("TAG", "onClosing:" + reason);}@Overridepublic void onClosed(WebSocket webSocket, int code, String reason) {super.onClosed(webSocket, code, reason);Log.e("TAG", "onClosed:" + reason);}@Overridepublic 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 {@Overridepublic 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() {@Overridepublic 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);//每隔一定的时间,对长连接进行一次心跳检测}};//关闭长连接@Overridepublic 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.hqwc.cn/news/18026.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为云编译构建CodeArts Build新手操作指南

华为云编译构建&#xff08;CodeArts Build&#xff09;基于云端大规模并发加速&#xff0c;为客户提供高速、低成本、配置简单的混合语言构建能力&#xff0c;帮助客户缩短构建时间&#xff0c;提升构建效率。 本文将给各位开发者带来华为云CodeArts Pipeline的手把手初级教学…

一文教会你风格迁移CycleGAN从入门到高阶再到最终成功魔改(附成功魔改代码)

专栏导读 &#x1f525;&#x1f525;本文已收录于专栏&#xff1a;《风格迁移之从入门到成功魔改》&#xff0c;欢迎免费订阅 ​此专栏用于带你从零基础学会什么是风格迁移&#xff0c;风格迁移有什么作用&#xff0c;传统做法和Cyclegan的原理&#xff0c;及其优缺点&#x…

基于51单片机和proteus的水质水位检测系统

此系统是基于51单片机和proteus的仿真设计&#xff0c;功能如下&#xff1a; 1. LCD1602实时显示水质和水位状态。 2. 按键可设定水质检测阈值并通过LCD显示。 3. LED指示水质水位和系统运行状态。 4. 水质差超过阈值后自动启动排水泵。 5. 水位过低时自动启动进水泵。 6…

SpringBoot项目模块间通信的两种方式

说明&#xff1a;在微服务架构开发中&#xff0c;一个请求是通过模块之间的互相通信来完成的&#xff0c;如下面这个场景&#xff1a; 创建两个子模块&#xff1a;订单模块&#xff08;端口8081&#xff09;、用户模块&#xff08;端口8082&#xff09;&#xff0c;两个模块之…

Spring Boot 中的 Native SQL 是什么, 如何使用

在 Spring Boot 中&#xff0c;我们通常使用 ORM 框架&#xff08;例如 Hibernate 或 MyBatis&#xff09;来操作数据库。但是&#xff0c;有时候我们需要执行一些自定义的 SQL 查询或更新语句&#xff0c;这时候就需要使用 Spring Boot 中的 Native SQL。 在本文中&#xff0…

常见面试题之JVM实践(调优)

1. JVM调优的参数可以在哪里设置参数值&#xff1f; 1.1 tomcat的设置vm参数 修改TOMCAT_HOME/bin/catalina.sh文件&#xff0c;如下图&#xff1a; JAVA_OPTS"-Xms512m -Xmx1024m" 1.2 springboot项目jar文件启动 通常在linux系统下直接加参数启动springboot项…

鼠标右击没有新建WORD、EXCEL、PPT选项卡解决方案

一、WinR打开运行窗口&#xff0c;输入regedit打开注册表 二、进入到相应位置&#xff0c;复制粘贴到路径处即可 ①word word&#xff1a;计算机\HKEY_CLASSES_ROOT\.docx 计算机\HKEY_CLASSES_ROOT\.doc 看你改哪个都行&#xff0c;我觉得修改第一个docx那个就行&#xff0c…

【Elasticsearch】RestClient操作文档

目录 5.RestClient操作文档 5.1.新增文档 5.1.1.索引库实体类 5.1.2.语法说明 5.1.3.完整代码 5.2.查询文档 5.2.1.语法说明 5.2.2.完整代码 5.3.删除文档 5.4.修改文档 5.4.1.语法说明 5.4.2.完整代码 5.5.批量导入文档 5.5.1.语法说明 5.5.2.完整代码 5.6.小…

[QT编程系列-1]:C++图形用户界面编程,QT框架快速入门培训 - 0- 总述

目录 导言 主要内容 附录&#xff1a; 导言 1. 在这里强调为啥选择 PPT 方式&#xff0c;而不是直接讲解代码 2. 重原理和方法 3. 种 QT 的框架和 QT 的开发流程 4. 轻 UI 界面美观&#xff08; UI 设计单独课程&#xff09; 5. 请代码具体实现&#xff08;后期自学&#xf…

使用selenium爬取猫眼电影榜单数据

文章目录 前言导入所需的库&#xff1a;设置ChromeDriver的路径&#xff0c;并创建一个Chrome浏览器实例&#xff1a;打开目标网页&#xff0c;这里以猫眼电影榜单页面为例&#xff1a;使用XPath定位电影信息。通过查看网页源代码&#xff0c;发现电影信息所在的<dd>标签…

Appium+python自动化(二)- 环境搭建—下(超详解)

简介 上一篇android测试开发环境已经准备好&#xff0c; 那么接下来就是appium的环境安装和搭建了。 环境装好后&#xff0c;可以用真机连电脑&#xff0c;也可以用android-sdk里面的模拟器&#xff08;当然这个模拟器不是很好用&#xff09;&#xff0c;我一般喜欢真机&#…

Git 学习笔记

1、创建本地库&#xff0c;添加文件 1.1 创建本地库 先用git跳转到对应的文件夹下&#xff0c;可以手动创建仓库文件夹&#xff0c;也可以在git中使用如下指令创建并跳转&#xff1a; mkdir filename cd filename 注&#xff1a;这里的filename为文件夹名 随后输入指令&#x…