Socket   B
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 299
Duplicated Lines 5.69 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 42
c 2
b 1
f 0
lcom 1
cbo 7
dl 17
loc 299
rs 8.295

15 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 6 17 5
B connect() 0 42 6
A disconnect() 0 11 2
A send() 0 15 4
C recv() 11 60 15
A getSocketBufferSize() 0 4 1
A getLastError() 0 4 1
A setIp() 0 4 1
A getIp() 0 4 1
A isIPv6() 0 4 1
A setPort() 0 4 1
A getPort() 0 4 1
A setType() 0 4 1
A getType() 0 4 1
A isConnected() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Socket often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Socket, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of Dedipanel project
5
 *
6
 * (c) 2010-2015 Dedipanel <http://www.dedicated-panel.net>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace DP\GameServer\GameServerBundle\Socket;
13
14
use DP\GameServer\GameServerBundle\Socket\Packet;
15
use DP\GameServer\GameServerBundle\Socket\Exception\CreateSocketException;
16
use DP\GameServer\GameServerBundle\Socket\Exception\ConnectionFailedException;
17
use DP\GameServer\GameServerBundle\Socket\Exception\NotConnectedException;
18
use DP\GameServer\GameServerBundle\Socket\Exception\SendDataException;
19
use DP\GameServer\GameServerBundle\Socket\Exception\RecvDataException;
20
use DP\GameServer\GameServerBundle\Socket\Exception\RecvTimeoutException;
21
22
/**
23
 * @author Albin Kerouanton 
24
 * 
25
 * @todo ajout support IPv6
26
 * @todo ajout callbacks pre/postSend, preRecv & del 1 callback postRecv
27
 */
28
class Socket
29
{
30
    private $ip;
31
    private $port;
32
    private $type;
33
    private $timeout;
34
    private $socket;
35
    private $connected;
36
    
37
    private $callbacks;
38
    
39
    const MTU = 1400;
40
41
    const MULTI_DETECTOR = 'isMultiResp';
42
    const MULTI_RECEIVER = 'recvMultiResp';
43
    
44
    /**
45
     * Constructor
46
     * 
47
     * @param string $ip
48
     * @param int $port
49
     * @param string $type (tcp/udp)
50
     * @param array $timeout (0 => timeout sec, 1 => timeout usec)
51
     * @param array $callbacks Array of callbacks
52
     */
53
    public function __construct(
54
        $ip, $port, $type, array $timeout, $callbacks = null)
55
    {
56
        $this->ip = $ip;
57
        $this->port = $port;
58
        $this->type = $type;
59
        $this->timeout = $timeout;
60
        $this->connected = false;
61
        $this->callbacks = $callbacks;
62
        
63 View Code Duplication
        if (isset($callbacks[self::MULTI_DETECTOR]) && is_callable($callbacks[self::MULTI_DETECTOR])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
64
            $this->callbacks[self::MULTI_DETECTOR] = $callbacks[self::MULTI_DETECTOR];
65
        }
66 View Code Duplication
        if (isset($callbacks[self::MULTI_RECEIVER]) && is_callable($callbacks[self::MULTI_RECEIVER])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
            $this->callbacks[self::MULTI_RECEIVER] = $callbacks[self::MULTI_RECEIVER];
68
        }
69
    }
70
    
71
    /**
72
     * Connect to the server
73
     * 
74
     * @throws CreateSocketException
75
     * @throws ConnectionFailedException 
76
     */
77
    public function connect()
78
    {
79
        $domain = AF_INET;
80
        if ($this->isIPv6()) $domain = AF_INET6;
81
        
82
        if ($this->type == 'tcp') {
83
            $type = SOCK_STREAM;
84
            $proto = SOL_TCP;
85
        }
86
        elseif ($this->type == 'udp') {
87
            $type = SOCK_DGRAM;
88
            $proto = SOL_UDP;
89
        }
90
        else {
91
            $type = SOCK_RAW;
92
            $proto = SOL_ICMP;
93
        
94
        }
95
        
96
        $this->socket = socket_create($domain, $type, $proto);
97
        
98
        if ($this->socket === false) {
99
            throw new CreateSocketException($type, $this->getLastError());
100
        }
101
        
102
        // On défini la socket comme étant bloquante
103
        // Et on défini le timeout
104
        socket_set_block($this->socket);
105
        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $this->timeout[0], 'usec' => $this->timeout[1]));
106
        socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $this->timeout[0], 'usec' => $this->timeout[1]));
107
               
108
        $connect = @socket_connect($this->socket, $this->ip, $this->port);
109
        
110
        if (!$connect) {
111
            throw new ConnectionFailedException($this->getLastError());
112
        }
113
        else {
114
            $this->connected = true;
115
        }
116
        
117
        return true;
118
    }
119
    
120
    /**
121
     * Disconnect the socket  
122
     */
123
    public function disconnect()
124
    {
125
        if (!$this->connected) {
126
            throw new NotConnectedException('The socket is already disconnected.');
127
        }
128
        
129
        $this->connected = false;
130
        
131
        socket_close($this->socket);
132
        unset($this->socket);
133
    }
134
    
135
    /**
136
     * Send a packet
137
     * 
138
     * @param Packet $packet
139
     * 
140
     * @return integer Data length sended 
141
     * 
142
     * @throws NotConnectedException
143
     * @throws SendDataException 
144
     */
