понедельник, 27 августа 2012 г.

Социальная Flash-игра — Игровое поле


Этот этап занял некоторое время. Хорошо что нашёлся ещё один пример с открытым кодом от Adam Zwakk, что мне очень помогло:

Создать игровое поле было довольно просто, особенно имея в наличии примеры реализации. Но были и ошибки с которыми я столкнулся как новичок. Кроме того в примерах я не нашёл части нужной функциональности, которую пришлось доделывать самому.

Основной проблемой было ограничить скроллинг игрового поля, и при этом позволить пользователю масштабировать это же поле. Поэтому я сделал отдельный пример с моим решением этой проблемы.

Вот код примера: скачать. А дальше я расскажу об основных препятствиях.


Система координат


Первым делом необходимо понять систему координат. Поначалу не разобравшись как следует сделал лишнюю работу, прежде чем понял что к чему.

Изображение в As3Iso.lib строится следующим образом:
  • IsoView - {x = 0; y = 0} в центре экрана, положительные значения вверх и влево.
  • IsoScene – координат не содержит
  • IsoSprite - имеет доступ к двум системам координат:
    • 1. IsoSprite.container – (спрайт отображающий объекты IsoSprite) {x = 0; y = 0} в центре экрана, но положительные значения вправо и вниз.
    • 2. Изометрические координаты IsoSprite, о которых сейчас речь не идёт.
  • Sprite – {x = 0; y = 0} в центре экрана, положительные значения вправо и вниз.

Масштабирование и скроллинг


Масштабирование и скроллинг по отдельности устроены довольно просто. Но совмещение этих двух вещей с наложением ограничений на скроллинг может стать проблемой.

Чтобы ограничить скроллинг создаётся специальный спрайт, размером превышающий окно игры и прикреплённый к текущему IsoView следующим образом:

pan_limit_sp = new Sprite();
// задаётся размер и положение в пространстве
updatePanLimitSize(); 
view.backgroundContainer.addChild(pan_limit_sp);
 
view.rangeOfMotionTarget = pan_limit_sp;
view.limitRangeOfMotion  = true;

После этого скроллинг возможен до тех пор пока окно игры расположено в пределах «pan_limit_sprite».

При масштабировании «pan_limit_sprite» должен соотвественно менять размер и положение чтобы границы окна игры постоянно находились внутри него, иначе эта система не будет работать.

Полное решение можно посмотреть в коде примера (ссылка на скачивание выше). Масштабирование выполняется колесом мышки. Попробуйте увеличить спрайт до максимума, передвинуть до упора в любую из сторон, затем уменьшить до минимума.

понедельник, 20 августа 2012 г.

PHP - закругление углов у картинки

Давно было интересно как можно в PHP сделать с картинкой что-нибудь такое как указано в заголовке, а тут ещё и задание соответствующее попалось.

Для выполнения задания необходимо было обойтись по возможности штатными средствами PHP, потому что установка чего-то дополнительного — это отдельный процесс. Поэтому я стал искать решения в модуле GD.

Если вам, также как и мне, интересно что значит «GD», то вот ответ:

В GD 1.0, это означало «gif draw». После того как Unisys запатентовало алгоритм LZW сжатия, используемый в GIF и поддержка GIF была убрана, аббревиатура оффицально ничего не обознает, поэтому пусть просто будет «graphics draw» и всё. (поддержка GIF возвращена, благодаря тому что срок действия патента истёк, но GD может гораздо больше чем создание GIF)

Итак, нужно закруглить края картинки причём с прозрачностью. Для этого необходимо воспользоваться дополнительной картинкой такого же размера. Потому что по углам нужно создать области равномерно залитые одним, прозрачным цветом.

На исходной картинке такое сделать нельзя, потому что заливка возможна только либо при создании примитива (заполненный эллипс или заполненный прямоугольник), либо на области равномерно закрашенной каким-либо одним цветом.

Поэтому последовательность действий у меня следующая:
  1. на временной картинке рисую те части, которые должны стать прозрачными в результате;
  1. накладываю на исходное изображение;
  2. по нужным координатам заполняю прозрачным цветом (результат вверху).
