Как ни странно, но многие разработчики до сих пор хранят ip адрес в базе плейн текстом, что вобщем то совсем непрактично.
На самом деле адрес IPv4 удобно хранить в виде беззнакового int 32. И в этой кратенькой статье я вам расскажу о том, как это делать и какие неоспоримые преимущества это дает. Я думаю, этот материал будет полезен многим.
UPD: Написал схожую статью для IPv6, так же советую прочесть: Работа c IPv6 в PHP и MySQL
Работа в PHP
Для начала рассмотрим способ преобразования ip в int. В PHP есть функция ip2long:
ip2long — Конвертирует строку, содержащую (IPv4) Интернет адрес с точками в допустимый адрес
Возвращает IPv4 адрес или FALSE, если параметр ip_address содержит ошибку.
То есть возвращает unsigned 32 int. Это то что нам подходит. Но появляется вот какой нюанс, в зависимости от того какой разрядности у нас система, эта функция будет выдавать разные результаты. Вот, к примеру, на 64 битной:
ip2long(0.0.0.0); //0
ip2long(127.255.255.255); //2147483647
ip2long(128.0.0.0); //2147483648
ip2long(255.255.255.255); //4294967295
Собственно, все верно. Так и должно быть. Но вот на 32 разрядной системе это будет выглядеть иначе:
ip2long(0.0.0.0); //0
ip2long(127.255.255.255); //2147483647
ip2long(128.0.0.0); //-2147483648
ip2long(255.255.255.255); //-1
Этот момент, кстати, отмечен в документации:
Ввиду того, что PHP тип integer является знаковым, и на 32-битных системах большое количество IP адресов будет представлено в виде отрицательных чисел, необходимо использовать «%u» в функции sprintf() или printf() для получения IP адреса в строковом беззнаковом виде.
Посему, эту функцию, при работе с базой данных, следует использовать так:
$ip = sprintf('%u', ip2long($ip));
Кстати о базе данных. Для поля в котором вы будете хранить IP адрес, не забывайте указывать UNSIGNED, иначе все ваши труды по правильному представлению числа в самом PHP пойдут насмарку.
Обратное преобразование выполняется с помощью функции long2ip. С ней, я думаю, вы сможете разобраться самостоятельно.
Работа в SQL
В MySQL есть две встроенные функции
INET_ATON(expr) — Given the dotted-quad representation of an IPv4 network address as a string, returns an integer that represents the numeric value of the address in network byte order (big endian). INET_ATON() returns NULL if it does not understand its argument.
INET_NTOA(expr) — Given a numeric IPv4 network address in network byte order, returns the dotted-quad representation of the address as a binary string. INET_NTOA() returns NULL if it does not understand its argument.
Запоминаются просто — INET_ATON — INET_Adress_TO_Number — Адрес_В_Число, и INET_NTOA — INET_Number_TO_Adress — Число_В_Адрес.
То есть можно не заморачиваться с приведением адреса IP в скриптах, а возложить эту обязанность на БД. Так например:
INSERT .... VALUES(..., INET_ATON('255.255.255.255'), ...)
SELECT .... WHERE ip = INET_ATON('255.255.255.255')
Но самая крутая возможность — это операции поиска по диапазонам и маскам.
Задача: нам нужно выдернуть все записи находящиеся в диапазоне 158.192.12.0 — 158.192.74.255. Если бы вы хранили IP плейнтекстом, то вам бы пришлось реализовывать это с помощью регулярных выражений, и подобный запрос выполнялся бы очень долго.
Но в нашем случае, все очень просто:
SELECT .... WHERE ip BETWEEN INET_ATON('158.192.12.0') AND INET_ATON('158.192.94.255')
И если вы используете индекс по данному полю, то результат вернется практически моментально.
Или надо получить адрес подсети по маске:
SELECT ..., (ip & INET_ATON('255.255.255.0')), ...
Вобщем все что вашей душе угодно :)
Подведем итог
Чем же все таки этот метод лучше:
- в базе данных теперь будет хранится 4 байта INT вместо 15 байт VARCHAR;
- бОльшая скорость при использовании операций выборки по ip адресу;
- простой поиск по диапазону;
- битовые операции с IP адресами.
Минусы:
- перевод формы представления адреса из строки в число и обратно;
- отсутствие человеко-понятного отображения значения при просмотре значений через, к примеру, phpMyAdmin.
Кстати в phpMyAdmin, по крайней мере в последних версиях, есть возможность отображать такие значения в понятном виде автоматически (см. здесь).
все-таки 4 байта (http://dev.mysql.com/doc/refman/5.1/en/integer-types.html), но все равно короче, удобнее и правильнее, чем текстом :)
Ой… затупил, сорри)
А еще интом не запишешь ipv6 адрес.
Да, не запишешь. Кстати сказать, в ближайшее время планирую написать статью по поводу работы с IPV6 средствами PHP.
В любом случаее надо соблаюдать тип полей при вставке IP в базу: либo INT (для +/-), либо UNSIGNED INT (только +).
А еще можно переложить операции по переводу адресов на сам SQL.
Если я собираюсь использовать INET_ATON и INET_NTOA — какое поле мне надо создать в MySQL (int, long?), как его настроить, какой размер задать?
обычный int, и не заморачивайтесь ;)
У меня INT не прокатил — только BIGINT, INT циферок не хватает
UNSIGNED INT
Как может в int цыфер не хватать рухнуть чтоли?
Лонги по 10 цыфер а в int 11 влазит