Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
do-aki committed Oct 2, 2013
1 parent f58cc54 commit ada3468
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
vendor/
build/
composer.lock

*.bak
*.old
*~
*.swp

13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: php
php:
- 5.5
- 5.4
- 5.3

before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --dev --no-interaction

script:
- php vendor/bin/phpunit --bootstrap tests/bootstrap.php --coverage-clover build/logs/clover.xml tests && php vendor/bin/coveralls -v

28 changes: 28 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "dooaki/net-empty_port",
"type": "library",
"description": "find a free TCP/UDP port",
"keywords": ["net", "socket", "tcp", "udp"],
"homepage": "https://github.com/do-aki/Net-EmptyPort",
"license": "MIT",
"authors": [
{
"name": "do_aki",
"email": "do.hiroaki@gmail.com",
"homepage": "http://do-aki.net"
}
],
"require": {
"php": ">=5.3.0",
"ext-sockets": "*"
},
"require-dev": {
"phpunit/phpunit": "3.7.*@stable",
"phpunit/php-invoker": ">=1.1.0,<1.2.0",
"satooshi/php-coveralls": "dev-master"
},
"autoload": {
"psr-0": { "do-aki\\Net" : "src" }
}
}

144 changes: 144 additions & 0 deletions src/Net/EmptyPort.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
namespace dooaki\Net;

/**
* 空きポートを探す
* オリジナルは Perl の Test::TCP に含まれる Net::EmptyPort
*
* @author do_aki <do.hiroaki@gmail.com>
*/
class EmptyPort
{

/**
* Dynamic and/or Private Ports (49152-65535) の中で、空いているポートを探す
* 有効なポート番号が指定されている場合は 指定ポート番号から 65535 までを探す
*
* @param integer $port port
* @param string $proto tcp or udp
* @throws \UnexpectedValueException
* @return integer 空いているポート番号
*/
public static function find($port=null, $proto='tcp')
{
if (null === $port || false === $port) {
$port = 50000 + rand(0, 1000);
} else {
$port = intval($port);
if ($port < 0 || 49152 < $port) {
$port = 49152;
}
}
$proto = strtolower($proto);

do {
if (self::isPortUsed($port, $proto)) {
continue;
}

if ($proto === 'tcp') {
$sock = self::_createSocket($proto);
if (@socket_bind($sock, '127.0.0.1', $port) && @socket_listen($sock, 5)) {
socket_close($sock);
return $port;
}
socket_close($sock);
} else {
return $port;
}

} while (++$port <= 65535);

throw new \UnexpectedValueException('empty port not found');
}

/**
* 指定したポートが使われているかどうかを返す
*
* @param integer $port port
* @param string $proto tcp or udp
* @return boolean 使われている場合は true, そうでない場合は false
*/
public static function isPortUsed($port, $proto='tcp')
{
$port = intval($port);
$proto = strtolower($proto);

$sock = self::_createSocket($proto);
if ($proto === 'tcp') {
$ret = @socket_connect($sock, '127.0.0.1', $port);
} else {
$ret = !socket_bind($sock, '127.0.0.1', $port);
}
socket_close($sock);

return $ret;
}

/**
* 指定ポートが接続可能な状態になるまで待つ
*
* @param integer $port port
* @param float $max_wait_sec 最大待ち秒数
* @param string $proto tcp or udp
* @return boolean 時間内に接続可能になった場合は true, そうでない場合は false
*/
public static function wait($port, $max_wait_sec, $proto='tcp')
{
$port = intval($port);
$proto = strtolower($proto);

$waiter = self::_makeWaiter($max_wait_sec * 1000000);

while ($waiter()) {
if (self::isPortUsed($port, $proto)) {
return true;
}
}
return false;
}

/**
* TCP あるいは UDP のソケットを返す
*
* @param string $proto tcp or udp
* @throws \UnexpectedValueException ソケットの生成に失敗した場合
* @return resource socket
*/
private static function _createSocket($proto)
{
$sock = ($proto === 'tcp') ?
socket_create(AF_INET, SOCK_STREAM, SOL_TCP):
socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)
;
if (!$sock) {
throw new \UnexpectedValueException("socket_create() failed: " . socket_strerror(socket_last_error()));
}
return $sock;
}

/**
* 待ちクロージャを返す
*
* @param integer $max_wait 最大待ち時間(マイクロ秒)
* @return closure
*/
private static function _makeWaiter($max_wait)
{
$waited = 0;
$sleep = 1000;

return function () use ($max_wait, &$waited, &$sleep) {
if ($max_wait < $waited) {
return false;
}

usleep($sleep);
$waited += $sleep;
$sleep *= 2;

return true;
};
}
}

33 changes: 33 additions & 0 deletions tests/Net/EmptyPortTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
namespace dooaki\Test\Net;
use dooaki\Net\EmptyPort;

class EmptyPortTest extends \PHPUnit_Framework_TestCase
{

public function test_find_tcp()
{
$this->assertNotEmpty(EmptyPort::find());
}

public function test_find_udp()
{
$this->assertNotEmpty(EmptyPort::find(null, 'udp'));
}

public function test_wait_tcp()
{
$port = EmptyPort::find();

$this->assertFalse(EmptyPort::wait($port, 0.1), 'closed');

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($sock, '127.0.0.1', $port);
socket_listen($sock, 5);

$this->assertTrue(EmptyPort::wait($port, 2), 'open');

socket_close($sock);
}
}

5 changes: 5 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
require dirname(__DIR__) . '/src/Net/EmptyPort.php';
$loader = require dirname(__DIR__) . '/vendor/autoload.php';
$loader->add('dooaki\Test', __DIR__);

0 comments on commit ada3468

Please sign in to comment.