Чтобы прозрачность работала, у исходной картинки необходимо установить соответствующий флаг при помощи функции:
imagesavealpha($img_res, true);
, а вот наличие альфа канала у временного изображения не важно (я использовал true color, потому что в этом случае на один вызов функции imagecolortransparent() меньше).

Решение:
class ImgUtils
{
    public static function imageToRes($img_file)
    {
        $img_res = false;
 
        list($tmp, $tmp, $type) = getimagesize($img_file);
 
        switch ($type)
        {
            case 2: // JPEG
 
                $img_res = imagecreatefromjpeg($img_file);
                break;
 
            case 1: // GIF
 
                $img_res = imagecreatefromgif($img_file);
                break;
 
            case 3: // PNG
 
                $img_res = imagecreatefrompng($img_file);
                break;
        }
 
        return $img_res;
    }
 
 
    public static function roundedCorners($img_res, $radius)
    {
        $width  = imagesx($img_res);
        $height = imagesy($img_res);
 
        imagesavealpha($img_res, true);
 
        // corners START
        $img_tmp = imagecreate($width, $height);
 
        $black  = imagecolorallocate($img_tmp, 0, 0, 0);
        $transp = imagecolorallocatealpha($img_tmp,
                                            0, 0, 0, 127);
 
        imagefill($img_tmp, $width / 2, $height / 2, $transp);
 
        // left upper
        imagearc($img_tmp,  $radius / 2 - 1,
                            $radius / 2 - 1,
                            $radius,
                            $radius, 180, 270, $black);
        imagefill($img_tmp, 0, 0, $black);
        // right upper
        imagearc($img_tmp,  $width - $radius / 2,
                            $radius / 2 - 1,
                            $radius,
                            $radius, 270, 0, $black);
        imagefill($img_tmp, $width - 1, 0, $black);
        // right lower
        imagearc($img_tmp,  $width - $radius / 2,
                            $height - $radius / 2,
                            $radius,
                            $radius, 0, 90, $black);
        imagefill($img_tmp, $width - 1, $height - 1, $black);
        // left lower
        imagearc($img_tmp,  $radius / 2 - 1,
                            $height - $radius / 2,
                            $radius,
                            $radius, 90, 180, $black);
        imagefill($img_tmp, 0, $height - 1, $black);
 
        // corners END
 
        imagecopyresampled($img_res, $img_tmp,
                            0, 0, 0, 0,
                            $width, $height, $width, $height);
 
        $transp = imagecolorallocatealpha($img_res,
                                            0, 0, 0, 127);
        imagefill($img_res, 0, 0, $transp);
        imagefill($img_res, $width - 1, 0, $transp);
        imagefill($img_res, $width - 1, $height - 1, $transp);
        imagefill($img_res, 0, $height - 1, $transp);
 
        imagedestroy($img_tmp);
 
        return $img_res;
    }
}
Изначально я пытался нарисовать «уголки» на временном изображении при помощи одного прямоугольника и одного эллипса наложенного поверх. Оказалось что в таких условиях трудно контролировать радиусь углов — тогда я стал рисовать на каждый угол по своей окружности.

Фотографию предоставила Анастасия Мельникова (StacyLV).

вторник, 14 августа 2012 г.

FlashDevelop - опыт использования

Изучая примеры Flash-игр я начал пользоваться FlashDevelop просто потому, что примеры, которыми я распологал, были созданы в нём. Пользуюсь я им уже около года, без всякого предварительного обучения - всё что мне нужно было для работы с AS 3.0-проектами я смог найти методом тыка (тем более, что документация не поражает глубиной описания).

Сейчас я решил подробнее разобраться в возможностях этой программы и написать о своём опыте. Расскажу о том что было полезно или интересно на мой взгляд.

FlashDevelop (FD) – это IDE (Integrated Development Environment) для вэб-разработчиков, с поддержкой ActionScript (AS) 2 и 3 (и не только AS), с автодополнением, компиляцией и отладкой, шаблонами проектов и много чем другим.

