Подтвердить что ты не робот

Многопоточность SCTP

Я разрабатываю это простое клиент-серверное приложение с C, где клиент просто отправляет на сервер случайные данные, и сервер просто слушает то, что отправляет клиент. Протокол, который я использую, - это SCTP, и я заинтересован в том, как реализовать для него многочленную функцию.

Я искал в Интернете информацию о SCTP и многопользовательском режиме и не смог найти примеров того, как SCTP использовать несколько адресов для связи. Мне только удалось найти, какие команды следует использовать при попытке настроить SCTP с помощью многократной записи, и это должно быть довольно просто.

Я создал клиент и сервер, на которых оба компьютера используют два интерфейса WLAN в качестве точек подключения. Оба адаптера подключены к одной и той же точке доступа. Сервер прослушивает данные от клиента с этих интерфейсов, и клиент отправляет данные через них. Проблема в том, что когда я отсоединяю основной адаптер WLAN, клиент отправляет данные, передача просто останавливается, когда он должен отступать к вторичному соединению. Я отслеживал пакеты с Wireshark, а первые пакеты INIT и INIT_ACK сообщают, что как клиент, так и сервер используют адаптеры WLAN в качестве своих линий связи.

Когда я снова подключаю основное WLAN-соединение, передача продолжается через некоторое время и разрывает огромную нагрузку на сервер, который не прав. Пакеты должны были передаваться по вторичному соединению. На многих сайтах сказано, что SCTP автоматически переключается между соединениями, но в моем случае это не происходит. Итак, у вас есть какие-то подсказки, почему передача не отступает от вторичного соединения, когда основная ссылка не работает, даже если клиент и сервер знают друг друга, включая вторичный адрес?

О сервере:

Сервер создает сокет типа SOCK_SEQPACKET и связывает все интерфейсы, найденные с помощью INADDR_ANY. getladdrs сообщает, что сервер ограничен 3 адресами (включая 127.0.0.1). После этого сервер слушает сокет и ждет, когда клиент отправит данные. Сервер считывает данные с помощью вызова sctp_recvmsg.

О клиенте:

Клиент создает также сокет SEQPACKET и подключается к IP-адресу, указанному аргументом командной строки. В этом случае getladdrs возвращает также 3 адреса, как в случае с серверами. После этого клиент только начинает отправлять данные на сервер с одной секундой задержки на сервере, пока пользователь не прерывает отправку с помощью Ctrl-C.

Вот некоторый исходный код:

Сервер:

#define BUFFER_SIZE (1 << 16)
#define PORT 10000   

int sock, ret, flags;
int i;
int addr_count = 0;
char buffer[BUFFER_SIZE];
socklen_t from_len;

struct sockaddr_in addr;
struct sockaddr_in *laddr[10];
struct sockaddr_in *paddrs[10];
struct sctp_sndrcvinfo sinfo;
struct sctp_event_subscribe event;  
struct sctp_prim prim_addr; 
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;

void handle_signal(int signum);

int main(void)
{
    if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
        perror("socket");

    memset(&addr, 0, sizeof(struct sockaddr_in));
    memset((void*)&event, 1, sizeof(struct sctp_event_subscribe));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);

    from_len = (socklen_t)sizeof(struct sockaddr_in);

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(struct sctp_event_subscribe)) < 0)
        perror("setsockopt");

    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))< 0)
        perror("setsockopt");

    if(bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
        perror("bind");

    if(listen(sock, 2) < 0)
        perror("listen");

    addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
    printf("Addresses binded: %d\n", addr_count);

    for(i = 0; i < addr_count; i++)
         printf("Address %d: %s:%d\n", i +1, inet_ntoa((*laddr)[i].sin_addr), (*laddr)[i].sin_port);

    sctp_freeladdrs((struct sockaddr*)*laddr);

    while(1)
    {
        flags = 0;

        ret = sctp_recvmsg(sock, buffer, BUFFER_SIZE, (struct sockaddr*)&addr, &from_len, NULL, &flags);

        if(flags & MSG_NOTIFICATION)
        printf("Notification received from %s:%u\n",  inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

        printf("%d bytes received from %s:%u\n", ret, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));      
    }

    if(close(sock) < 0)
        perror("close");
}   

void handle_signal(int signum)
{
    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }
}

И клиент:

#define PORT 10000
#define MSG_SIZE 1000
#define NUMBER_OF_MESSAGES 1000
#define PPID 1234

