Guide to Network Сетевое программирование от Биджа
Скачать 1.34 Mb.
|
recv() с добавлением пары полей. from это указатель на локальную struct sockaddr_storage, которая будет заполнена IP адресом машины отправителя. fromlen это указатель на локальную целую, которая будет инициализирована из sizeof *from или sizeof(struct sockaddr_storage). Когда функция возвращает управление, fromlen будет содержать действительную длину сохранённого в from. возвращает число принятых байтов или -1 в случае ошибки соответственно установив errno). Теперь вопрос почему мы используем struct sockaddr_storage как тип сокета? Почему не struct sockaddr_in? Потому что, видите ли, мы не хотим привязывать 27 Beej's Guide to Network себя кили. Так что мы используем общую struct которой, как мы знаем, хватает для обеих. (Так… Ещё вопрос почему struct sockaddr самой недостаточно для любого адреса Мы даже приводим struct sockaddr_storage общего назначения к struct sockaddr общего назначения Кажется чуждыми чрезмерным, ха Ответ в том, что она недостаточно велика и изменение её сейчас будет весьма проблематичным. Так что они сделали новую) Помните, если вы подключаете дейтаграммный сокет ом, то можете просто использовать send() и recv() для транзакций. Сам сокет остаётся дейтаграммным и пакеты используют UDP, но интерфейс сокета автоматически добавит информацию об источнике и назначении. 5.9. close() и shutdown() -‐ Прочь сглаз моих! Уф! Выгоняли и recv() сданными весь день и закончили. Вы готовы закрыть соединение на вашем дескрипторе сокета. Это легко. Можно использовать обычную функцию закрытия файлового дескриптора Unix close(): close(sockfd); Это предотвратит дальнейшее чтение и запись в сокет. Любой, попытавшийся читать или писать в этот сокет на удалённом конце получит ошибку. Если вы хотите получить немного больше управления над закрытием сокета, можно использовать функцию shutdown(). Она позволяет разорвать связь в определенном направлении или в обоих (как close()). Пишется так int shutdown(int sockfd, int how); sockfd это файловый дескриптор выключаемого сокета, how принимает следующие значения 0 - Дальнейший приём запрещён 1 - Дальнейшая отправка запрещена 2 - Дальнейшие прими отправка запрещены (как close()) shutdown() возвращает 0 в случае успеха и -1 при ошибке (errno установлен соответственно. Если вы соизволите использовать shutdown() с неподключённым дейтаграммным сокетом, он просто делает его недоступным для последующих вызовов send() и помните, что вы можете их использовать, если вы connect()ите дейтаграммный сокет). Важно отметить, что shutdown() в действительности не закрывает файловый дескриптора только изменяет его использование. Для освобождения дескриптора сокета используйте close(). Вот и всё. Разве что нужно помнить, если вы используете Windows ивам должно вызывать closesocket() вместо close()). 5.10.getpeername() -‐ Кто вы Эта функция очень проста. Так проста, что я чуть было не оставил её без свого раздела. Но всё равно вот она. Функция скажет вам кто находится на другом конце подключённого потокового сокета. Вот запись #include ! int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 28 Beej's Guide to Network Programming sockfd это дескриптор подключённого потокового сокета, addr это указатель на struct sockaddr (или struct sockaddr_in), которая будет содержать информацию о другой стороне соединения и addrlen это указатель на целое, которое должно быть инициализировано sizeof *addr или sizeof(struct sockaddr). В случае ошибки функция возвращает -1 и соответственно устанавливает errno. Как только у вас есть их адрес, вы можете использовать inet_ntop(), getnameinfo() или, чтобы распечатать или получить дополнительную информацию. Нет, их логин вы получить не можете. (Ладно, ладно. Если на другом компьютере выполняется демон ident, это возможно. Однако, это выходит за рамки данного документа. Подробнее см. RFC 1413 ). 20 5.11.gethostname() -‐ Кто Я? Функция gethostname() даже проще, чем getpeername(). Она возвращает имя компьютера, на котором запущена ваша программа. Это имя затем может быть использовано в gethostbyname() для определения IP адреса вашей локальной машины. Что может быть ещё веселее Я подумало многих вещах, но они не относятся к программированию сокетов. В любом случае, вот запись #include ! int gethostname(char *hostname, size_t size); Аргументы просты hostname это указатель на массив символов, который по возвращении из функции будет содержать имя хоста, и size это длина массива hostname в байтах. При успешном завершении функция возвращает 0 ив случае ошибки, errno устанавливается как обычно. 29 http://tools.ietf.org/html/rfc1413 20 Beej's Guide to Network Programming 6. Архитектура Клиент-‐Сервер Это клиент-серверный мир, крошка. Почти всё в сети это разговор клиентского процесса с серверным и наоборот. Возьмём к примеру telnet. Когда вы подключаетесь к удалённому хосту на порт 23 сом (клиент, программа на хосте (именуемая telnetd, сервер) оживает. Она обрабатывает входное telnet подключение, выставляет вам запрос на логин и т.д. Взаимодействие Клиент-Сервер Обмен информацией между клиентом и сервером приведён на диаграмме выше. Заметим, что пара клиент-сервер могут говорить на SOCK_STREAM, SOCK_DGRAM или чём угодно, если они говорят на одном языке. Хорошие примеры пар клиент-сервер это telnet/telnetd, ftp/ftpd или Firefox/Apache. Каждый раз, когда вы используете ftp есть удалённая программа ftpd, которая обслуживает вас. Часто на машине будет работать только один сервер, и этот сервер будет обслуживать множество клиентов используя fork(). Основная программа такова сервер будет ждать подключения, примет его (accept()), и запустит процесс-потомок для его обслуживания (fork()). Именно так работает пример сервера в следующем разделе. 6.1. Простой потоковый сервер Этот сервер просто посылает строку “Hello, World!\n” по потоковому соединению. Вам нужно только запустить его водном окне и telnet-нуть ему из другого вот так $ telnet remotehostname где remotehostname это имя машины на которой выработаете. Серверный код : 21 /* ** server.c -- пример сервера потокового сокета */ ! #include #include #include #include #include #include #include #include #include #include #include #include ! #define PORT "3490" // порт для подключения пользователей ! #define BACKLOG 10 // размер очереди ожидающих подключений 30 http://beej.us/guide/bgnet/examples/server.c 21 Beej's Guide to Network Programming void sigchld_handler(int s) { while(waitpid(-1, NULL, WNOHANG) > 0); } ! // получить sockaddr, IPv4 или IPv6: ! void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } ! return &(((struct sockaddr_in6*)sa)->sin6_addr); } ! int main(void) { int sockfd, new_fd; // слушать на sock_fd, новое подключение на new_fd struct addrinfo hints, *servinfo, *p; struct sockaddr_storage their_addr; // адресная информация подключившегося socklen_t sin_size; struct sigaction sa; int yes=1; char s[INET6_ADDRSTRLEN]; int rv; ! memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // использовать мой IP ! if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } ! // цикл по всем результатами связывание с первым возможным for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("server: socket"); continue; } ! if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } ! if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("server: bind"); continue; } ! break; } ! if (p == NULL) { 31 Beej's Guide to Network Programming fprintf(stderr, "server: failed to bind\n"); return 2; } ! freeaddrinfo(servinfo); // со структурой закончили ! if (listen(sockfd, BACKLOG) == -1) { perror("listen"); exit(1); } ! sa.sa_handler = sigchld_handler; // жатва всех мёртвых процессов sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); } ! printf("server: waiting for connections…\n"); ! while(1) { // главный цикл accept() sin_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); if (new_fd == -1) { perror("accept"); continue; } ! inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s); ! printf("server: got connection from %s\n", s); ! if (!fork()) { // это порождённые процесс close(sockfd); // его слушать ненужно родителю это ненужно На случай, если вы любопытны. Весь мой код расположен водной большой функции main() для синтаксической ясности (мне так кажется. Можете свободно разделить его на меньшие функции если вам от этого станет лучше. И ещё, sigaction() может быть совсем новинкой для вас, это нормально. Этот код здесь отвечает за уборку зомби-процессов, которые появляются при завершении процесса-потомка после fork(). Если вы создадите множество зомби-процессов и не сожнёте их, ваш системный администратор очень разволнуется. Данные от этого сервера можно получить с помощью описанного в следующем разделе клиента. ! 32 Beej's Guide to Network Programming 6.2. Простой потоковый клиент Этот хлопец даже проще, чем сервер. Всё, что этот клиент делает, это подключается к хосту, который вы указываете в командной строке, порт 3490. Он принимает строку, которую высылает сервер. Исходник клиента : 22 /* ** client.c -- пример клиента потокового сокета */ ! #include #include #include #include #include #include #include #include #include ! #include ! #define PORT "3490" // порт для подключения клиентов ! #define MAXDATASIZE 100 // максимальная длина принимаемых зараз данных ! // получить sockaddr, IPv4 или IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } ! return &(((struct sockaddr_in6*)sa)->sin6_addr); } ! int main(int argc, char *argv[]) { int sockfd, numbytes; char buf[MAXDATASIZE]; struct addrinfo hints, *servinfo, *p; int rv; char s[INET6_ADDRSTRLEN]; ! if (argc != 2) { fprintf(stderr,"usage: client hostname\n"); exit(1); } ! memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } ! // цикл по всем результатами связывание с первым возможным 33 http://beej.us/guide/bgnet/examples/client.c 22 Beej's Guide to Network Programming for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("client: socket"); continue; } ! if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("client: connect"); continue; } ! break; } ! if (p == NULL) { fprintf(stderr, "client: failed to connect\n"); return 2; } ! inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s); printf("client: connecting to %s\n", s); ! freeaddrinfo(servinfo); // с этой структурой закончили ! if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) { perror("recv"); exit(1); } ! buf[numbytes] = ’\0’; ! printf("client: received ’%s’\n",buf); ! close(sockfd); ! return 0; } Заметим, что если вы не запустите сервер до клиента, connect() вернёт “Connection refused” (Подключение отвергнуто. Очень полезно. 6.3. Дейтаграммные сокеты Мы уже рассмотрели основы дейтаграммных сокетов UDP обсуждая sendto() и recvfrom() выше, так что я просто представлю пару примеров программ talker.c и listener.c. listener сидит на машине, ожидая входящие пакеты на порт 4950. talker посылает пакет в этот порт на указанной машине, который содержит всё, что пользователь введёт в командной строке. Вот исходник listener.c : 23 /* ** listener.c -- пример сервера дейтаграммного сокета */ ! #include 34 http://beej.us/guide/bgnet/examples/listener.c 23 Beej's Guide to Network Programming #include #include #include #include #include #include #include #include #include ! #define MYPORT "4950" #define MAXBUFLEN 100 ! // порт для подключающихся пользователей // получить sockaddr, IPv4 или IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } ! return &(((struct sockaddr_in6*)sa)->sin6_addr); } ! int main(void) { int sockfd; struct addrinfo hints, *servinfo, *p; int rv; int numbytes; struct sockaddr_storage their_addr; char buf[MAXBUFLEN]; socklen_t addr_len; char s[INET6_ADDRSTRLEN]; ! memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // установить AF_INET для выбора IPv4 hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; // использовать мой IP if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } ! // цикл по всем результатами связывание с первым возможным for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("listener: socket”); continue; } ! if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("listener: bind"); continue; } ! break; } ! 35 Beej's Guide to Network Programming if (p == NULL) { fprintf(stderr, "listener: failed to bind socket\n"); return 2; } ! freeaddrinfo(servinfo); ! printf("listener: waiting to recvfrom…\n"); ! addr_len = sizeof their_addr; if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN - 1 , 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) { perror("recvfrom"); exit(1); } ! printf("listener: got packet from %s\n", inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s)); printf("listener: packet is %d bytes long\n", numbytes); buf[numbytes] = '\0'; printf("listener: packet contains \"%s\"\n", buf); ! close(sockfd); ! return 0; } Отметим, что в нашем вызове getaddrinfo() мы в итоге используем SOCK_DGRAM. Также отметим, что использовать listen() и accept() нет нужды. Этим можно выпендриваться используя дейтаграммные сокеты. Исходник talker.c : 24 /* ** talker.c -- пример дейтаграммного клиента */ ! #include #include #include #include #include #include #include #include #include #include ! #define SERVERPORT “4950" ! // порт для подключающихся пользователей int main(int argc, char *argv[]) { int sockfd; struct addrinfo hints, *servinfo, *p; int rv; int numbytes; ! 36 http://beej.us/guide/bgnet/examples/talker.c 24 Beej's Guide to Network Programming if (argc != 3) { fprintf(stderr,"usage: talker hostname message\n"); exit(1); } ! memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; ! if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } ! // цикл по всем результатами создание сокета for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("talker: socket"); continue; } break; } ! if (p == NULL) { fprintf(stderr, "talker: failed to bind socket\n"); return 2; } if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0, p->ai_addr, p->ai_addrlen)) == -1) { perror("talker: sendto"); exit(1); } ! freeaddrinfo(servinfo); printf("talker: sent %d bytes to %s\n", numbytes, argv[1]); ! close(sockfd); return 0; } И это всё! Запустите listener на одной машине, затем talker на другой. Как они общаются Радости на штуку баксов для всей ядерной семейки В этот раз вам даже ненужно запускать сервер Вы можете запустить один talker ион с удовольствием выстрелит пакеты во тьму, где они исчезнут если никто не ждёт их сна другой стороне. Помните данные, посланные в дейтаграммный UDP сокет необязательно прибывают За исключением одной крохотной детали, о которой ямного разговорил в прошлом подключённые дейтаграммные сокеты. Должет сказать об этом и здесь, поскольку мы в дейтаграммном разделе документа. Ведь |