Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Интерфейс прикладного программирования Socket API, Часть 5: SCTP

Оригинал: "The Socket API, Part 5: SCTP"
Автор: Pankaj Tanwar
Дата публикации: December 29, 2011
Перевод: Н.Ромоданов
Дата перевода: июль 2012 г.

Начало серии статей о Socket API

В этой статье, посвященной программированию сокетов, мы рассмотрим протокол Stream Control Transmission Protocol (SCTP). Он похож как на протокол TCP, так и на протокол UDP и в нем есть некоторые черты обоих этих протоколов. Он ориентирован на передачу сообщений и гарантирует надежную последовательную их доставку. Протокол SCTP можно использовать сразу в нескольких сетях, т. е. использовать более одного адреса IP с каждой стороны соединения. Поэтому вместо соединения используется ассоциация, т. к. при соединении происходит передача данных между двумя адресами IP, а при ассоциации связь устанавливается между двумя системами, которые могут иметь несколько адресов IP.

Протокол SCTP может поддерживать несколько потоков данных между конечными точками соединения, причем в каждом потоке будет происходить своя собственная надежная последовательная доставка сообщений, так что никакое из потерянныхе сообщение не будет блокировать доставку сообщений в любых других потоках. Протокол SCTP неуязвим для атак типа SYN flooding (переполнение сообщениями SYN), поскольку в нем для установки соединения требуется 4 обмена сообщениями (4-way handshake).

Научную сторону мы обсудим позже, а сейчас перейдем к коду так, как мы обычно это делаем. Но сначала нужно знать типы сокетов протокола SCTP:

  • Сокет типа "один к одному", что соответствует ровно одной ассоциации SCTP (подобно TCP).
  • Сокет типа "один ко многим", когда на сокете могут быть одновременно активны много ассоциаций SCTP (по аналогии с получением датаграмм UDP из нескольких конечных точек).

Сокет типа "один к одному" (также называемый сокетом типа TCP) был разработан для облегчения портирования существующих приложений TCP на SCTP, так что различие между серверами, использующие протоколы TCP и SCTP, не такое уж и большое. Нам просто нужно заменить обращение к функции socket() на обращение к функции socket(AF_INET, SOCK_STREAM и IPPROTO_SCTP), а все остальное остается прежним - вызовы listen(), accept() - для сервера, connect() - для клиента и read() и write() - для обеих программ.

Теперь давайте перейдем к сокету типа "один ко многим" и напишем сервер, использующий несколько потоков, поступающих от клиента.

Во-первых, код для сервера - smtpserver.c:

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
 
 
#define MAX_BUFFER  1024
 
int main()
{
    int sfd, cfd, len, i;
    struct sockaddr_in saddr, caddr;
    struct sctp_initmsg initmsg;
    char buff[INET_ADDRSTRLEN];
    char buffer[MAX_BUFFER+1] = "Message ##\n";
 
 
    sfd = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );
 
    bzero( (void *)&saddr, sizeof(saddr) );
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl( INADDR_ANY );
    saddr.sin_port = htons(29008);
 
    bind( sfd, (struct sockaddr *)&saddr, sizeof(saddr) );
 
/* Maximum of 3 streams will be available per socket */
    memset( &initmsg, 0, sizeof(initmsg) );
    initmsg.sinit_num_ostreams = 3;
    initmsg.sinit_max_instreams = 3;
    initmsg.sinit_max_attempts = 2;
    setsockopt( sfd, IPPROTO_SCTP, SCTP_INITMSG, 
            &initmsg, sizeof(initmsg) );
 
    listen( sfd, 5 );
 
    for(;;) {
        printf("Server Running\n");
 
        len=sizeof(caddr);
        cfd=accept(sfd, (struct sockaddr *)&caddr, &len);
         
        printf("Connected to %s\n",
            inet_ntop(AF_INET, &caddr.sin_addr, buff,
            sizeof(buff)));
 
         
        for(i=0; i< 3; i++) {
/* Changing 9th character the character after # in the message buffer */
            buffer[9] = '1'+i;
 
            sctp_sendmsg( cfd, (void *)buffer, (size_t)strlen(buffer),
                    NULL, 0, 0, 0, i /* stream */, 0, 0 );
            printf("Sent: %s\n", buffer);
        }
 
        close( cfd );
    }
    return 0;
}

А вот код клиента sctpclient.c:

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
 
#define MAX_BUFFER  1024
 
int main(int argc, char **argv)
{
    int cfd, i, flags;
    struct sockaddr_in saddr;
    struct sctp_sndrcvinfo sndrcvinfo;
    struct sctp_event_subscribe events;
    struct sctp_initmsg initmsg;
    char buffer[MAX_BUFFER+1];
 
    if(argc!=2) {
        printf("Usage: %s ipaddress\n", argv[0]);
    return -1;
    }
 
    cfd = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );
 