Речь пойдёт о FlashDevelop версии 4.0.4 RTM (RTM? Wikipedia подсказывает, что возможно это "release to manufacturing" или "release to marketing").

Общие заметки

После установки FD полностью готов к работе: можно создать AS-проект, скомпилировать и запустить.

По умолчанию для компиляции AS FD использует бесплатную Flex SDK, которая устанавливается вместе с IDE. Это тот вариант, которым я всё время пользовался.

Основные комбинации клавишь, которые я использовал в работе с FD:
  1. F5 – компиляции;
  2. F8 – тестовый запуск;
  3. Shift+F5 - принудительная остановка отладчика;
  4. и, наверное, ещё ESC - закрытие подсказки автодополнения.
На данный момент моей основной задачей с AS было разбираться в устройстве чужих проектов. Поэтому наиболее востребованной функциональностью являлись:
  1. Всплывающие подсказки при наведение на название класса;
  2. Возможность кликнуть правой кнопкой на названии класса и перейти к его определению;
  3. Закладка «Outline» содержащая интерактивное дерево текущего класса + импортируемые и родительский классы.

Автодополнение работает хорошо и почти не мешается. Особенно нравится автдополнение в директиве import: можно написать только название самого класса, а FD уже выдаст список с полными путями.

Особенности FlashDevelop

По-умолчанию все файлы (*.as и .as3proj) открываются в одном и том же окне программы. Если нужно запускать несколько окон FD, то в корневой папке программы нужно создать файл .multi и перезапустить программу. После этого каждый файл, ассоциированный с FD, запускает свою отдельную версию - это учит пользоваться встроенным Project explorer (можно ещё использовать drag`n`drop на нужном окне).

Также при закрытии FD не запоминает проекты и вкладки всех окон, а только последнего.

При отладке тоже есть свой нюанс (но это скорее относится к самому Flash Player, чем к FD). Если при тестовом запуске приложение не может запуститься из-за ошибки, окно Flash Player  вашего приложения остаётся позади всех остальных окон и не появляется в task bar даже после принудительной остановки отладчика (через меню Debug или Shift+F5). Если это окно не найти и не закрыть, то последующие попытки запустить компиляцию приведут к ошибке:

The process cannot access the file 'D:\Dev\Flash\MyProject\bin\ MyProject .swf' because it is being used by another process.

По не знанию может быть трудно догадаться о причине этого сообщения.

Другая функциональность

FD предлагает большое количество настроек:
  1. Расположения окон интерфейса;
  2. Изменения всевозможных цветов (подсветки синтаксиса и фона окон);
  3. Автодополнения и шаблонов классов;
  4. Изменения пунктов главного меню через XML-файлы;
  5. Некоторых комбинаций клавишь.
Также есть поддержка макросов — внутренних команд («запустить программу», «открыть файл», «сообщение плагину» и др.) с набором аргументов («имя файла/программы» и др.).

Помимо этого, существуют ещё и плагины - скрипты использующие синтаксис C#.

У меня потребность в плагинах не возникала, за исключением одного: Apache Ant. Этот плагин выглядит как дополнительная панель и предоставляет графический интерфейс к одноимённой программе.

Дополнительно из интересного:
  1. Встроенный профайлер;
  2. Закладки (Bookmarks) - напоминают точки останова в отладчике, с возможностью просмотра списка закладок и перехода по нему (причём для точек останова, есть похожая панель, только кликать там надо на колонку с номером строки);
  3. Список задач (Task List) — по исходному коду ведётся отслеживание меток (по умолчанию это - TODO, FIXME, BUG). В панели Tasks выводится список найденных меток с указанием файли и строки;
  4. Автоматическое построение документации (интерфейс к ASDoc);
  5. Анализ кода на потенциальные ошибки;
  6. Проверка синтаксиса. Запускается автоматически, когда начинаешь редактировать код, из-за этого FD может «задумываться» на несколько секунд, но это случается изредка;
  7. Просмотр типов проекта (Type Explorer) – дерево всех классов с их открытыми методами. Практически тоже самое, что и закладка Outline только глобально.
