Как писать программы на ip-сокетах.
socket() создает точку обмена данными. Возвращает номер сокета.
socket(<домен AF_INET>,<типа сокета (SOCKET_STREAM)>,<номер протокола (= 0)>)
Адрес сокета используется,чтобы клиенты могли найти сервер.
Адрес сокета для IP-сокета описан в специальном man 4 inet
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_len — длина
sin_family — к какому семейству относится
i sin_port — указывает порт
sin_addr— IP-адрес(указываем IP-адрес интерфейса,через который
обращаемся)
sin_zero[8] - равно 0 и зарезервировано для дальнейшего использования. Если
адрес сокета будет меняться в дальнейшем,в этом поле будут доп. данные.
Обнулить sockaddr_in можно и нужно с помощью bzero или memset.
Номер порта — 16-ти битное целое число.
Отступление: порядок байтов при записи числа.
Когда мы передает данные через интернет, проблема в том,что данные на разных компьютерах могут представляться по-разному, поэтому все данные в заголовках IP-пакетов должны иметь стандартную форму или сетевой порядок байтов.
Мы знаем, что для представления числа, большего 255(2^8 — 1 =255 максимальное целое число, записываемое одним байтом) надо использовать несколько байтов. Тогда любое целое число запишется в системе счисления по основанию 256 таким образом:
Н абор чисел A_0 … A_n, каждое из которых равно от 0 до 255, является последовательностью байтов.
Так уж исторически сложилось, что некоторые процессоры (Motorola 68000, SPARC) используют порядок от старшего к младшему или big-endian, то есть запись начинают с A_n, а заканчивают A_0, а некоторые процессоры(x86) используют порядок с точностью наоборот (little-endian). Простенькая схема иллюстрирует то, как выглядит число в памяти при различных порядках записи:
Н е сложно представить, как исказится машинное слово (пусть у нас ЦП 16-ти битный, тогда в слове тоже 16 бит или 2 байта, а передаём мы 2-х байтовое число).
Тогда слово 1|2|3|4 после передачи станет 3|4|1|2, для четырёхбайтовых числе, которые умешаются в машинное слово, если ЦП 32-х битный, всё ещё хуже. Чтобы избежать проблем, надо использовать функции, которые преобразуют число из сетевого порядка в тот, который используется на конечном компьютере и обратно.
Так вот, сетевым порядком называют тот, который аналогичен big-endian.
Для преобразования из сетевого порядка в хостовый для двухбайтных слов используется
Ntohs – 2байтное число из network в host
Htons – 2байтное число из host в network
Ntohl – 4байтное число из network в host
Htonl – 4байтное число из host в network
Прежде,чем записывать данные в структуру, надо использовать именно эти функции!
Ip-адрес тоже структура с одним полем s_addr (это четырхбайтовое целое число в сетевом порядке байтов)
Обычно IP-адрес записывается в виде чисел через точку. Для преобразования используется
inet_aton – ANSI → network
inet_addr делает то же самое
Для обратного преобразования: inet_ntoa
Иногда бывает необходимо,чтобы сервер принимал сообщения с произвольного сетевого интерфейса. Для этого используют константу вместо IP-адрес INADDR_ANY. На самом деле эта константа равна нулю, но для большой читабельности ее записывают так,как записывают. После того, как сокет связан с адресом сокета, мы должны создать очередь запросов с помощью listen(). Дальше с помощью accept() принимать запросы и принимать/передавать данные с помощью read() и write().
На клиентской стороне надо создать сокет, далее connect(), write() для отправления запроса и т д. При передаче данных через интернет, данные сначала записываются в буфер ОС. Может так случиться,что места в буфере окажется меньше,чем write() потребовал записать. В этом случае с помощью write запишут не всю информацию, а ту, которая умещается в буфер. Таким образом,чтобы передать буфер целиком, надо организовать дозапись. Таким образом для передачи одной порции данных может потребоваться несколько вызовов write(). Аналогично при read(). Он не ждет, пока буфер заполнится,а возвращает столько данных, сколько есть, и только если их нету совсем, она переходит в состояние ожидания. То есть за один раз он может прочитать не всю порцию данных, а только часть ее и может потребоваться ещё раз вызвать read чтобы дочитать остаток. Read() никогда не возвращает 0. Если данных новых нету, он просто ждет, пока они появятся. Если read возвращает 0, это значит,что другой конец сокета был закрыт.
ЗАДАЧА: Написать клиент и сервер, которые общаются по протоколу http.
Требования: на стороне клиента точность реализации http не очень важна, надо только, чтобы клиент мог общаться со стандартным http сервером и с нашим самописным http-сервером. А сам сервер должен уметь общаться как со стандартным explorer'oм (или любым другим клиентом) так и с нашим самописным клиентом. У обоих должен быть параметр. У сервера — директория, которой является пресловутая documentroot. У клиента — имя файла, который мы хотим получить от сервера. Каждому из нас выделен индивидуальный порт. Этот порт получен так: взять UID и вычесть 2000. Сервер не должен уметь отдавать произвольный файл с машины, он должен уметь отдавать только те, которые лежат в DocumentRoot. Особенно не стоит отдавать файлы типа
../../etc/passwd
realpath – даёт на входе имя файла в такой форме,как выше,а на выходе — его истинный путь
Сервер должен взять файл, взятый от клиента, потом преобразовать путь к каноническому виду с помощью realpath и потом проверить, совпадает ли начало пути к файлу с documentroot. Соответственно, если совпадает, всё в порядке, а если не совпадает, то клиента надо послать к чёртовой матери.
Полезно использовать strncmp при проверки пути на то, начинается ли он с documentroot.
Рабочие компьютеры
Login: user1
Password: 8U7y6t5r
Сервер
Ip-address for VNC: 172.21.240.251
Ip-address for Putty (new server address): 144.206.250.34