int sock;
struct sockaddr_in *paddrs[10];
struct sockaddr_in *laddrs[10];

void handle_signal(int signum);

int main(int argc, char **argv)
{
    int i;
    int counter = 1;
    int ret;
    int addr_count;
    char address[16];
    char buffer[MSG_SIZE];
    sctp_assoc_t id;
    struct sockaddr_in addr;
    struct sctp_status status;
    struct sctp_initmsg initmsg;
    struct sctp_event_subscribe events;
    struct sigaction sig_handler;

    memset((void*)&buffer,  'j', MSG_SIZE);
    memset((void*)&initmsg, 0, sizeof(initmsg));
    memset((void*)&addr,    0, sizeof(struct sockaddr_in));
    memset((void*)&events, 1, sizeof(struct sctp_event_subscribe));

    if(argc != 2 || (inet_addr(argv[1]) == -1))
    {
        puts("Usage: client [IP ADDRESS in form xxx.xxx.xxx.xxx] ");        
        return 0;
    }

    strncpy(address, argv[1], 15);
    address[15] = 0;

    addr.sin_family = AF_INET;
    inet_aton(address, &(addr.sin_addr));
    addr.sin_port = htons(PORT);

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

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
        perror("socket");

    if((setsockopt(sock, SOL_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) != 0)
        perror("setsockopt");

    if((setsockopt(sock, SOL_SCTP, SCTP_EVENTS, (const void *)&events, sizeof(events))) != 0)
        perror("setsockopt");

    if(sendto(sock, buffer, MSG_SIZE, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr)) == -1)
        perror("sendto");

    addr_count = sctp_getpaddrs(sock, 0, (struct sockaddr**)paddrs);
    printf("\nPeer addresses: %d\n", addr_count);

    for(i = 0; i < addr_count; i++)
    printf("Address %d: %s:%d\n", i +1, inet_ntoa((*paddrs)[i].sin_addr), (*paddrs)[i].sin_port);

    sctp_freepaddrs((struct sockaddr*)*paddrs);

    addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddrs);
    printf("\nLocal addresses: %d\n", addr_count);

    for(i = 0; i < addr_count; i++)
    printf("Address %d: %s:%d\n", i +1, inet_ntoa((*laddrs)[i].sin_addr), (*laddrs)[i].sin_port);

    sctp_freeladdrs((struct sockaddr*)*laddrs);

    i = sizeof(status);
    if((ret = getsockopt(sock, SOL_SCTP, SCTP_STATUS, &status, (socklen_t *)&i)) != 0)
        perror("getsockopt");

    printf("\nSCTP Status:\n--------\n");
    printf("assoc id  = %d\n", status.sstat_assoc_id);
    printf("state     = %d\n", status.sstat_state);
    printf("instrms   = %d\n", status.sstat_instrms);
    printf("outstrms  = %d\n--------\n\n", status.sstat_outstrms);

    for(i = 0; i < NUMBER_OF_MESSAGES; i++)
    {
        counter++;
        printf("Sending data chunk #%d...", counter);
        if((ret = sendto(sock, buffer, MSG_SIZE, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr))) == -1)
            perror("sendto");

        printf("Sent %d bytes to peer\n",ret);

        sleep(1);
    }

    if(close(sock) != 0)
        perror("close");
}


void handle_signal(int signum)
{

    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }

}

У вас есть какие-то подсказки, что я делаю неправильно?

4b9b3361

Ответ 1

Хорошо, я наконец решил проблему многопоточности. Вот что я сделал.

Я скорректировал значение сердцебиения до 5000 мс с помощью структуры sctp_paddrparams. Переменная flags, находящаяся в структуре, должна находиться в режиме SPP_HB_ENABLE, потому что иначе SCTP игнорирует значение биения при попытке установить значение с помощью setsockopt().

Именно по этой причине SCTP не посылал сердечные сокращения так часто, как я хотел. Причина, почему я не заметила переменную флага, была устаревшим справочным руководством по SCTP, которое я читал, в котором говорилось, что в структуре не существует переменной flags! Более новая ссылка показала, что это было. Проблема с сердцем решена!

Другое дело - изменить значение rto_max, например, 2000 мс или около того. Опускание значения говорит SCTP об изменении пути намного раньше. Значение по умолчанию было 60 000 мс, которое было слишком высоким (за 1 минуту до начала изменения пути). Значение rto_max может быть скорректировано с помощью структуры sctp_rtoinfo.

