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

AbstractClientIo::write()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 7
Bugs 0 Features 0
Metric Value
c 7
b 0
f 0
dl 0
loc 28
ccs 18
cts 18
cp 1
rs 8.439
cc 6
eloc 17
nc 6
nop 2
crap 6
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\Exception\ConnectionException;
13
use AsyncSockets\Exception\FrameException;
14
use AsyncSockets\Exception\NetworkSocketException;
15
use AsyncSockets\Exception\UnsupportedOperationException;
16
use AsyncSockets\Frame\FramePickerInterface;
17
use AsyncSockets\Frame\PartialFrame;
18
use AsyncSockets\Socket\SocketInterface;
19
20
/**
21
 * Class AbstractClientIo
22
 */
23
abstract class AbstractClientIo extends AbstractIo
24
{
25
    /**
26
     * Disconnected state
27
     */
28
    const STATE_DISCONNECTED = 0;
29
30
    /**
31
     * Connected state
32
     */
33
    const STATE_CONNECTED = 1;
34
35
    /**
36
     * Unhandled portion of data at the end of frame
37
     *
38
     * @var string
39
     */
40
    private $unhandledData = '';
41
42
    /**
43
     * Socket state
44
     *
45
     * @var int
46
     */
47
    private $state = self::STATE_DISCONNECTED;
48
49
    /**
50
     * Amount of attempts to write data before treat request as failed
51
     *
52
     * @var int
53
     */
54
    private $writeAttempts = self::IO_ATTEMPTS;
55
56
    /**
57
     * Maximum amount of OOB data length (0 - not supported)
58
     *
59
     * @var int
60
     */
61
    private $maxOobPacketLength;
62
63
    /**
64
     * AbstractClientIo constructor.
65
     *
66
     * @param SocketInterface $socket Socket object
67
     * @param int             $maxOobPacketLength Maximum amount of OOB data length (0 - not supported)
68
     */
69 103
    public function __construct(SocketInterface $socket, $maxOobPacketLength)
70
    {
71 103
        parent::__construct($socket);
72 103
        $this->maxOobPacketLength = $maxOobPacketLength;
73 103
    }
74
75
    /**
76
     * Read raw data from network into given picker
77
     *
78
     * @param FramePickerInterface $picker Frame picker
79
     *
80
     * @return string Data after end of frame
81
     */
82
    abstract protected function readRawDataIntoPicker(FramePickerInterface $picker);
83
84
    /**
85
     * Write data to socket
86
     *
87
     * @param string $data Data to write
88
     * @param bool $isOutOfBand Flag if data are out of band
89
     *
90
     * @return int Number of written bytes
91
     */
92
    abstract protected function writeRawData($data, $isOutOfBand);
93
94
    /**
95
     * Check whether given socket resource is connected
96
     *
97
     * @return bool
98
     */
99
    abstract protected function isConnected();
100
101
    /**
102
     * Return true if frame can be collected in nearest future, false otherwise
103
     *
104
     * @return bool
105
     */
106
    abstract protected function canReachFrame();
107
108
    /**
109
     * Return remote socket ip address
110
     *
111
     * @return string
112
     */
113
    abstract protected function getRemoteAddress();
114
115
    /** {@inheritdoc} */
116 46
    final public function read(FramePickerInterface $picker)
117
    {
118 46
        $this->setConnectedState();
119 40
        $isEndOfFrameReached = $this->handleUnreadData($picker);
120 40
        if (!$isEndOfFrameReached) {
121 40
            $this->unhandledData = $this->readRawDataIntoPicker($picker);
122
123 39
            $isEndOfFrameReached = $picker->isEof();
124 39
            if (!$isEndOfFrameReached && !$this->canReachFrame()) {
125 5
                throw new FrameException($picker, $this->socket, 'Failed to receive desired frame.');
126
            }
127 35
        }
128
129 35
        $frame = $picker->createFrame();
130 35
        if (!$isEndOfFrameReached) {
131 2
            $frame = new PartialFrame($frame);
132 2
        }
133
134 35
        return $frame;
135
    }
136
137
    /** {@inheritdoc} */
138 42
    final public function write($data, $isOutOfBand)
139
    {
140 42
        $this->setConnectedState();
141 26
        if ($isOutOfBand) {
142 13
            if ($this->maxOobPacketLength === 0) {
143 8
                throw UnsupportedOperationException::oobDataUnsupported($this->socket);
144
            }
145
            
146 5
            $dataLength = strlen($data);
147 5
            if ($dataLength > $this->maxOobPacketLength) {
148 4
                throw UnsupportedOperationException::oobDataPackageSizeExceeded(
149 4
                    $this->socket,
150 4
                    $this->maxOobPacketLength,
151
                    $dataLength
152 4
                );
153
            }
154 1
        }
155
156 14
        $result              = $this->writeRawData($data, $isOutOfBand);
157 7
        $this->writeAttempts = $result > 0 ? self::IO_ATTEMPTS : $this->writeAttempts - 1;
158
159 7
        $this->throwNetworkSocketExceptionIf(
160 7
            !$this->writeAttempts && $result !== strlen($data),
161
            'Failed to send data.'
162 7
        );
163
164 7
        return $result;
165
    }
166
167
    /**
168
     * Verify, that we are in connected state
169
     *
170
     * @return void
171
     * @throws ConnectionException
172
     */
173 88
    private function setConnectedState()
174
    {
175 88
        $resource = $this->socket->getStreamResource();
176 88
        if (!is_resource($resource)) {
177 20
            $message = $this->state === self::STATE_CONNECTED ?
178 20
                'Connection was unexpectedly closed.' :
179 20
                'Can not start io operation on uninitialized socket.';
180 20
            throw new ConnectionException($this->socket, $message);
181
        }
182
183 68
        if ($this->state !== self::STATE_CONNECTED) {
184 68
            if (!$this->isConnected()) {
185 2
                throw new ConnectionException($this->socket, 'Connection refused.');
186
            }
187
188 66
            $this->state = self::STATE_CONNECTED;
189 66
        }
190 66
    }
191
192
    /**
193
     * Checks that we are in connected state
194
     *
195
     * @param string $message Message to pass in exception
196
     *
197
     * @return void
198
     * @throws NetworkSocketException
199
     */
200 2
    final protected function throwExceptionIfNotConnected($message)
201
    {
202 2
        $this->throwNetworkSocketExceptionIf(
203 2
            !$this->isConnected(),
204
            $message
205 2
        );
206 1
    }
207
208
    /**
209
     * Read unhandled data if there is something from the previous operation
210
     *
211
     * @param FramePickerInterface $picker Frame picker to use
212
     *
213
     * @return bool Flag whether it is the end of the frame
214
     */
215 40
    private function handleUnreadData(FramePickerInterface $picker)
216
    {
217 40
        if ($this->unhandledData) {
218 9
            $this->unhandledData = $picker->pickUpData($this->unhandledData, $this->getRemoteAddress());
219 9
        }
220
221 40
        return $picker->isEof();
222
    }
223
}
224