Completed
Branch 0.4-dev (bf83d7)
by Evgenij
03:03
created

StreamedClientIo::writeOobData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 10
cts 11
cp 0.9091
rs 9.4285
cc 3
eloc 10
nc 3
nop 2
crap 3.0067
1
<?php
2
/**
3
 * Async sockets
4
 *
5
 * @copyright Copyright (c) 2015-2016, Efimov Evgenij <[email protected]>
6
 *
7
 * This source file is subject to the MIT license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
namespace AsyncSockets\Socket\Io;
11
12
use AsyncSockets\Frame\FramePickerInterface;
13
14
/**
15
 * Class StreamedClientIo
16
 */
17
class StreamedClientIo extends AbstractClientIo
18
{
19
    /**
20
     * Read attempts count
21
     */
22
    const READ_ATTEMPTS = 2;
23
24
    /**
25
     * Amount of read attempts
26
     *
27
     * @var int
28
     */
29
    private $readAttempts = self::READ_ATTEMPTS;
30
31
    /**
32
     * Remote socket address
33
     *
34
     * @var string
35
     */
36
    private $remoteAddress;
37
38
    /** {@inheritdoc} */
39 25
    protected function readRawDataIntoPicker(FramePickerInterface $picker)
40
    {
41
        // work-around https://bugs.php.net/bug.php?id=52602
42 25
        $resource         = $this->socket->getStreamResource();
43
        $readContext      = [
44 25
            'countCycles'       => 0,
45 25
            'dataBeforeIo'      => $this->getDataInSocket(),
46 25
            'isStreamDataEmpty' => false,
47 25
        ];
48
49
        do {
50 25
            $data = fread($resource, self::SOCKET_BUFFER_SIZE);
51 25
            $this->throwNetworkSocketExceptionIf($data === false, 'Failed to read data.', true);
52 24
            $isDataEmpty = $data === '';
53 24
            $result      = $picker->pickUpData($data, $this->getRemoteAddress());
54
55 24
            $readContext['countCycles']      += 1;
56 24
            $readContext['isStreamDataEmpty'] = $this->isReadDataActuallyEmpty($data);
57 24
            $this->readAttempts               = $this->resolveReadAttempts($readContext, $this->readAttempts);
58 24
        } while (!$picker->isEof() && !$isDataEmpty);
59
60 24
        return $result;
61
    }
62
63
    /**
64
     * Calculate attempts value
65
     *
66
     * @param array $context Read context
67
     * @param int   $currentAttempts Current attempts counter
68
     *
69
     * @return int
70
     */
71 24
    private function resolveReadAttempts(array $context, $currentAttempts)
72
    {
73 24
        return ($context['countCycles'] === 1 && empty($context['dataBeforeIo'])) ||
74 11
               ($context['countCycles'] > 1   && $context['isStreamDataEmpty']) ?
75 24
            $currentAttempts - 1 :
76 24
            self::READ_ATTEMPTS;
77
78
    }
79
80
    /** {@inheritdoc} */
81 5
    protected function writeRawData($data, $isOutOfBand)
82
    {
83 5
        $resource = $this->socket->getStreamResource();
84 5
        $test     = stream_socket_sendto($resource, '');
85 5
        $this->throwNetworkSocketExceptionIf($test !== 0, 'Failed to send data.', true);
86
87
        $written = $isOutOfBand ?
88 4
            $this->writeOobData($resource, $data) :
89 4
            fwrite($resource, $data, strlen($data));
90
91 4
        $this->throwNetworkSocketExceptionIf($written === false, 'Failed to send data.', true);
92
93 3
        if ($written === 0) {
94 2
            $this->throwExceptionIfNotConnected('Remote connection has been lost.');
95 1
        }
96
97 2
        return $written;
98
    }
99
100
    /** {@inheritdoc} */
101 34
    protected function isConnected()
102
    {
103 34
        return $this->resolveRemoteAddress() !== false;
104
    }
105
106
    /** {@inheritdoc} */
107 24
    protected function getRemoteAddress()
108
    {
109 24
        if ($this->remoteAddress === null) {
110 24
            $this->remoteAddress = $this->resolveRemoteAddress();
111 24
        }
112
113 24
        return $this->remoteAddress;
114
    }
115
116
    /**
117
     * Checks whether data read from stream buffer can be filled later
118
     *
119
     * @param string $data Read data
120
     *
121
     * @return bool
122
     */
123 24
    private function isReadDataActuallyEmpty($data)
124
    {
125 24
        $result = false;
126 24
        if ($data === '') {
127 5
            $dataInSocket = $this->getDataInSocket();
128 5
            $result       = $dataInSocket === '' || $dataInSocket === false;
129 5
        }
130
131 24
        return $result;
132
    }
133
134
    /** {@inheritdoc} */
135 2
    protected function canReachFrame()
136
    {
137 2
        return $this->readAttempts > 0 && $this->isConnected();
138
    }
139
140
    /**
141
     * Return first byte from socket buffer
142
     *
143
     * @return string
144
     */
145 25
    private function getDataInSocket()
146
    {
147 25
        return stream_socket_recvfrom($this->socket->getStreamResource(), 1, STREAM_PEEK);
148
    }
149
150
    /**
151
     * Return remote address if we connected or false otherwise
152
     *
153
     * @return string|bool
154
     */
155 34
    private function resolveRemoteAddress()
156
    {
157 34
        return stream_socket_get_name($this->socket->getStreamResource(), true);
158
    }
159
160
    /**
161
     * Write out-of-band data
162
     *
163
     * @param resource $socket Socket resource
164
     * @param string   $data Data to write
165
     *
166
     * @return int Amount of written bytes
167
     */
168 1
    private function writeOobData($socket, $data)
169
    {
170 1
        $result     = 0;
171 1
        $dataLength = strlen($data);
172 1
        for ($i = 0; $i < $dataLength; $i++) {
173 1
            $written = stream_socket_sendto($socket, $data[$i], STREAM_OOB);
174 1
            $this->throwNetworkSocketExceptionIf($written < 0, 'Failed to send data.', true);
175 1
            if ($written === 0) {
176
                break;
177
            }
178
179 1
            $result += $written;
180 1
        }
181
182 1
        return $result;
183
    }
184
}
185