С этими двумя модификациями многопользовательский режим начал работать. О, и еще одна вещь. Клиент должен находиться в режиме STREAM, когда сервер находится в режиме SEQPACKET. Клиент отправляет данные на сервер с обычной командой send() и данными чтения сервера с помощью sctp_recvmsg(), где addr struct имеет значение NULL.

Я надеюсь, что эта информация поможет другим парням бороться с многократным запуском SCTP. Приветствую ребята за ваши мнения, это была большая помощь для меня! Вот пример кода, поэтому, возможно, это первый простой пример в сети, если вы спросите меня (не нашли примеров, кроме примеров с несколькими примерами)

Сервер:

#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdlib.h>
#include <pthread.h>

#define BUFFER_SIZE (1 << 16)
#define PORT 10000   

int sock, ret, flags;
int i, reuse = 1;
int addr_count = 0;
char buffer[BUFFER_SIZE];
socklen_t from_len;

struct sockaddr_in addr;
struct sockaddr_in *laddr[10];
struct sockaddr_in *paddrs[10];
struct sctp_sndrcvinfo sinfo;
struct sctp_event_subscribe event;  
struct sctp_prim prim_addr; 
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;
struct sctp_rtoinfo rtoinfo;

void handle_signal(int signum);

int main(void)
{
    if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
        perror("socket");

    memset(&addr,       0, sizeof(struct sockaddr_in));
    memset(&event,      1, sizeof(struct sctp_event_subscribe));
    memset(&heartbeat,  0, sizeof(struct sctp_paddrparams));
    memset(&rtoinfo,    0, sizeof(struct sctp_rtoinfo));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);

    from_len = (socklen_t)sizeof(struct sockaddr_in);

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    heartbeat.spp_flags = SPP_HB_ENABLE;
    heartbeat.spp_hbinterval = 5000;
    heartbeat.spp_pathmaxrxt = 1;

    rtoinfo.srto_max = 2000;

    /*Set Heartbeats*/
    if(setsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS , &heartbeat, sizeof(heartbeat)) != 0)
        perror("setsockopt");

    /*Set rto_max*/
    if(setsockopt(sock, SOL_SCTP, SCTP_RTOINFO , &rtoinfo, sizeof(rtoinfo)) != 0)
        perror("setsockopt");

    /*Set Signal Handler*/
    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    /*Set Events */
    if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(struct sctp_event_subscribe)) < 0)
        perror("setsockopt");

    /*Set the Reuse of Address*/
    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))< 0)
        perror("setsockopt");

    /*Bind the Addresses*/
    if(bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
        perror("bind");

    if(listen(sock, 2) < 0)
        perror("listen");

    /*Get Heartbeat Value*/
    i = (sizeof heartbeat);
    getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, (socklen_t*)&i);
    printf("Heartbeat interval %d\n", heartbeat.spp_hbinterval);

    /*Print Locally Binded Addresses*/
    addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
    printf("Addresses binded: %d\n", addr_count);
    for(i = 0; i < addr_count; i++)
         printf("Address %d: %s:%d\n", i +1, inet_ntoa((*laddr)[i].sin_addr), (*laddr)[i].sin_port);
    sctp_freeladdrs((struct sockaddr*)*laddr);

    while(1)
    {
        flags = 0;

        ret = sctp_recvmsg(sock, buffer, BUFFER_SIZE, NULL, 0, NULL, &flags);

        if(flags & MSG_NOTIFICATION)
        printf("Notification received from %s:%u\n",  inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

        printf("%d bytes received from %s:%u\n", ret, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));      
    }

    if(close(sock) < 0)
        perror("close");
}   

void handle_signal(int signum)
{
    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }
}

Клиент:

#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#define PORT 11000
#define MSG_SIZE 1000
#define NUMBER_OF_MESSAGES 1000

int sock;
struct sockaddr_in *paddrs[5];
struct sockaddr_in *laddrs[5];

void handle_signal(int signum);