Заключение

Изначально я не выбирал какой редактор использовать, а просто взял что попалось первым. Сейчас после года использования, привыкнув к немногим недостаткам, FD мне кажется очень удобным.

Единственная бесплатная альтернатива FD, которую мне удалось найти, и которая не заброшена разработчиками это FDT 5 Free. Нужно попробовать её в работе, тогда будет что сравнить с FD.

понедельник, 6 августа 2012 г.

PHP Sockets - отправка файла

Это продолжение записки "PHP модуль Sockets для HTTP запросов", здесь будет дополнение к ранее написанному и я не буду приводить весь код класса.

Уже при написании HttpSocket мне было интересно узнать, как составить HTTP заголовок для отправки файла. Поэтому я решил разобраться что и как, и дописать эту функциональность.

Как указано в RFC 1867, при отправке файла методом POST, заголовок Content-Type должен принять значение «multipart/form-data». Также Content-Type должен содержать определение boundary:

Content-Type: multipart/form-data; boundary=ABCuniq_idABC

Более того теперь тело запроса разделено на несколько частей начинающихся, разделённых и заканчивающихся строкой указанной в boundary. Каждая часть содержит свой «подзаголовок» и выглядит следующим образом:

--ABCuniq_idABC
Content-Disposition: form-data; name="var_name1"

value1
--ABCuniq_idABC
Content-Disposition: form-data; name="var_name2"

value2
--ABCuniq_idABC
Content-Disposition: form-data; name="img_file"; filename="IMG_20120305_102159.jpg"
Content-Type: image/jpeg

<содержимое файла>
--ABCuniq_idABC--

Обратите внимание, как к строке boundary добавляются символы «--».
Указанная информация о файле, в случае удачной отправки, на принимающей стороне может выглядеть примерно так:

$_FILES = Array(
[img_file] => Array(
[name] => IMG_20120305_102159.jpg
[type] => image/jpeg
[tmp_name] => C:\WINDOWS\Temp\php5DF.tmp
[error] => 0
[size] => 450055
)
)

В дополнение к сделанному, я думал что лучше будет отправлять файл закодированным в base64, чтобы данные не исказились, да и при выводе заголовка он выглядит лучше. Я исследовал такие поля как Content-Encoding, Transfer-Encoding и даже Content-Transfer-Encoding.

Как выяснилось, протокол HTTP принимает двоичные данные (содержимое файла) без проблем и кодирование не нужно. Поля Content-Encoding, Transfer-Encoding нужны соотвественно для сжатия и передачи содержимого по частям, а поле Content-Transfer-Encoding не поддерживается HTTP и используется в почтовых протоколах.

Если с заголовком всё понятно, то написание метода не создаст проблем. Но перед этим, одно «но»: на данный момент в PHP нет надёжного встроенного способа определить MIME-тип произвольного файла. Зато есть простой способ определить MIME-тип изображений. Поэтому я буду отправлять только изображения.

Получившийся у меня код метода класса HttpSocket:

public function reqPostFile($addr,
                            $var_arr = array(),
                            $file_path = null)
{
    $this->parseAddr($addr);
 
    $boundary = '>>============' . uniqid() . '<<';
    $req_body = '';
 
    foreach ($var_arr as $var_name => $var_val)
    {
        $req_body .= '--' . $boundary . NL;
        $req_body .= 'Content-Disposition: form-data;' .
            ' name="' . $var_name . '"' . DNL;
 
        $req_body .= $var_val . NL;
    }
 
    if (!is_null($file_path))
    {
        if (($mime = getimagesize($file_path)) !== false)
        {
            $mime = $mime['mime'];
 
            $req_body .= '--' . $boundary . NL;
            $req_body .= 'Content-Disposition: form-data;' .
                ' name="img_file";' .
                ' filename="' . basename($file_path) . '"' . NL;
 
            $req_body .= 'Content-Type: ' . $mime . DNL;
            $req_body .= file_get_contents($file_path) . NL;
 
        } else {
 
            trigger_error('Mime type of image 
                "' . $file_path . '" can`t be detected',
                E_USER_ERROR);
        }
    }
 
    $req_body .= '--' . $boundary . '--' . NL;
 
 
    $header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL;
    $header .= 'Host: ' . $this->host_str . NL;
    $header .= 'Content-Type: multipart/form-data;' .
        ' boundary=' . $boundary . NL;
 
    $header .= 'Content-Length: ' . strlen($req_body) . NL;
    $header .= 'Connection: Close' . DNL;
 
    $header .= $req_body;
 
    return $this->doQuery($header);
}

понедельник, 30 июля 2012 г.

Smarty и рассылка писем

"Smarty это - движок шаблонов (шаблонизатор) для PHP. Точнее, он предоставляет легко управляемый способ отделения логики и содержания приложения от его внешнего вида." ©документация.

Ещё не встречал случаев, когда в приложениях просто необходимо отделять логику от отображения. Возможно мне не приходилось работать с такими большими задачами. В моей практике я использую Smarty немного иначе, что можно понять из заголовка.

Установка Smarty подробно описана в документации. Только отмечу, что гораздо удобнее использовать продвинутую установку, т.е. создать свой класс, расширяющий класс Smarty и задающий начальные установки:

require_once('Smarty-X.X.XX/libs/Smarty.class.php');


class MySmarty extends Smarty
{
 function __construct()
 {
  parent::__construct();

  $this->template_dir = CWD . '/modules/Smarty-X.X.XX/templates/';
  $this->compile_dir = CWD . '/modules/Smarty-X.X.XX/templates_c/';
  $this->config_dir = CWD . '/modules/Smarty-X.X.XX/configs/';
  $this->cache_dir = CWD . '/modules/Smarty-X.X.XX/cache/';

  //$this->caching  = Smarty::CACHING_OFF;
  //$this->debugging  = true;
  //$this->error_reporting = E_ALL;
 }
}

$Smarty = new MySmarty();

При обычном использовании шаблоны Smarty отображаются функцией display() :

$Smarty->assign('lang_code', 'RU');
$Smarty->assign('curr_date', date('d.m.Y') );

$Smarty->display('test_templ.tpl');

В моём случае была задача введения переменных в тело письма при автоматизированных рассылках с помошью скрипта. Переменные (дата, имя, фамилия и т.д.) вставляются оператором в тексты стандартных писем, и во время самой рассылки переменные в каждом письме должны подменяться соответствующей информацией.

Я решил попробовать использовать Smarty для этой цели. Как оказалось функции display() и fetch() используют понятие ресурс: в первом параметре, перед основным значением можно вставить строку "string: " и тогда остальное значение будет воспринято как текст шаблона вместо пути к файлу. И в сочетании с функцией fetch(), возвращающей результат в виде строки, это всё что нужно для моей задачи.

В этом схематичном примере сначала Smarty переменные в тексте заменяются нужными значениями, затем текст вставляется в подготовленный заранее шаблон с HTML:

set_time_limit(0);

require_once(CWD . '/modules/Smarty.php');

while (true)
{
 $res = db_query('...');

 while ($row = db_fetch($res))
 {
  $Smarty->assign('FirstName',  $row['first_name']);
  $Smarty->assign('LastName',  $row['last_name']);

  $text_ready  = $Smarty->fetch('string: ' . $row['text']);
  $Smarty->assign('text',  $text_ready);

  $mail_body  = $Smarty->fetch(CWD . 'mail/generic_templ.tpl');


  mail($row['to'], $row['subj'], $mail_body);

  db_query('...'); // set mail as sent

  $Smarty->clearAllAssign();
 }
}

Помимо "string: " для работы со строками есть ещё "eval: " отличающийся только тем, что шаблон перекомпилируется каждый раз заново.

понедельник, 23 июля 2012 г.

Социальная Flash-игра — Начало

Уже довольно давно я мечтаю о том, чтобы создать игру. Мною было предпринято несколько попыток, которые так и остались наполовину начатыми. Поэтому теперь в приоритете довести проект до завершения.

(скриншот из игры "Game Dev Story")

Почему игру? Наверное потому что это то что всегда меня привлекало в компьютерах в первую очередь, а также потому что это, вероятно, наиболее сложный тип программы. И вообще, создать даже небольшой мирок, по-моему, очень интересно.

Это будет онлайн-игра для социальных сетей (предположительно ВКонтакте). В качестве платформы я беру Flash, как наиболее распространённую на десктопах.

Сюжет не будет оригинальным и будет напоминать «фермы», потому что сейчас для меня важна техническая сторона и не менее важно довести разработку до завершения.

На первом этапе я хочу выполнить программу-минимум:
  1. игровое поле (скроллинг, зум);
  2. юниты (выделить, направить);
  3. здания (выбрал в меню, построил).
Думаю что потом уже можно будет думать о механике самой игры, хотя допускаю что я ошибаюсь.

Помимо клиентской, есть ещё и серверная часть. Судя по всему PHP для этого не очень подходит, но я буду делать сервер на PHP, потому что хорошо с ним знаком. Позднее, если возникнут проблемы использую что-нибудь другое, например Java или Node.js.

Ах да..., нужно ещё всё красиво нарисовать. По ходу дела буду рисовать (искать арт) сам, потом буду решать этот вопрос отдельно.

Мой опыт с Flash мнимален, поэтому придётся учиться походу дела. В качестве инструментов разработки я выбрал:
  1. as3isolib, просто потому что эта библиотека попалась мне первой, она бесплатна, у неё есть документация и, главное, есть пример с открытым кодом: Anggie Bratadinata's Adobe Camp Jakarta Demo (Thanks Anggie!!!);
  2. FlashDevelop — взятый мною пример использования as3isolib был создан с помощью этого редактора, он бесплатен.
Думаю, что более осознанный выбор инструментов я сейчас не смогу сделать в виду отсутствия опыта.

Установка инструментов проблем не создаёт. Беру демо Anggie и начинаю. Посмотрим что получится.

О каждом пройденном пунтке я буду писать отчёт.

вторник, 17 июля 2012 г.

PHP модуль Sockets для HTTP запросов

По работе мне потребовалось использовать удалённое API для получения информации об IP клиентов. В PHP я нашёл два варианта как можно выполнить запрос на удалённый сервер: Sockets и cURL. Так как с cURL я был уже знаком, решил узнать что такое Sockets.

Насколько я понял - Сокет это просто точка соединения. Есть разные типы Сокетов, наиболее распространённые (и реализованные в PHP) это AF_INET для интернет-соединений и AF_UNIX для коммуникаций между процессами ОС.

Это мой первый опыть работы с Sockets, поэтому наверняка есть какие-то недочёты в моём решении. Плюс к этому - моя задача, на данный момент, ограничена работой с HTTP-запросами: отправить Get или Post, и считать ответ.

Первым делом, я взял один из примеров с документации по PHP и начал работу с него. Принцип работы с этим модулем следующий:

// создание сокета
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

// подключение созданного сокета
socket_connect($socket, $address, $service_port);

// запись в сокет запроса
socket_write($socket, $in, strlen($in));

// считывание ответа фиксированными порциями
while ($out = socket_read($socket, 2048))
{
}

// закрытие
socket_close($socket);

Основная сложность в том, что запрос нужно выполнять с помощью HTTP-заголовка и ответ также приходит с HTTP заголовком, который нужно сначала выделить а потом разобрать нужные поля.

Необходимую информацию о заголовках я нашёл тут:
, и официальный ресурс (которым я почти не воспользовался):

У меня получился следующий класс:

if (!defined('NL'))
{
 define('NL', "\r\n");
 define('DNL', NL . NL);
}


class HttpSocket
{
 private $socket;

 private $host_str;
 private $query_str;
 private $ip_addr;

 private $resp_code;


 public function __construct()
 {
  $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

  if ($this->socket === false)
   trigger_error('socket_create() failed: reason: ' . socket_strerror(socket_last_error()), E_USER_ERROR);
 }


 public function reqGet($addr)
 {
  $this->parseAddr($addr);

  $header = 'GET ' . $this->query_str . ' HTTP/1.1' . NL;
  $header .= 'Host: ' . $this->host_str . NL;
  $header .= 'Connection: Close' . DNL;

  return $this->doQuery($header);
 }


 public function reqPost($addr, $var_arr = array())
 {
  $this->parseAddr($addr);
  $req_str = http_build_query($var_arr);

  $header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL;
  $header .= 'Host: ' . $this->host_str . NL;
  $header .= 'Content-Type: application/x-www-form-urlencoded' . NL;
  $header .= 'Content-Length: ' . strlen($req_str) . NL;
  $header .= 'Connection: Close' . DNL;

  $header .= $req_str;

  return $this->doQuery($header);
 }


 public function getRespCode()
 {
  return $this->resp_code;
 }


 public function __destruct()
 {
  socket_close($this->socket);
 }


 private function parseAddr($addr)
 {
  // http://hostname.com/query/string?v=1
  // http://192.168.241.125/query/string?v=1

  preg_match('/^(?:https?:\/\/)?([^\/]+)(.*)/', $addr, $matches);

  if (filter_var($matches[1], FILTER_VALIDATE_IP))
  {
   $this->ip_addr = $matches[1];

  } else {

   $this->host_str = $matches[1];
   $this->ip_addr = gethostbyname($this->host_str);
  }

  $this->query_str = strlen($matches[2]) ? $matches[2] : '/';
 }


 private function doQuery($header)
 {
  $service_port = getservbyname('www', 'tcp');
  $result   = socket_connect($this->socket, $this->ip_addr, $service_port);

  if ($result === false)
   trigger_error('socket_connect() failed. Reason: (' . $result . ') ' . socket_strerror(socket_last_error($this->socket)), E_USER_ERROR);

  socket_write($this->socket, $header, strlen($header));

  $resp_str = $this->doFetch();
  $resp_bdy = $this->parseResponse($resp_str);

  return $resp_bdy;
 }


 private function doFetch()
 {
  $resp = '';

  while ($out = socket_read($this->socket, 2048))
   $resp .= $out;

  return $resp;
 }


 private function parseResponse($resp_str)
 {
  $pos_cr  = strpos($resp_str, "\n\n");
  $pos_crlf = strpos($resp_str, "\r\n\r\n");

  if ($pos_cr !== false AND $pos_cr < $pos_crlf)
  {
   $sep_pos = $pos_cr;
   $sep_len = 2;

  } else {

   $sep_pos = $pos_crlf;
   $sep_len = 4;
  }

  $head = substr($resp_str, 0, $sep_pos);

  preg_match('/HTTP[^\s]+\s(\d+)/i', $head, $matches);
  $this->resp_code = $matches[1];

  preg_match('/Content-Length:\s(\d+)/i', $head, $matches);
  $length = intval($matches[1]);

  $body = substr($resp_str, $sep_pos + $sep_len, $length);

  return $body;
 }
}

Функции __construct() и __destruct() соответственно создают и закрывают сокет. Функции reqGet() и reqPost() получают URL в качестве аргумента и выполняют соответствующие запросы.

Пару слов о HTTP заголовках:
  1. Строка "Host: ..." сообщает на какой именно домен идёт запрос (на случай если на одном IP несколько доменов).
  2. Строка "Connection: Close" сообщает что соединение не должно считаться постоянным.
Ещё немного напишу о процессе разбора ответа в функции parseResponse():
  1. Сначала нужно отделить заголовок от основного сообщения, для этого ищу двойную новую строку. Это может быть как "\r\n\r\n", так и просто "\n\n".
  2. Далее сохраняю код ответа и выбираю информацию о длинне сообщения (Content-Length).
После этого уже выделяю само сообщение.