Московский физико-технический институт
Опубликован: 16.09.2004 | Доступ: свободный | Студентов: 7771 / 1592 | Оценка: 4.44 / 4.28 | Длительность: 15:33:00
Лекция 10:

Семейство протоколов TCP/IP. Сокеты (sockets) в UNIX и основы работы с ними

< Лекция 9 || Лекция 10: 1234567891011

Установление логического соединения. Системный вызов connect()

Среди системных вызовов со стороны клиента появляется только один новый – connect() . Системный вызов connect() при работе с TCP-сокетами служит для установления логического соединения со стороны клиента. Вызов connect() скрывает внутри себя настройку сокета на выбранный системой порт и произвольный сетевой интерфейс (по сути дела, вызов bind() с нулевым номером порта и IP-адресом INADDR_ANY ). Вызов блокируется до тех пор, пока не будет установлено логическое соединение, или пока не пройдет определенный промежуток времени, который может регулироваться системным администратором.

Для установления соединения необходимо задать три параметра: дескриптор активного сокета, через который будет устанавливаться соединение, полный адрес сокета сервера и его длину.

Системный вызов connect()

Прототип системного вызова

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockd, 
    struct sockaddr *servaddr, 
    int addrlen);

Описание системного вызова

Системный вызов connect служит для организации связи клиента с сервером. Чаще всего он используется для установления логического соединения, хотя может быть применен и при связи с помощью датаграмм (connectionless). Данное описание не является полным описанием системного вызова, а предназначено только для использования в нашем курсе. Полную информацию можно найти в UNIX Manual.

Параметр sockd является дескриптором созданного ранее коммуникационного узла, т. е. значением, которое вернул системный вызов socket() .

Параметр servaddr представляет собой адрес структуры, содержащей информацию о полном адресе сокета сервера. Он имеет тип указателя на структуру-шаблон struct sockaddr, которая должна быть конкретизирована в зависимости от используемого семейства протоколов и заполнена перед вызовом.

Параметр addrlen должен содержать фактическую длину структуры, адрес которой передается в качестве второго параметра. Эта длина меняется в зависмости от семейства протоколов и различается даже в пределах одного семейства протоколов (например, для UNIX Domain).

При установлении виртуального соединения системный вызов не возвращается до его установления или до истечения установленного в системе времени – timeout. При использовании его в connectionless связи вызов возвращается немедленно.

Возвращаемое значение

Системный вызов возвращает значение 0 при нормальном завершении и отрицательное значение, если в процессе его выполнения возникла ошибка.

Пример программы TCP-клиента

Рассмотрим пример – программу 15–16-3.с. Это простой TCP-клиент, обращающийся к стандартному системному сервису echo. Стандартный сервис принимает от клиента текстовую датаграмму и, не изменяя ее, отправляет обратно. За сервисом зарезервирован номер порта 7. Заметим, что это порт 7 TCP – не путать с портом 7 UDP из примера в разделе "Пример программы UDP-клиента"! Для правильного запуска программы необходимо указать символьный IP-адрес сетевого интерфейса компьютера, к сервису которого требуется обратиться, в качестве аргумента командной строки, например:

a.out 192.168.253.12

Для того чтобы подчеркнуть, что после установления логического соединения клиент и сервер могут обмениваться информацией неоднократно, клиент трижды запрашивает текст с экрана, отсылает его серверу и печатает полученный ответ. Ниже представлен текст программы.

