Установка соединения (клиент)
На стороне клиента для установления соединения используется функция connect, которая имеет следующий прототип.
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); |
Здесь sockfd - сокет, который будет использоваться для обмена данными с сервером, serv_addr содержит указатель на структуру с адресом сервера, а addrlen - длину этой структуры. Обычно сокет не требуется предварительно привязывать к локальному адресу, так как функция connect сделает это за вас, подобрав подходящий свободный порт. Вы можете принудительно назначить клиентскому сокету некоторый номер порта, используя bind перед вызовом connect. Делать это следует в случае, когда сервер соединяется с только с клиентами, использующими определённый порт (примерами таких серверов являются rlogind и rshd). В остальных случаях проще и надёжнее предоставить системе выбрать порт за вас.
Обмен данными
После того как соединение установлено, можно начинать обмен данными. Для этого используются функции send и recv. В Unix для работы с сокетами можно использовать также файловые функцииread и write, но они обладают меньшими возможностями, а кроме того не будут работать на других платформах (например, под Windows), поэтому я не рекомендую ими пользоваться.
Функция send используется для отправки данных и имеет следующий прототип.
int send(int sockfd, const void *msg, int len, int flags); |
Здесь sockfd - это, как всегда, дескриптор сокета, через который мы отправляем данные, msg - указатель на буфер с данными, len - длина буфера в байтах, а flags - набор битовых флагов, управляющих работой функции (если флаги не используются, передайте функции 0). Вот некоторые из них (полный список можно найти в документации):
- MSG_OOB. Предписывает отправить данные как срочные (out of band data, OOB). Концепция срочных данных позволяет иметь два параллельных канала данных в одном соединении. Иногда это бывает удобно. Например, Telnet использует срочные данные для передачи команд типа Ctrl+C. В настоящее время использовать их не рекомендуется из-за проблем с совместимостью (существует два разных стандарта их использования, описанные в RFC793 и RFC1122). Безопаснее просто создать для срочных данных отдельное соединение.
- MSG_DONTROUTE. Запрещает маршрутизацию пакетов. Нижележащие транспортные слои могут проигнорировать этот флаг.
Функция send возвращает число байтов, которое на самом деле было отправлено (или -1 в случае ошибки). Это число может быть меньше указанного размера буфера. Если вы хотите отправить весь буфер целиком, вам придётся написать свою функцию и вызывать в ней send, пока все данные не будут отправлены. Она может выглядеть примерно так.
int sendall(int s, char *buf, int len, int flags) { int total = 0; int n; while(total < len) { n = send(s, buf+total, len-total, flags); if(n == -1) { break; } total += n; } return (n==-1 ? -1 : total); } |
Использование sendall ничем не отличается от использования send, но она отправляет весь буфер с данными целиком.
Для чтения данных из сокета используется функция recv.
int recv(int sockfd, void *buf, int len, int flags); |
В целом её использование аналогично send. Она точно так же принимает дескриптор сокета, указатель на буфер и набор флагов. Флаг MSG_OOB используется для приёма срочных данных, аMSG_PEEK позволяет "подсмотреть" данные, полученные от удалённого хоста, не удаляя их из системного буфера (это означает, что при следующем обращении к recv вы получите те же самые данные). Полный список флагов можно найти в документации. По аналогии с send функция recv возвращает количество прочитанных байтов, которое может быть меньше размера буфера. Вы без труда сможете написать собственную функцию recvall, заполняющую буфер целиком. Существует ещё один особый случай, при котором recv возвращает 0. Это означает, что соединение было разорвано.
Закрытие сокета
Закончив обмен данными, закройте сокет с помощью функции close. Это приведёт к разрыву соединения.
#include <unistd.h> int close(int fd); |
Вы также можете запретить передачу данных в каком-то одном направлении, используя shutdown.
int shutdown(int sockfd, int how); |
Параметр how может принимать одно из следующих значений:
- 0 - запретить чтение из сокета
- 1 - запретить запись в сокет
- 2 - запретить и то и другое
Хотя после вызова shutdown с параметром how, равным 2, вы больше не сможете использовать сокет для обмена данными, вам всё равно потребуется вызвать close, чтобы освободить связанные с ним системные ресурсы.
Обработка ошибок
До сих пор я ни слова не сказал об ошибках, которые могут происходить (и часто происходят) в процессе работы с сокетами. Так вот: если что-то пошло не так, все рассмотренные нами функции возвращают -1, записывая в глобальную переменную errno код ошибки. Соответственно, вы можете проанализировать значение этой переменной и предпринять действия по восстановлению нормальной работы программы, не прерывая её выполнения. А можете просто выдать диагностическое сообщение (для этого удобно использовать функцию perror), а затем завершить программу с помощью exit. Именно так я буду поступать в демонстрационных примерах.
Отладка программ
Начинающие программисты часто спрашивают, как можно отлаживать сетевую программу, если под рукой нет сети. Оказывается, можно обойтись и без неё. Достаточно запустить клиента и сервера на одной машине, а затем использовать для соединения адрес интерфейса внутренней петли (loopback interface). В программе ему соответствует константа INADDR_LOOPBACK (не забудьте применять к ней функцию htonl!). Пакеты, направляемые по этому адресу, в сеть не попадают. Вместо этого они передаются стеку протоколов TCP/IP как только что принятые. Таким образом моделируется наличие виртуальной сети, в которой вы можете отлаживать ваши сетевые приложения.
Для простоты я буду использовать в демонстрационных примерах интерфейс внутренней петли.
Эхо-клиент и эхо-сервер
Теперь, когда мы изучили основные функции для работы с сокетами, самое время посмотреть, как они используются на практике. Для этого я написал две небольшие демонстрационные программы. Эхо-клиент посылает сообщение "Hello there!" и выводит на экран ответ сервера. Его код приведён в листинге 1. Эхо-сервер читает всё, что передаёт ему клиент, а затем просто отправляет полученные данные обратно. Его код содержится в листинге 2.
Листинг 1. Эхо-клиент.
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> char message[] = "Hello there!\n"; char buf[sizeof(message)]; int main() { int sock; struct sockaddr_in addr; sock = socket(AF_INET, SOCK_STREAM, 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); if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("connect"); exit(2); } send(sock, message, sizeof(message), 0); recv(sock, buf, sizeof(message), 0); printf(buf); close(sock); return 0; } |
Листинг 2. Эхо-сервер.
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> int main() { int sock, listener; struct sockaddr_in addr; char buf[1024]; int bytes_read; listener = socket(AF_INET, SOCK_STREAM, 0); if(listener < 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(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); exit(2); } listen(listener, 1); while(1) { sock = accept(listener, NULL, NULL); if(sock < 0) { perror("accept"); exit(3); } while(1) { bytes_read = recv(sock, buf, 1024, 0); if(bytes_read <= 0) break; send(sock, buf, bytes_read, 0); } close(sock); } return 0; } |
Обмен датаграммами
Как уже говорилось, датаграммы используются в программах довольно редко. В большинстве случаев надёжность передачи критична для приложения, и вместо изобретения собственного надёжного протокола поверх 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; } |
Вывод
На данный момент, в 2012 году, уже сложно представить себе мир без компьютерных сетей. Большинство современных людей пользуется ими постоянно - с их помощью люди работают, узнают новости, общаются, играют в компьютерные игры и т.д. Для очень многих людей работа в глобальной сети стала площадкой для бизнеса, а создатели социальных сетей, например, Марк Цукерберг или Павел Дуров и вовсе стали одними из самых богатых людей планеты.
Число пользователей как персональных компьютеров, так и людей, постоянно пользующихся Инернетом, с каждым днем становится все больше. По данным социологов, доля россиян, пользующихся интернетом каждый день, за минувший год стремительно увеличилась и составила 38% (данные на апрель 2012 г.). Уже сложно говорить о том, что за сетевыми технологиями будущее, т.к. и в настоящем они уже прочно вошли в нашу жизнь.
Но и развитие технологий не стоит на месте. Все более ясной становится тенденция максимально возможной мобильности пользователя, т.е. человек перестает быть привязан к определенной рабочей станции со всей ее вычислительной мощью и постепенно переходит на более мобильные, но и менее мощные устройства - это нетбуки, смартфоны, планшеты.. И будущее теперь уже за так называемыми «облачными» технологиями - т.е. все вычисления будут проводится не на устройсве пользователя, а на удаленном сервере с соответствующей мощностью и програмным обеспечением, предоставляющем такие услуги.
Список литературы
1. Жеретинцева Н. Н. Курс лекций по компьютерным сетям. - Владивосток,
2000 год. - 81с.
2. Кульгин М.В. Компьютерные сети. - СПб.: Питер, 2003год.: 462с.: ил.
3. Кульгин М.В. Коммутация и маршрутизация IP/IPX трафика. - М.: АйТи,
1998 год.
4. Нанс Б. Компьютерные сети. – М.: БИНОМ, 1996 год.
5. Олифер В.Г. Компьютерные сети: html учебник.
6. Семенов А.Б. Проектирование и расчет скс и их компонентов. - М.: ДМК-издательство, 2003 год. - 410с.
7. http://www.d-link.ru/products/
8. http://www.ecolan.ru/imp_info/introduction/
9. http://www.lanmaster.ru/catalog/