最近公司做了一个项目,需要使用一个儿童智能手表,这个手表与我们的服务器进行socket通信,于是搭建socket的任务就落在了我身上,这个手表每次发送的参数是一个字符串,而且貌似只能发字符串,形如
KM*8800000015*0001*0032*INIT,1346545,0,k6_BASE_V1.5454,3,2
本代码只进行了socket的简单封装,以及鉴权的举例说明,正常情况下需要配合第三方来进行鉴权操作,比如约定一个token来进行验证,本文假装KM
既为token,同时一些逻辑操作也需要根据自己的业务和第三方api来进行更改,本文仅为学习示例。
<?php
/**
* Author huaixiu.zhen
* http://litblc.com
* User: litblc
* Date: 2018/5/23
* Time: 15:45
*/
class SocketController
{
private $sockets;
private $master;
private $handshake;
/**
* 创建连接
* Author huaixiu.zhen
* http://litblc.com
* @param $address
* @param $port
* @return resource
*/
private function createSocket($address, $port)
{
//创建socket
$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
or die("socket_create() failed: reason1: " . socket_strerror(socket_last_error()) . "\n");
socket_bind($master, $address, $port)
or die("socket_bind() failed: reason2: " . socket_strerror(socket_last_error($master)) . "\n");
socket_listen($master, 4)
or die("socket_listen() failed: reason3: " . socket_strerror(socket_last_error($master)) . "\n");
return $master;
}
/**
* 鉴权处理(如果改服务商,需要修改成对应处理)
* Author huaixiu.zhen
* http://litblc.com
* @param $buffer
* @return array|bool
*/
private function authenticate($buffer)
{
$data = explode(',', $buffer);
// $data = json_decode($buffer);
if(is_array($data) && substr($data[0], 0, 2) == 'KM' && count($data) > 1){
// todo 目前只知道这么多情况,如果需要入库的话,将再次进行数据库查询是否存在该设备
$this->handshake = true;
return $data;
}
return false;
}
/**
* 关闭连接
* Author huaixiu.zhen
* http://litblc.com
* @param $socket
*/
private function close($socket)
{
$index = array_search($socket, $this->sockets);
socket_close($socket);
if ($index >= 0) {
array_splice($this->sockets, $index, 1);
}
}
/**
* Author huaixiu.zhen
* http://litblc.com
* @param $response
*/
private function sendMessageToClient($response)
{
foreach ($this->sockets as $socket) {
socket_write($socket, $response, strlen($response));
}
}
/**
* 后端逻辑(根据服务商修改成对应处理)
* Author huaixiu.zhen
* http://litblc.com
* @param $dataArr
* @return array|string
*/
private function handle($dataArr)
{
$case = substr($dataArr[0], -2);
$result = [$dataArr[0], 0];
switch ($case) {
case 'IT':
$result = $this->init($dataArr);
break;
case 'LK':
$result = $this->lk($dataArr);
}
return $result;
}
/**
* 初始化示例操作(根据服务商修改成对应处理)
* Author huaixiu.zhen
* http://litblc.com
* @param $dataArr
* @return string
*/
private function init($dataArr)
{
return json_encode([
$dataArr[0],
1
]);
}
/**
* lk示例操作(根据服务商修改成对应处理)
* Author huaixiu.zhen
* http://litblc.com
* @param $dataArr
* @return string
*/
private function lk($dataArr)
{
return json_encode([
$dataArr[0],
'LK_TEST'
]);
}
/**
* Author huaixiu.zhen
* http://litblc.com
* @param $address
* @param $port
*/
public function run($address, $port)
{
//配置错误级别、运行时间、刷新缓冲区
echo "welcome to timelink socket. \n";
error_reporting(0);
set_time_limit(0);
ob_implicit_flush();
//创建socket
$this->master = $this->createSocket($address, $port);
$this->sockets[] = $this->master;
//运行socket
while (true) {
$sockets = $this->sockets;
$write = NULL;
$except = NULL;
socket_select($sockets, $write, $except, NULL); //$write,$except传引用
foreach ($sockets as $socket) {
if ($socket == $this->master) {
$client = socket_accept($socket);
$this->handshake = false;
if ($client) {
$this->sockets[] = $client; //加入连接池
}
} else {
$bytes = @socket_recv($socket, $buffer, 1024, 0);
// 鉴权处理 必须传字符串 形如:
// KM*8800000015*0001*0032*INIT,1346545,0,k6_BASE_V1.5454,3,2
$data = $this->authenticate($buffer);
if (!$data || $bytes <= 6) {
$this->close($socket);
continue;
}
// 入库操作 具体逻辑实现
$response = $this->handle($data);
$this->sendMessageToClient($response, $socket);
}
}
}
}
}
<?php
/**
* Author huaixiu.zhen
* http://litblc.com
* User: litblc
* Date: 2018/5/23
* Time: 15:50
*/
require_once 'SocketController.php';
$sc = new SocketController();
$sc->run('192.168.9.116', 886404);
控制台执行 php Socket.php
我本地ip为 192.168.9.116,服务端设置的端口为886404,代码里可以看到。
尝试发送垃圾数据,可以看到,它会自动断开当前连接:
ok,发送约定好的数据:
ok,大功告成。剩下的就是与第三方厂商协商具体传输数据的问题了。
(局域网测试的话记得关闭防火墙或者修改端口权限;传输机密内容的话应该加密)
后来临时改变厂商与传输方式,要求我做一个转发socket,但是是一个中间人的功能,就是终端传给我一些数据,经过我手里我自己处理一下(copy一份存库之类的操作),然后再原封不动的给第三方服务器,之后把第三方返回的数据原封不动的返回给终端。
于是我决定尝试一下既当服务端,又充当客户端。
在SocketController.php中新添加一个socket Client方法
/**
* 客户端 向第三方服务器发送socket
* Author huaixiu.zhen
* http://litblc.com
* @param $host
* @param $port
* @param $message
* @return int|string
*/
private function sendNewSocket($host, $port, $message)
{
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
@socket_connect($socket, $host, $port);
$num = 0;
$length = strlen($message);
do
{
$buffer = substr($message, $num);
$ret = @socket_write($socket, $buffer);
$num += $ret;
} while ($num < $length);
$ret = '';
do
{
$buffer = @socket_read($socket, 1024, PHP_BINARY_READ);
$ret .= $buffer;
} while (strlen($buffer) == 1024);
socket_close($socket);
return $ret;
}
然后在处理方法init中调用此方法,并返回他的返回值:
/**
* 初始化示例操作(根据服务商修改成对应处理)
* Author huaixiu.zhen
* http://litblc.com
* @param $dataArr
* @return string
*/
private function init($dataArr, $buffer)
{
$newScout = $this->sendNewSocket($newHost = '192.168.9.116', $newPort = '2046', $message = $buffer);
return json_encode([
$dataArr[0],
$newScout
]);
}
ok, 开俩个服务端进行测试,大功告成,既是客户端又充当服务端也是可行的。