/* Простой пример TCP-клиента для сервиса echo */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
void main(int argc, char **argv)
{
    int sockfd; /* Дескриптор сокета */
    int n; /* Количество переданных или прочитанных 
        символов */
    int i; /* Счетчик цикла */
    char sendline[1000],recvline[1000]; /* Массивы 
        для отсылаемой и принятой строки */
    struct sockaddr_in servaddr; /* Структура для 
        адреса сервера */
    /* Сначала проверяем наличие второго аргумента в
    командной строке. При его отсутствии прекращаем 
    работу */
    if(argc != 2){
        printf("Usage: a.out <IP address>\n");
        exit(1);
    }
    /* Обнуляем символьные массивы */
    bzero(sendline,1000);
    bzero(recvline,1000);
    /* Создаем TCP сокет */
    if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0){
        perror(NULL); /* Печатаем сообщение об ошибке */
        exit(1);
    }
    /* Заполняем структуру для адреса сервера: семейство 
    протоколов TCP/IP, сетевой интерфейс – из аргумента 
    командной строки, номер порта 7. Поскольку в структуре
    содержится дополнительное не нужное нам поле, 
    которое должно быть нулевым, перед заполнением обнуляем 
    ее всю */
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(51000);
    if(inet_aton(argv[1], &servaddr.sin_addr) == 0){
        printf("Invalid IP address\n");
        close(sockfd);
        exit(1);
    }
    /* Устанавливаем логическое соединение через 
созданный сокет с сокетом сервера, адрес которого мы занесли
в структуру */
    if(connect(sockfd, (struct sockaddr *) &servaddr, 
    sizeof(servaddr)) < 0){
        perror(NULL);
        close(sockfd);
        exit(1);
    }
    /* Три раза в цикле вводим строку с клавиатуры, отправляем 
    ее серверу и читаем полученный ответ */
    for(i=0; i<3; i++){
        printf("String => ");
        fflush(stdin);
        fgets(sendline, 1000, stdin);
        if( (n = write(sockfd, sendline, 
        strlen(sendline)+1)) < 0){
            perror("Can\'t write\n");
            close(sockfd);
            exit(1);
        }
        if ( (n = read(sockfd,recvline, 999)) < 0){
            perror("Can\'t read\n");
            close(sockfd);
            exit(1);
        }
        printf("%s", recvline);
    }
    /* Завершаем соединение */
    close(sockfd);
}
Листинг 15-16.3. Программа 15–16-3.c . Простой пример TCP-клиента для сервиса echo.

Наберите и откомпилируйте программу. Перед запуском "узнайте у своего системного администратора", запущен ли в системе стандартный TCP-сервис echo и, если нет, попросите это сделать. Запустите программу с запросом к сервису своего компьютера, к сервисам других компьютеров. Если в качестве IP-адреса указать несуществующий адрес или адрес выключенной машины, то программа сообщит об ошибке при работе вызова connect() (правда, возможно, придется подождать окончания timeout’а). При задании адреса компьютера, на котором не работает сервис echo, об ошибке станет известно сразу же. Протокол TCP является надежным протоколом. Если логическое соединение установить не удалось, то отправитель будет знать об этом.

Как происходит установление виртуального соединения

Протокол TCP является надежным дуплексным протоколом. С точки зрения пользователя работа через протокол TCP выглядит как обмен информацией через поток данных. Внутри сетевых частей операционных систем поток данных отправителя нарезается на пакеты данных, которые, собственно, путешествуют по сети и на машине-получателе вновь собираются в выходной поток данных. В лекции 4 речь шла о том, каким образом может обеспечиваться надежность передачи информации в средствах связи, использующих в своей основе передачу пакетов данных. В протоколе TCP используются приемы нумерации передаваемых пакетов и контроля порядка их получения, подтверждения о приеме пакета со стороны получателя и насчет контрольных сумм по передаваемой информации. Для правильного порядка получения пакетов получатель должен знать начальный номер первого пакета отправителя. Поскольку связь является дуплексной, и в роли отправителя пакетов данных могут выступать обе взаимодействующие стороны, они до передачи пакетов данных должны обменяться, по крайней мере, информацией об их начальных номерах. Согласование начальных номеров происходит по инициативе клиента при выполнении системного вызова connect() .

Для такого согласования клиент посылает серверу специальный пакет информации, который принято называть SYN (от слова synchronize – синхронизировать). Он содержит, как минимум, начальный номер для пакетов данных, который будет использовать клиент. Сервер должен подтвердить получение пакета SYN от клиента и отправить ему свой пакет SYN с начальным номером для пакетов данных, в виде единого пакета с сегментами SYN и ACK (от слова acknowledgement – подтверждение). В ответ клиент пакетом данных ACK должен подтвердить прием пакета данных от сервера.

Описанная выше процедура, получившая название трехэтапного рукопожатия (three-way handshake), схематично изображена на рисунке 15–16.8. При приеме на машине-сервере пакета SYN, направленного на пассивный (слушающий) сокет, сетевая часть операционной системе создает копию этого сокетаприсоединенный сокет – для последующего общения, отмечая его как сокет с не полностью установленным соединением. После приема от клиента пакета ACK этот сокет переводится в состояние полностью установленного соединения, и тогда он готов к дальнейшей работе с использованием вызовов read() и write().

< Лекция 9 || Лекция 10: 1234567891011
лия логовина
лия логовина

организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2.

Макар Оганесов
Макар Оганесов