PHP-实现代码怎么写-Socket如何收发十六进制数据

教程大全 2026-02-27 00:01:37 浏览

PHP通过Socket收发十六进制数据的核心在于利用PHP内置的系列函数建立底层TCP/UDP连接,并配合与函数实现十六进制字符串与二进制数据之间的相互转换,在处理硬件通信、物联网协议对接或私有二进制协议时,PHP的字符串类型本质上是二进制安全的,这为直接处理Socket字节流提供了天然优势,开发者无需依赖第三方扩展,仅通过原生函数即可构建高效、稳定的十六进制数据通信服务。

Socket连接的建立与配置

在PHP中操作Socket的第一步是创建一个通信端点,使用 socket_create(AF_INET, SOCK_STREAM, SOL_TCP) 函数可以创建一个TCP流套接字,这里代表IPv4网络协议, SOCK_STREAM 提供面向连接的可靠数据传输,创建成功后,需要通过 socket_connect 函数与目标服务器建立连接。

为了保证通信的健壮性,必须设置Socket超时时间,通过 socket_set_option 函数可以配置 SO_RCVtimeO (接收超时)和 SO_SNDTIMEO (发送超时),防止因网络波动导致脚本长时间阻塞,这一步在实际生产环境中至关重要,能够有效避免PHP进程僵死。

十六进制与二进制数据的转换机制

这是实现十六进制通信最关键的技术环节,PHP发送Socket数据时,发送的内容必须是二进制字符串,而我们通常看到的“0xAA 0xBB”这种格式是供人类阅读的十六进制表示。

发送数据转换 :使用函数,将十六进制字符串“7E”转换为实际的字节,代码实现为 pack('H*', '7E') ,其中格式符“H*”表示将十六进制字符串作为高位在前的一串数据进行打包,如果需要发送整型、浮点型等复合数据结构,函数支持多种格式符(如N代表无符号长整型、n代表短整型),能够满足不同字节序(大端/小端)的需求。

接收数据解析 :使用函数,当从Socket读取到二进制数据流后,需要将其还原为可读的十六进制字符串或具体数值,将接收到的二进制数据转换为十六进制字符串表示,使用 unpack('H*', $binaryData) ,这一步是调试和业务逻辑处理的基础。

完整的Socket收发实现代码

以下是一个封装良好的PHP类,展示了完整的连接、发送十六进制指令及接收响应的过程。

