<?php
//大概流程
/*
1:电脑插入海关给的操作员卡
2:添加海关请求declare_response.php,接受openReq{orderNo":"ord201800002","sessionID": "fe2374-8fnejf97-32839218","serviceTime":1533271903898}并保存
3:php加签,同时验证,定时任务执行declare_sign.php每10秒请求1次,查看是否有单,有单执行验签和推单
4:推单后执行发回执发送给服务器,服务器修改状态
*/
class ClientService
{
protected $socket_uri;
protected $socket;
protected $is_connected = false;
protected $is_closing = false;
protected $last_opcode = null;
protected $close_status = null;
protected $huge_payload = null;
protected $options = array();
protected static $opcodes = array(
'continuation' => 0,
'text' => 1,
'binary' => 2,
'close' => 8,
'ping' => 9,
'pong' => 10,
);
/**
* @param string $uri A ws/wss-URI
* @param array $options
* Associative array containing:
* - context: Set the stream context. Default: empty context
* - timeout: Set the socket timeout in seconds. Default: 5
* - headers: Associative array of headers to set/override.
*/
public function __construct($uri, $options = array())
{
$this->options = $options;
if (!array_key_exists('timeout', $this->options)) {
$this->options['timeout'] = 5;
}
// the fragment size
if (!array_key_exists('fragment_size', $this->options)) {
$this->options['fragment_size'] = 4096;
}
$this->socket_uri = $uri;
}
public function getLastOpcode()
{
return $this->last_opcode;
}
public function getCloseStatus()
{
return $this->close_status;
}
public function isConnected()
{
return $this->is_connected;
}
public function setTimeout($timeout)
{
$this->options['timeout'] = $timeout;
if ($this->socket && get_resource_type($this->socket) === 'stream') {
stream_set_timeout($this->socket, $timeout);
}
}
public function setFragmentSize($fragment_size)
{
$this->options['fragment_size'] = $fragment_size;
return $this;
}
public function getFragmentSize()
{
return $this->options['fragment_size'];
}
public function send($payload, $opcode = 'text', $masked = true)
{
if (!$this->is_connected) {
$this->connect();
}
/// @todo This is a client function, fixme!
if (!in_array($opcode, array_keys(self::$opcodes))) {
throw new \Exception("Bad opcode '$opcode'. Try 'text' or 'binary'.");
}
// record the length of the payload
$payload_length = strlen($payload);
$fragment_cursor = 0;
// while we have data to send
while ($payload_length > $fragment_cursor) {
// get a fragment of the payload
$sub_payload = substr($payload, $fragment_cursor, $this->options['fragment_size']);
// advance the cursor
$fragment_cursor += $this->options['fragment_size'];
// is this the final fragment to send?
$final = $payload_length <= $fragment_cursor;
// send the fragment
$this->send_fragment($final, $sub_payload, $opcode, $masked);
// all fragments after the first will be marked a continuation
$opcode = 'continuation';
}
}
protected function send_fragment($final, $payload, $opcode, $masked)
{
// Binary string for header.
$frame_head_binstr = '';
// Write FIN, final fragment bit.
$frame_head_binstr .= (bool) $final ? '1' : '0';
// RSV 1, 2, & 3 false and unused.
$frame_head_binstr .= '000';
// Opcode rest of the byte.
$frame_head_binstr .= sprintf('%04b', self::$opcodes[$opcode]);
// Use masking?
$frame_head_binstr .= $masked ? '1' : '0';
// 7 bits of payload length...
$payload_length = strlen($payload);
if ($payload_length > 65535) {
$frame_head_binstr .= decbin(127);
$frame_head_binstr .= sprintf('%064b', $payload_length);
} elseif ($payload_length > 125) {
$frame_head_binstr .= decbin(126);
$frame_head_binstr .= sprintf('%016b', $payload_length);
} else {
$frame_head_binstr .= sprintf('%07b', $payload_length);
}
$frame = '';
// Write frame head to frame.
foreach (str_split($frame_head_binstr, 8) as $binstr) {
$frame .= chr(bindec($binstr));
}
// Handle masking
if ($masked) {
// generate a random mask:
$mask = '';
for ($i = 0; $i < 4; ++$i) {
$mask .= chr(rand(0, 255));
}
$frame .= $mask;
}
// Append payload to frame:
for ($i = 0; $i < $payload_length; ++$i) {
$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
}
$this->write($frame);
}
public function receive()
{
if (!$this->is_connected) {
$this->connect();
}
/// @todo This is a client function, fixme!
$this->huge_payload = '';
$response = null;
while (is_null($response)) {
$response = $this->receive_fragment();
}
return $response;
}
protected function receive_fragment()
{
// Just read the main fragment information first.
$data = $this->read(2);
// Is this the final fragment? // Bit 0 in byte 0
/// @todo Handle huge payloads with multiple fragments.
$final = (bool) (ord($data[0]) & 1 << 7);
// Should be unused, and must be false… // Bits 1, 2, & 3
$rsv1 = (bool) (ord($data[0]) & 1 << 6);
$rsv2 = (bool) (ord($data[0]) & 1 << 5);
$rsv3 = (bool) (ord($data[0]) & 1 << 4);
// Parse opcode
$opcode_int = ord($data[0]) & 31; // Bits 4-7
$opcode_ints = array_flip(self::$opcodes);
if (!array_key_exists($opcode_int, $opcode_ints)) {
throw new \Exception("Bad opcode in websocket frame: $opcode_int");
}
$opcode = $opcode_ints[$opcode_int];
// record the opcode if we are not receiving a continutation fragment
if ($opcode !== 'continuation') {
$this->last_opcode = $opcode;
}
// Masking?
$mask = (bool) (ord($data[1]) >> 7); // Bit 0 in byte 1
$payload = '';
// Payload length
$payload_length = (int) ord($data[1]) & 127; // Bits 1-7 in byte 1
if ($payload_length > 125) {
if ($payload_length === 126) {
$data = $this->read(2);
}
// 126: Payload is a 16-bit unsigned int
else {
$data = $this->read(8);
}
// 127: Payload is a 64-bit unsigned int
$payload_length = bindec(self::sprintB($data));
}
// Get masking key.
if ($mask) {
$masking_key = $this->read(4);
}
// Get the actu