int main(int argc, char **argv)
{
    int i;
    int counter = 0;
    int asconf = 1;
    int ret;
    int addr_count;
    char address[16];
    char buffer[MSG_SIZE];
    sctp_assoc_t id;
    struct sockaddr_in addr;
    struct sctp_status status;
    struct sctp_initmsg initmsg;
    struct sctp_event_subscribe events;
    struct sigaction sig_handler;
    struct sctp_paddrparams heartbeat;
    struct sctp_rtoinfo rtoinfo;

    memset(&buffer,     'j', MSG_SIZE);
    memset(&initmsg,    0,   sizeof(struct sctp_initmsg));
    memset(&addr,       0,   sizeof(struct sockaddr_in));
    memset(&events,     1,   sizeof(struct sctp_event_subscribe));
    memset(&status,     0,   sizeof(struct sctp_status));
    memset(&heartbeat,  0,   sizeof(struct sctp_paddrparams));
    memset(&rtoinfo,    0, sizeof(struct sctp_rtoinfo))

    if(argc != 2 || (inet_addr(argv[1]) == -1))
    {
        puts("Usage: client [IP ADDRESS in form xxx.xxx.xxx.xxx] ");        
        return 0;
    }

    strncpy(address, argv[1], 15);
    address[15] = 0;

    addr.sin_family = AF_INET;
    inet_aton(address, &(addr.sin_addr));
    addr.sin_port = htons(PORT);

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

    heartbeat.spp_flags = SPP_HB_ENABLE;
    heartbeat.spp_hbinterval = 5000;
    heartbeat.spp_pathmaxrxt = 1;

    rtoinfo.srto_max = 2000;

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    /*Handle SIGINT in handle_signal Function*/
    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    /*Create the Socket*/
    if((ret = (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP))) < 0)
        perror("socket");

    /*Configure Heartbeats*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS , &heartbeat, sizeof(heartbeat))) != 0)
        perror("setsockopt");

    /*Set rto_max*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_RTOINFO , &rtoinfo, sizeof(rtoinfo))) != 0)
        perror("setsockopt");

    /*Set SCTP Init Message*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) != 0)
        perror("setsockopt");

    /*Enable SCTP Events*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_EVENTS, (void *)&events, sizeof(events))) != 0)
        perror("setsockopt");

    /*Get And Print Heartbeat Interval*/
    i = (sizeof heartbeat);
    getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, (socklen_t*)&i);

    printf("Heartbeat interval %d\n", heartbeat.spp_hbinterval);

    /*Connect to Host*/
    if(((ret = connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)))) < 0)
    {
        perror("connect");
        close(sock);
        exit(0);
    }

    /*Get Peer Addresses*/
    addr_count = sctp_getpaddrs(sock, 0, (struct sockaddr**)paddrs);
    printf("\nPeer addresses: %d\n", addr_count);

    /*Print Out Addresses*/
    for(i = 0; i < addr_count; i++)
        printf("Address %d: %s:%d\n", i +1, inet_ntoa((*paddrs)[i].sin_addr), (*paddrs)[i].sin_port);

    sctp_freepaddrs((struct sockaddr*)*paddrs);

    /*Start to Send Data*/
    for(i = 0; i < NUMBER_OF_MESSAGES; i++)
    {
        counter++;
        printf("Sending data chunk #%d...", counter);

        if((ret = send(sock, buffer, MSG_SIZE, 0)) == -1)
            perror("write");

        printf("Sent %d bytes to peer\n",ret);

        sleep(1);
    }

    if(close(sock) != 0)
        perror("close");
}


void handle_signal(int signum)
{
    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }
}

Ответ 2

Я никогда не пробовал SCTP, но согласно этот сайт поддерживает две модели.

стиль "один-к-одному" и стиль "один-ко-многим".

Насколько я понимаю, многопользовательские работы работают для индивидуального стиля.

Чтобы открыть один-на-один сокет стиля, вам нужен вызов вроде:

connSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );

Обратите внимание, что SOCK_STREAM отличается от SOCK_SEQPACKET

Когда вы делаете

sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)

он, кажется, открывает один-ко-многим сокет стиля, который я не мог понять, если он поддерживает многопоточность или нет.

Итак, попробуйте свой код с параметром SOCK_STREAM.

Также здесь является примером, который использует SOCK_STREAM.

Ответ 3

Клиент открывает ассоциацию с помощью вызова sendto(). Все хорошо. Но после этого вы больше не должны использовать sendto(). Поскольку sendto() будет принудительно заставлять SCTP использовать интерфейс данного IP. Итак, используйте write() или send() вместо sendto():

counter++;
printf("Sending data chunk #%d...", counter);

if((ret = write(sock, buffer, MSG_SIZE) == -1)
    perror("write");