class HexSocketClient {private $socket;private $host;private $port;public function __construct($host, $port) {$this->host = $host;$this->port = $port;}public function connect() {$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);if ($this->socket === false) {throw new Exception("Socket创建失败: " . socket_strerror(socket_last_error()));}// 设置发送和接收超时为5秒socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 5, 'usec' => 0]);socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 5, 'usec' => 0]);if (!socket_connect($this->socket, $this->host, $this->port)) {throw new Exception("连接失败: " . socket_strerror(socket_last_error($this->socket)));}}/*** 发送十六进制字符串数据* @param string $hexString "AABBCCDD"*/public function sendHex($hexString) {// 将十六进制字符串转换为二进制数据$binaryData = pack('H*', $hexString);$length = strlen($binaryData);$sent = socket_write($this->socket, $binaryData, $length);if ($sent === false) {throw new Exception("发送失败: " . socket_strerror(socket_last_error($this->socket)));}return $sent;}/*** 读取数据并返回十六进制字符串* @param int $length 读取长度*/public function readHex($length = 1024) {$binaryData = socket_read($this->socket, $length, PHP_BINARY_READ);if ($binaryData === false) {throw new Exception("读取失败: " . socket_strerror(socket_last_error($this->socket)));}// 将二进制数据转换为十六进制字符串if (empty($binaryData)) return '';$data = unpack('H*', $binaryData);return strtoupper($data[1]);}public function close() {if ($this->socket) {socket_close($this->socket);}}}// 使用示例try {$client = new HexSocketClient('192.168.1.100', 8080);$client->connect();// 发送指令:设备查询头 0xAA 0x55$client->sendHex('AA55');// 读取响应$response = $client->readHex(1024);echo "收到响应: " . $response;$client->close();} catch (Exception $e) {echo "错误: " . $e->getMessage();}

酷番云 实战经验案例:物联网网关的高并发通信

在实际的企业级物联网项目中,单纯的代码逻辑往往不足以应对复杂的网络环境。 酷番云 在为一家智能硬件制造商搭建数据中台时,遇到了PHP Socket进程在处理高并发设备上报时的稳定性挑战。

在该案例中,数万台传感器通过TCP长连接向服务器发送十六进制心跳包和数据帧,初期,部署在普通虚拟机上的PHP脚本经常因为Socket连接堆积导致内存溢出。 酷番云 技术团队提供的解决方案是:利用 酷番云高性能计算型云服务器 的独享计算资源,配合PHP的 pcntl_fork 多进程扩展,将Socket监听服务拆分为多个Worker进程。

利用 酷番云 内网的高带宽和低延迟特性,将PHP Socket服务与Redis消息队列解耦,Socket进程仅负责通过 socket_read 快速接收十六进制流并推入队列,后续复杂的解析和业务逻辑处理异步进行,这种架构不仅解决了单进程阻塞问题,还将十六进制数据的处理吞吐量提升了300%以上,此案例证明,在稳定的底层基础设施之上,PHP处理二进制Socket通信的能力完全可以满足工业级需求。

进阶处理与常见陷阱

在处理十六进制数据时, 字节序 是一个极易被忽视的问题,x86架构通常是小端序,而网络标准是大端序,在使用处理整数时,必须明确使用(大端长整型)或(小端长整型)等格式符,否则数据解析将完全错误。

Socket通信中的 粘包与拆包 现象是二进制开发的必修课,TCP是流式协议,并不能保证一次 接收十六进制数据 socket_read 就能读取到一个完整的应用层数据包,开发者通常需要在协议头定义数据长度字段,或者使用特殊的十六进制分隔符(如)来切分数据流,在PHP中,可以通过循环读取缓冲区并拼接字符串,直到满足特定长度或结束符为止。

相关问答

Q1:为什么我使用socket_read收到的数据是乱码? 这通常是因为直接输出了二进制数据,Socket接收到的原始数据是ASCII码值在0-255之间的字节,直接echo会导致显示乱码,请务必使用 unpack('H*', $data) 将其转换为十六进制字符串后再进行查看或调试。

Q2:PHP Socket发送十六进制数据时,如何发送空字节(0x00)? PHP字符串是二进制安全的,可以包含空字节,在构造十六进制字符串时,直接包含“00”即可,例如发送 pack('H*', '01000002') ,中间的“00”会被正确转换为一个空字节,也会正确计算包含空字节在内的总长度。

通过掌握上述原理与代码实现,开发者可以充分利用PHP在快速开发上的优势,高效解决底层硬件通信与二进制协议对接的难题,如果您在具体实施过程中遇到关于Socket阻塞或超时设置的细节问题,欢迎在评论区讨论,共同探索PHP在网络编程中的更多可能性。


Python Serial 串口能不能发送16进制的数字

pyserial默认的写入格式是字符串,并不是16进制的。 接收到的也是字符串,除非你在接收端设置了默认接受hex格式,就会显示hex。 我用过蓝牙串口和Arduino开发板的串口,收到的都是字符串。

epoll和select的区别

1、select实现(1)使用copy_from_user从用户空间拷贝fd_set到内核空间(2)注册回调函数__pollwait(3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)(4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。 (5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。 在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。 (6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。 (7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。 当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。 如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。 (8)把fd_set从内核空间拷贝到用户空间。 总结:select的几大缺点:(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大(3)select支持的文件描述符数量太小了,默认是、epollepoll既然是对select和poll的改进,就应该能避免上述的三个缺点。 那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。 而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。 对于第一个缺点,epoll的解决方案在epoll_ctl函数中。 每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。 epoll保证了每个fd在整个过程中只会拷贝一次。 对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。 epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。 对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 总结:(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。 而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。 虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。 这就是回调机制带来的性能提升。 (2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。 这也能节省不少的开销。

如何通过WebSocket连接服务器进行数据传输

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。 两者之间就直接可以数据互相传送。 Cocos2d-x引擎集成libwebsockets,并在libwebsockets的客户端API基础上封装了一层易用的接口,使得引擎在C++, JS, Lua层都能方便的使用WebSocket来进行游戏网络通讯。 引擎支持最新的WebSocket Version 13。 在C++中使用详细代码可参考引擎目录下的/samples/Cpp/TestCpp/Classes/ExtensionsTest/NetworkTest/文件。 头文件中的准备工作首先需要include WebSocket的头文件。 #include network/2d::network::WebSocket::Delegate定义了使用WebScocket需要监听的回调通知接口。 使用WebSocket的类,需要public继承这个Delegate。 class WebSocketTestLayer : public cocos2d::Layer, public cocos2d::network::WebSocket::Delegate 并Override下面的4个接口:virtual void onOpen(cocos2d::network::WebSocket* ws); virtual void onMessage(cocos2d::network::WebSocket* ws, const cocos2d::network::WebSocket::Data& data); virtual void onClose(cocos2d::network::WebSocket* ws); virtual void onError(cocos2d::network::WebSocket* ws, const cocos2d::network::WebSocket::ErrorCode& error); 后面我们再详细介绍每个回调接口的含义。 新建WebSocket并初始化 提供了一个专门用来测试WebSocket的服务器ws://。 测试代码以链接这个服务器为例,展示如何在Cocos2d-x中使用WebSocket。 新建一个WebSocket:cocos2d::network::WebSocket* _wsiSendText = new network::WebSocket(); init第一个参数是delegate,设置为this,第二个参数是服务器地址。 URL中的ws://标识是WebSocket协议,加密的WebSocket为wss://._wsiSendText->init(*this, ws://) WebSocket消息监听在调用send发送消息之前,先来看下4个消息回调。 onOpeninit会触发WebSocket链接服务器,如果成功,WebSocket就会调用onOpen,告诉调用者,客户端到服务器的通讯链路已经成功建立,可以收发消息了。 void WebSocketTestLayer::onOpen(network::WebSocket* ws) { if (ws == _wsiSendText) { _sendTextStatus->setString(Send Text WS was opened.); } } onMessagenetwork::WebSocket::Data对象存储客户端接收到的数据, isBinary属性用来判断数据是二进制还是文本,len说明数据长度,bytes指向数据。 void WebSocketTestLayer::onMessage(network::WebSocket* ws, const network::WebSocket::Data& data) { if (!) { _sendTextTimes++; char times[100] = {0}; sprintf(times, %d, _sendTextTimes); std::string textStr = std::string(response text msg: )++, +times; log(%s, textStr.c_str()); _sendTextStatus->setString(textStr.c_str()); } } onClose不管是服务器主动还是被动关闭了WebSocket,客户端将收到这个请求后,需要释放WebSocket内存,并养成良好的习惯:置空指针。 void WebSocketTestLayer::onClose(network::WebSocket* ws) { if (ws == _wsiSendText) { _wsiSendText = NULL; } CC_SAFE_DELETE(ws); } onError客户端发送的请求,如果发生错误,就会收到onError消息,游戏针对不同的错误码,做出相应的处理。 void WebSocketTestLayer::onError(network::WebSocket* ws, const network::WebSocket::ErrorCode& error) { log(Error was fired, error code: %d, error); if (ws == _wsiSendText) { char buf[100] = {0}; sprintf(buf, an error was fired, code: %d, error); _sendTextStatus->setString(buf); } } send消息到服务器在init之后,我们就可以调用send接口,往服务器发送数据请求。 send有文本和二进制两中模式。 发送文本_wsiSendText->send(Hello WebSocket, Im a text message.); 发送二进制数据(多了一个len参数)_wsiSendBinary->send((unsigned char*)buf, sizeof(buf)); 主动关闭WebSocket这是让整个流程变得完整的关键步骤, 当某个WebSocket的通讯不再使用的时候,我们必须手动关闭这个WebSocket与服务器的连接。 close会触发onClose消息,而后onClose里面,我们释放内存。 _wsiSendText->close(); 在Lua中使用详细代码可参考引擎目录下的/samples/Lua/TestLua/Resources/luaScript/ExtensionTest/文件。 创建WebSocket对象脚本接口相对C++要简单很多,没有头文件,创建WebSocket对象使用下面的一行代码搞定。 参数是服务器地址。 wsSendText = WebSocket:create(ws://) 定义并注册消息回调函数回调函数是普通的Lua function,4个消息回调和c++的用途一致,参考上面的说明。 local function wsSendTextOpen(strData) sendTextStatus:setString(Send Text WS was opened.) end local function wsSendTextMessage(strData) receiveTextTimes= receiveTextTimes + 1 local strInfo= response text msg: .., sendTextStatus:setString(strInfo) end local function wsSendTextClose(strData) print(_wsiSendText websocket instance closed.) sendTextStatus = nil wsSendText = nil end local function wsSendTextError(strData) print(sendText Error was fired) end Lua的消息注册不同于C++的继承 & Override,有单独的接口registerScriptHandler。 registerScriptHandler第一个参数是回调函数名,第二个参数是回调类型。 每一个WebSocket实例都需要绑定一次。 if nil ~= wsSendText then wsSendText:registerScriptHandler(wsSendTextOpen,_OPEN) wsSendText:registerScriptHandler(wsSendTextMessage,_MESSAGE) wsSendText:registerScriptHandler(wsSendTextClose,_CLOSE) wsSendText:registerScriptHandler(wsSendTextError,_ERROR) end send消息Lua中发送不区分文本或二进制模式,均使用下面的接口。 wsSendText:sendString(Hello WebSocket中文, Im a text message.) 主动关闭WebSocket当某个WebSocket的通讯不再使用的时候,我们必须手动关闭这个WebSocket与服务器的连接,以释放服务器和客户端的资源。 close会触发_CLOSE消息。 wsSendText:close() 在JSB中使用详细代码可参考引擎目录下的/samples/Javascript/Shared/tests/ExtensionsTest/NetworkTest/文件。 创建WebSocket对象脚本接口相对C++要简单很多,没有头文件,创建WebSocket对象使用下面的一行代码搞定。 参数是服务器地址。 this._wsiSendText = new WebSocket(ws://); 设置消息回调函数JSB中的回调函数是WebSocket实例的属性,使用匿名函数直接赋值给对应属性。 可以看出JS语言的特性,让绑定回调函数更加优美。 四个回调的含义,参考上面c++的描述。 this._ = function(evt) { self._(Send Text WS was opened.); }; this._ = function(evt) { self._sendTextTimes++; var textStr = response text msg: ++, +self._sendTextTimes; (textStr); self._(textStr); }; this._ = function(evt) { (sendText Error was fired); }; this._ = function(evt) { (_wsiSendText websocket instance closed.); self._wsiSendText = null; }; send消息发送文本,无需转换,代码如下:this._(Hello WebSocket中文, Im a text message.); 发送二进制,测试代码中,使用_stringConvertToArray函数来转换string为二进制数据,模拟二进制的发送。 new Uint16Array创建一个16位无符号整数值的类型化数组,内容将初始化为0。 然后,循环读取字符串的每一个字符的Unicode编码,并存入Uint16Array,最终得到一个二进制对象。 _stringConvertToArray:function (strData) { if (!strData) returnnull; var arrData = new Uint16Array(); for (var i = 0; i < ; i++) { arrData[i] = (i); } return arrData; }, send二进制接口和send文本没有区别,区别在于传入的对象,JS内部自己知道对象是文本还是二进制数据,然后做不同的处理。 var buf = Hello WebSocket中文,\0 Im\0 a\0 binary\0 message\0.; var binary = this._stringConvertToArray(buf); this._(); 主动关闭WebSocket当某个WebSocket的通讯不再使用的时候,我们必须手动关闭这个WebSocket与服务器的连接,以释放服务器和客户端的资源。 close会触发onclose消息。 onExit: function() { if (this._wsiSendText) this._();

本文版权声明本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请联系本站客服,一经查实,本站将立刻删除。

发表评论

热门推荐