/* Specify that a maximum of 3 streams will be available per socket */
    memset( &initmsg, 0, sizeof(initmsg) );
    initmsg.sinit_num_ostreams = 3;
    initmsg.sinit_max_instreams = 3;
    initmsg.sinit_max_attempts = 2;
    setsockopt( cfd, IPPROTO_SCTP, SCTP_INITMSG,
            &initmsg, sizeof(initmsg) );
 
    bzero( (void *)&saddr, sizeof(saddr) );
    saddr.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &saddr.sin_addr);
    saddr.sin_port = htons(29008);
     
    connect( cfd, (struct sockaddr *)&saddr, sizeof(saddr) );
 
    memset( (void *)&events, 0, sizeof(events) );
    events.sctp_data_io_event = 1;
    setsockopt( cfd, SOL_SCTP, SCTP_EVENTS,
        (const void *)&events, sizeof(events) );
 
     
/* Sending three messages on different streams */
 
    for (i=0; i<3; i++) {
        bzero( (void *)&buffer, sizeof(buffer) );
 
        sctp_recvmsg( cfd, (void *)buffer, sizeof(buffer),
                        (struct sockaddr *)NULL, 0, &sndrcvinfo, &flags );
 
        printf("Received following data on stream %d\n\n%s\n",
            sndrcvinfo.sinfo_stream, buffer);
 
    }
 
    close(cfd);
 
    return 0;
}

Сервер посылает три сообщения в трех различных потоках, а клиент просто принимает сообщения и выдает их на экран (рис. 1 и 2).

Рис.1: Данные, выдаваемые сервером

Рис.2: Данные, выдаваемые клиентом

Различие лишь в том, что мы создаем итеративный сервер, похожий на сервер для UDP, но здесь мы должны использовать вызов accept(). Клиент делает обратное - получает сообщения, идущие от сервера к клиенту. Теперь давайте попробуем разобраться в функциях, которые мы использовали:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/sctp.h>
 
int sctp_sendmsg (int sd, const void *msg, size_t len,
    struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t
    flags, uint16_t stream_no, uint32_t timetolive, uint32_t context);

Мы пользуемся этой функцией для отправки сообщений из сокета когда используем расширенные возможности протокола SCTP. Первый аргумент в функции - sd, дескриптор сокета, из которого посылается сообщение msg, имеющее длину len. В четвертом аргументе указывается адрес назначения - tolen задает длину адреса, а stream_no определяет номер потока, в который посылается данное собщение. Параметр flags используется для передачи некоторых настроек принимающей стороне. Вы можете посмотреть страницы руководства для sctp_sendmsg().

В параметре timetolive указывется время в миллисекундах, после которого сообщение можно не отправлять, если оно еще не отправлено; ноль указывает, что тайм-аут не установлен. Context является значением, которое в случае, если при отправке сообщения произошла ошибка, передается на верхний уровень вместе с недоставленным сообщением. В случае успеха функция возвращает количество отправленных байтов, или -1 - в случае ошибки.

Далее идет функция stcp_recvmsg():

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/sctp.h>
 
int sctp_recvmsg(int sd, void * msg, size_t len, struct
    sockaddr * from, socklen_t * fromlen, struct sctp_sndrcvinfo
    * sinfo, int * msg_flags);

Эта функция выполняет действия, обратные действию функции sctp_sendmsg, и используется для получения сообщений. Параметры аналогичны. Сокет sd принимает сообщение msg, имеющее длину len, из адреса *from, длина которого *fromlen, а *sinfo является указателем на адрес, который будет заполнен при получении сообщения. mag_flags является указателем на целое с флагами, похожими на MSG_NOTIFICATION или MSG_EOR. Функция возвращает количество полученных байтов или -1 - при ошибке.

#include <sys/types.h>
#include <sys/socket.h>
 
int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

Эта функция используется для установки параметров сокета sockfd. Следующим аргументом является тот уровень level, на котором используется устанавливаемый параметр. Чтобы манипулировать параметрами на уровне сокетов API, значение level должно быть определено как SOL_SOCKET. Параметр optname и все остальные указываемые параметры передаются в неинтерпретируемом виде в соответствующий модуль протокола для их последующей там интерпретации. Параметры level и optname определены в sys/sockets.h. Аргументы optval и optlen используются для доступа к значениям параметров в setsockopt(), которые хранятся в структуре. Параметры устанавливаются на сервере следующим образом:

initmsg.sinit_num_ostreams = 3;
initmsg.sinit_max_instreams = 3;
initmsg.sinit_max_attempts = 2;

Здесь в первых двух строках указывается, что нам доступны три входных и три выходных потока, а максимальное количество делаемых попыток будет равно двум. Те же самые параметры задаются в клиентской программе. Другие параметры устанавливаются для конкретных событий. Эта структура будет заполняться тогда , как будет возникать событие "сообщение принято", и наша программа будет получать уведомление. Противоположной ей функцией будет функция getsockopts()(смотрите подсказку в справочных страницах). Остальная часть кода проста для понимания.

Теперь скомпилируем и запустим программу; убедитесь, что вы установили пакет sctp-tools, т.к. вам нужен заголовок тsctp.h из netinet/. Для компиляции используйте команду gcc sctpserver.c -lsctp -o server && gcc sctpclient.c -lsctp -o client; а для запуска используйте следующий код:

$ ./server &
$  ./client

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