КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Обмен датаграммами
Как уже говорилось, датаграммы используются в программах довольно редко. В большинстве случаев надёжность передачи критична для приложения, и вместо изобретения собственного надёжного протокола поверх UDP программисты предпочитают использовать TCP. Тем не менее, иногда датаграммы оказываются полезны. Например, их удобно использовать при транслировании звука или видео по сети в реальном времени, особенно при широковещательном транслировании. Поскольку для обмена датаграммами не нужно устанавливать соединение, использовать их гораздо проще. Создав сокет с помощью socket и bind, вы можете тут же использовать его для отправки или получения данных. Для этого вам, как уже говорилось, понадобятся функции sendto и recvfrom. int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen); int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen); Функция sendto очень похожа на send. Два дополнительных параметра to и tolen используются для указания адреса получателя. Для задания адреса используется структура sockaddr, как и в случае с функцией connect. Функция recvfrom работает аналогично recv. Получив очередное сообщение, она записывает его адрес в структуру, на которую ссылается from, а записанное количество байт - в переменную, адресуемую указателем fromlen. Как мы знаем, аналогичным образом работает функция accept. Некоторую путаницу вносят присоединённые датаграммные сокеты (connected datagram sockets). Дело в том, что для сокета с типом SOCK_DGRAM тоже можно вызвать функцию connect, а затем использовать send и recv для обмена данными. Нужно понимать, что никакого соединения при этом не устанавливается. Операционная система просто запоминает адрес, который вы передали функции connect, а затем использует его при отправке данных. Обратите внимание, что присоединённый сокет может получать данные только от сокета, с которым он соединён. Для иллюстрации процесса обмена датаграммами рассмотрим две небольшие программы - sender (листинг 3) и receiver (листинг 4). Первая отправляет сообщения "Hello there!" и "Bye bye!", а вторая получает их и печатает на экране. Программа sender демонстрирует применение как обычного, так и присоединённого сокета, а receiver использует обычный. +++ Листинг 3. Программа sender. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> char msg1[] = "Hello there!\n"; char msg2[] = "Bye bye!\n"; int main() { int sock; struct sockaddr_in addr; sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { perror("socket"); exit(1); } addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sendto(sock, msg1, sizeof(msg1), 0, (struct sockaddr *)&addr, sizeof(addr)); connect(sock, (struct sockaddr *)&addr, sizeof(addr)); send(sock, msg2, sizeof(msg2), 0); close(sock); return 0; }
Листинг 4. Программа receiver. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> int main() { int sock; struct sockaddr_in addr; char buf[1024]; int bytes_read; sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { perror("socket"); exit(1); }
addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); exit(2); } while(1) { bytes_read = recvfrom(sock, buf, 1024, 0, NULL, NULL); buf[bytes_read] = '\0'; printf(buf); }
return 0; }
Использование низкоуровневых сокетов Низкоуровневые сокеты открывают перед вами новые горизонты. Они предоставляют программисту полный контроль над содержимым пакетов, которые отправляются в путешествие по сети. С другой стороны, они сложнее в использовании и обладают плохой переносимостью. Вот почему использовать их следует только в случае необходимости. Например, без них не обойтись при разработке системных утилит типа ping и traceroute. Первым делом выясним, чем низкоуровневые сокеты отличаются от обычных. Работая с обычными сокетами, вы передаёте системе "чистые" данные, а она сама заботится о добавлении к ним необходимых заголовков (а иногда ещё и концевиков). Например, когда вы посылаете сообщение через UDP-сокет, к нему добавляется сначала UDP-заголовок, потом IP-заголовок, а в самом конце - заголовок аппаратного протокола, который используется в вашей локальной сети (например, Ethernet). В результате получается кадр, показанный на рисунке 1.
Рисунок 1 Низкоуровневые сокеты позволяют вам включать в буфер с данными заголовки некоторых протоколов. Например, вы можете включить в ваше сообщение TCP- или UDP-заголовок, предоставив системе сформировать для вас IP-заголовок, а можете вообще сформировать все заголовки самостоятельно. Разумеется, при этом вам придётся изучить работу соответствующих протоколов и строго соблюсти формат их заголовков, иначе программа работать не будет. При работе с низкоуровневыми сокетами вам придётся указывать в третьем параметре функции socket тот протокол, к заголовкам которого вы хотите получить доступ. Константы для основных протоколов Internet объявлены в файле netinet/in.h. Они имеют вид IPPROTO_XXX, где XXX-название протокола: IPPROTO_TCP, IPPROTO_UDP, IPPROTO_RAW (в последнем случае вы получите возможность поработать с "сырым" IP и формировать IP-заголовки вручную). Все числовые данные в заголовках должны записываться в сетевом формате. Поэтому не забывайте использовать функции htons и htonl. Чтобы проиллюстрировать всё это примером, я переписал программу sender из предыдущего раздела с использованием низкоуровневых UDP-сокетов. При этом мне пришлось вручную формировать UDP-заголовок отправляемого сообщения. Я выбрал для примера UDP, потому что у этого протокола заголовок выглядит совсем просто (рисунок 2).
Рисунок 2 Код примера приведён в листинге 5. Хочу обратить ваше внимание на несколько моментов. Во-первых, я не стал задавать номер порта в структуре sockaddr_in. Поскольку этот номер содержится в UDP-заголовке, от поля sin_port уже ничего не зависит. Во-вторых, я записал в качестве контрольной суммы ноль, чтобы не утомлять вас её вычислением. Протокол UDP является ненадёжным по своей природе, поэтому он допускает подобную вольность. Но другие протоколы (например, IP) могут и не допускать. Наконец, обратите внимание, что все данные UDP-заголовка форматируются с использованием htons. Листинг 5. Программа sender с использованием низкоуровневых сокетов. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> struct UdpHeader { u_short src_port; u_short targ_port; u_short length; u_short checksum; }; char message[] = "Hello there!\n"; char msgbuf[1024]; int main() { int sock; struct sockaddr_in addr; struct UdpHeader header; sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); if(sock < 0) { perror("socket"); exit(1); } addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
header.targ_port = htons(3425); header.length = htons(sizeof(header)+sizeof(message)); header.checksum = 0;
memcpy((void *)msgbuf, (void *)&header, sizeof(header)); memcpy((void *)(msgbuf+sizeof(header)), (void *)message, sizeof(message)); sendto(sock, msgbuf, sizeof(header)+sizeof(message), 0, (struct sockaddr *)&addr, sizeof(addr)); close(sock); return 0; }
Дата добавления: 2017-02-01; Просмотров: 82; Нарушение авторских прав?; Мы поможем в написании вашей работы! |