145
    public function send(Packet $packet)
146
    {
147
        if (!$this->connected) {
148
            throw new NotConnectedException('Can\'t send data when the socket is disconnected.');
149
        }
150
        
151
        $len = $packet->getLength();
152
        $send = socket_send($this->socket, $packet, $len, 0);
153
        
154
        if ($send === null || $send != $len) {
155
            throw new SendDataException($this->getLastError());
156
        }
157
        
158
        return $send;
159
    }
160
    
161
    /**
162
     * Receive a packet
163
     * 
164
     * @param bool $multiPacket
165
     * @param double $packetLength
166
     * @return \DP\GameServer\GameServerBundle\Socket\Packet
167
     * @throws NotConnectedException
168
     * @throws RecvTimeoutException
169
     * @throws RecvDataException 
170
     */
171
    public function recv($multiPacket = true, $packetLength = Socket::MTU)
172
    {
173
        if (!$this->connected) {
174
            throw new NotConnectedException('Can\'t recv data when the socket is disconnected.');
175
        }
176
        
177
        // On souhaite uniquement lire la socket actuelle
178
        $read = array($this->socket);
179
        $write = array();
180
        $except = array();
181
        
182
        // On attend la modif de celle-ci jusqu'au timeout (en sec et usec)
183
        // socket_select renvoie le nombre de socket modifiés
184
        // N'ayant passé qu'une socket, si celle-ci est modifié
185
        // C'est que des données sont arrivés.
186
        $select = socket_select($read, $write, $except, $this->timeout[0], $this->timeout[1]);
187
        
188
        // select() renvoie toujours les sockets udp (puisqu'elles sont connectionless)
189
        // On doit donc vérifier que l'on reçoit bien des données
190
        // S'il a bien des données d'arrivés, on les récupères
191
        // Et on exécute les 2 callbacks de post réception si nécessaire (et si possible)
192
        // Ceux-ci servant à traiter les cas de réception multi-packets (notamment pour l'UDP)
193
        if ($select == 1) {
194
            if ($packetLength > Socket::MTU) $packetLength = Socket::MTU;
195
            
196
            $content = @socket_read($this->socket, $packetLength, PHP_BINARY_READ);
197
            
198 View Code Duplication
            if ($this->type == 'udp' && $content == null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
                $this->connected = false;
200
                throw new RecvTimeoutException($this->getLastError());
201
            }
202
            
203
            $read = new Packet($content);
204
205
            if ($multiPacket && is_array($this->callbacks) && !empty($this->callbacks)) {
206
                if (count($this->callbacks) == 2 
207
                && is_callable($this->callbacks[self::MULTI_DETECTOR]) 
208
                && is_callable($this->callbacks[self::MULTI_RECEIVER])) {
209
                    $isMultiPacketResp = call_user_func($this->callbacks[self::MULTI_DETECTOR], $read);
210
                    
211
                    if ($isMultiPacketResp) {
212
                        $read = call_user_func($this->callbacks[self::MULTI_RECEIVER], $read, $this);
213
                    }
214
                }
215
            }
216
            
217
            return $read;
218
        }
219 View Code Duplication
        elseif ($select === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
            if ($this->type == 'udp') {
221
                $this->connected = false;
222
            }
223
            
224
            throw new RecvTimeoutException($this->getLastError());
225
        }
226
        else {
227
            $this->connected = false;
228
            throw new RecvDataException($this->getLastError());
229
        }
230
    }
231
    
232
    /**
233
     * Get socket buffer size
234
     * @return int 
235
     */
236
    public function getSocketBufferSize()
237
    {
238
        return socket_get_option($this->socket, SOL_SOCKET, SO_RCVBUF);
239
    }
240
    
241
    /**
242
     * Get the last error message
243
     * @return string 
244
     */
245
    private function getLastError()
246
    {
247
        return socket_strerror(socket_last_error());
248
    }
249
    
250
    /**
251
     * Set IP
252
     * 
253
     * @param string $ip 
254
     */
255
    public function setIp($ip)
256
    {
257
        $this->ip = $ip;
258
    }   
259
    /**
260
     * Get IP
261
     * 
262
     * @return string
263
     */
264
    public function getIp()
265
    {
266
        return $this->ip;
267
    }
268
    /**
269
     * Verify if IP is v6
270
     * 
271
     * @todo Add IPv6 support
272
     * @return bool
273
     */
274
    public function isIPv6()
275
    {
276
        return false;
277
    }
278
    
279
    /**
280
     * Set port
281
     * 
282
     * @param int $port 
283
     */
284
    public function setPort($port)
285
    {
286
        $this->port = $port;
287
    }  
288
    /**
289
     * Get port
290
     * 
291
     * @return int 
292
     */
293
    public function getPort()
294
    {
295
        return $this->port;
296
    }
297
    
298
    /**
299
     * Set type (tcp or udp)
300
     * 
301
     * @param int $type 
302
     */
303
    public function setType($type)
304
    {
305
        $this->type = $type;
306
    }   
307
    /**
308
     * Get type (tcp or udp)
309
     * 
310
     * @return int 
311
     */
312
    public function getType()
313
    {
314
        return $this->type;
315
    }
316
    
317
    /**
318
     * Get socket status (connected or not)
319
     * 
320
     * @return bool
321
     */
322
    public function isConnected()
323
    {
324
        return $this->connected;
325
    }
326
}
327