Сокет TCP + UDP в C с использованием дневного протокола

Мой лектор дал нам свой реализованный дневной протокол на C с использованием TCP, и мы должны расширить его до UDP. Он должен принимать только UDP или TCP с использованием флагов cmd -u / -t. Я сделал все до сих пор, но функция sendto() возвращает

"Сервер запущен, прослушивание 0.0.0.0:13... Получено сообщение от 127.0.0.1:35585:

Успешное входящее соединение с 127.0.0.1:35585. Ошибка в функции send_datetime(), строка 319: ОШИБКА В SENDTO(): операция сокета на не-сокете " Я пробовал все, но все еще не могу это исправить, пожалуйста, помогите мне и спасибо :) (я новичок)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>

#define TCP         1
#define UDP         2

#define PROTO       TCP
#define PORT        13
#define BACKLOG     256

#define MSGLENGTH   27

#define XSTR(s)     STR(s)
#define STR(s)      #s

#define ERROR(str)  (fprintf(stderr, "Error in function %s() line %d: ", __FUNCTION__, __LINE__),\
                    perror(str), kill(0, SIGTERM), exit(EXIT_FAILURE))

#define LOG(...)    (printf(__VA_ARGS__), fflush(stdout))

volatile sig_atomic_t   active = 1;

/*
 * struct srvcfg is used to manage all necessary settings for a daytime service
 */
struct srvcfg {
    int                 proto;
    unsigned short      port;
    struct in_addr      server_ip;
    struct sockaddr_in  server_addr;
    int                 sockfd; 
};
/*
 * Declaration of functions
 */
void exiting(int sig);
void set_signal_handler(void);
void print_usage(void);
void parse_arguments(int argc, char *argv[], struct srvcfg *cfg);
void start_server(struct srvcfg *cfg);
int create_socket(int type, struct sockaddr_in *addr);
int sending_datetime(int fd, int port);
void destroy_server(struct srvcfg *cfg);

int main(int argc, char *argv[]) {

    struct srvcfg *cfg;
    struct sockaddr_in client;
    int client_len = sizeof(struct sockaddr_in);
    int fd;

    if ((cfg = malloc(sizeof(struct srvcfg))) == NULL)
        ERROR("Error allocating memory");

    set_signal_handler();
    parse_arguments(argc, argv, cfg);
    start_server(cfg);

    while (active) {
        
        // Sockets are set to non-blocking, therefor errno needs to be checked 
        // for EWOULDBLOCK and/or EAGAIN
        if (cfg->proto == TCP)
        {
            if (((fd = accept(cfg->sockfd, (struct sockaddr *)&client, &client_len)) == -1) 
                && (errno != EWOULDBLOCK) && (errno != EAGAIN))
                ERROR("Error incoming connection");
        }
        else if (cfg->proto == UDP)
        {
            char buffer[MSGLENGTH];

            if ((fd = recvfrom(cfg->sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client, &client_len)) == -1)
            {
                if (errno != EWOULDBLOCK && errno != EAGAIN)
                    ERROR("Error receiving message");
            }
            else
            {
                buffer[fd] = '\0';
                printf("Received message from %s:%d: %s\n", 
                inet_ntoa(client.sin_addr), ntohs(client.sin_port), buffer);
            }
        }

        // Only if we get a socket descriptor from the accept() call, 
        // we continue sending the daytime message
        if (fd != -1) {

            LOG("Successful incoming connection from %s:%d.\n", \
                inet_ntoa(client.sin_addr),ntohs(client.sin_port));

            if (cfg->proto == TCP)
            {
                if (sending_datetime(fd, TCP) == -1)
                    ERROR("Error sending datetime");
            }

            else if (cfg->proto == UDP)
            {
                if (sending_datetime(fd, UDP) == -1)
                    ERROR("Error sending datetime via UDP");
            }

            LOG("Successfully sent datetime to %s:%d.\n", \
                inet_ntoa(client.sin_addr),ntohs(client.sin_port));

            if (close(fd) == -1)
                ERROR("Error on closing socket");

            LOG("Successfully closing connection from %s:%d.\n", \
                inet_ntoa(client.sin_addr),ntohs(client.sin_port));
        }

    }

    destroy_server(cfg);

    free(cfg);

    exit(EXIT_SUCCESS);
}





 * Starting the daytime services by using create_socket() until the socket is in 
 * listen state.
 */ 
void start_server(struct srvcfg *cfg) 
{
    assert(cfg != NULL);

    bzero(&cfg->server_addr, sizeof(cfg->server_addr));
    cfg->server_addr.sin_family = AF_INET;
    cfg->server_addr.sin_port = htons(cfg->port);
    cfg->server_addr.sin_addr.s_addr = cfg->server_ip.s_addr;

    if (cfg->proto == TCP) {
        cfg->sockfd = create_socket(SOCK_STREAM, &cfg->server_addr);
        // We set the socket to non-blocking, which means an accept() call won't block 
        // until a client connection request is received
        fcntl(cfg->sockfd, F_SETFL, fcntl(cfg->sockfd, F_GETFL, 0) | O_NONBLOCK);
    }

    else if (cfg->proto == UDP)
    {
        cfg->sockfd = create_socket(SOCK_DGRAM, &cfg->server_addr);           // UDP Erweiterung
    }

    LOG("Server started, listening on %s:%d ...\n", inet_ntoa(cfg->server_ip), cfg->port);
}

/*
 * Creating a socket in listen state for the daytime service.
 */ 
int create_socket(int type, struct sockaddr_in *addr)
{
    assert(type == SOCK_STREAM || type == SOCK_DGRAM);
    assert(addr != NULL);

    int sockfd;
    int reuseaddr = 1;

    if ((sockfd = socket(PF_INET, type, 0)) == -1)
        ERROR("Error creating socket");

    if ((setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 
                    &reuseaddr, sizeof(reuseaddr))) == -1)
        ERROR("Error setting socket options");

    if ((bind(sockfd, (struct sockaddr *)addr, sizeof(*addr))) == -1)
        ERROR("Error binding socket");

    if (type == SOCK_STREAM) {
        if ((listen(sockfd, BACKLOG)) == -1)
            ERROR("Error setting stream socket into listening mode");
    }
  
    return sockfd;
}

/*
 * Sending a daytime message to the client.
 */ 
int sending_datetime(int fd, int port)
{
    time_t curr_time;
    char buffer[MSGLENGTH];
    struct sockaddr_in client;
    int client_len = sizeof(client);

    /*
     * A daytime message from this server is 26 bytes long, including a closing \r\n.
     * Example: Thu Nov 26 11:29:54 2020\r\n
     */    
    curr_time = time(NULL);
    snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&curr_time));

    if (port == TCP)
    {
        return write(fd, buffer, strlen(buffer));
    }

    else if (port == UDP)
    {  
        int n;
        n = sendto(fd, buffer, strlen(buffer), 0, (struct sockaddr *)&client, client_len);
        if (n < 0)
        {
            ERROR("ERROR IN SENDTO()");   // <----  HERE !!!!***
        }    
        else
            return n;
        
    }

        //sendto(fd, buffer, strlen(buffer), 0, (struct sockaddr *)&client, sizeof(client));
  
}

void destroy_server(struct srvcfg *cfg) 
{
    assert(cfg != NULL);

    if ((close(cfg->sockfd)) == -1)
        ERROR("Error closing socket");
}

Добро пожаловать в СО. Пожалуйста, прочитайте Как задать вопрос и отредактируйте свой вопрос, чтобы показать Минимально воспроизводимый пример. Фокус здесь на минимальном, что означает, что не включайте весь несвязанный код об использовании и т. д., а показывайте только релевантный код.

Gerhardh 17.02.2023 10:37

Хорошо, я уменьшил код

sn1perfl0w 17.02.2023 10:48

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

Gerhardh 17.02.2023 10:59
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
0
3
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
else if (cfg->proto == UDP)
        {
            char buffer[MSGLENGTH];

            if ((fd = recvfrom(cfg->sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client, &client_len)) == -1)
            {
                if (errno != EWOULDBLOCK && errno != EAGAIN)                   

Со страницы руководства:

После успешного завершения recvfrom() возвращает длину сообщение в байтах. Если сообщения недоступны для приема и одноранговый узел выполнил правильное завершение работы, recvfrom() должна вернуть 0. В противном случае функция должна вернуть -1 и установить errno, чтобы указать ошибка.

Recvfrom() не возвращает дескриптор файла. Возвращает длину сообщения в байтах.

Затем последующая операция sendto() пытается записать возвращаемое значение recvfrom(), что приводит к сбою с EBADF: Аргумент сокета не является допустимым файловым дескриптором.


Исправить:

Вам нужно указать дескриптор сокета, к которому привязан сервер, вместо возвращаемого значения recvfrom().

Функция sendto() должна отправить сообщение в режиме соединения. или сокет режима без установления соединения. Если сокет находится в режиме без установления соединения, сообщение должно быть отправлено на адрес, указанный в параметре dest_addr.

Передайте struct srvcfg в sending_daytime() и замените fd на cfg->sock.

int sending_datetime(struct srvcfg *cfg, int fd, int port)
{
    time_t curr_time;
    char buffer[MSGLENGTH];
    struct sockaddr_in client;
    int client_len = sizeof(client);

    /*
     * A daytime message from this server is 26 bytes long, including a closing \r\n.
     * Example: Thu Nov 26 11:29:54 2020\r\n
     */    
    curr_time = time(NULL);
    snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&curr_time));

    if (port == TCP)
    {
        return write(fd, buffer, strlen(buffer));
    }

    else if (port == UDP)
    {  
        int n;
        n = sendto(cfg->sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&client, client_len);
        if (n < 0)
        {
            ERROR("ERROR IN SENDTO()");   
        }    
        else
            return n;
        
    }     
}

Обратите внимание, что time() и ctime() могут выйти из строя. Педантичный код должен проверять их возвращаемые значения.

Спасибо за ваше предложение, в чем разница между cfg->sockfd и fd?

sn1perfl0w 17.02.2023 13:28
cfg->sockfd — это сокет, к которому привязан сервер и который он прослушивает. fd — это новый клиентский сокет, возвращаемый вызовом accept() (при условии TCP-соединения), или длина сообщения в байтах, возвращаемого recvfrom() (при условии, что используется UDP-соединение).
Haris 17.02.2023 13:30

Вы бы сделали какие-либо другие изменения в функции send_datetime()?

sn1perfl0w 17.02.2023 15:49

Я бы проверил возвращаемые значения time() и ctime() и обработал их соответствующим образом, использовал send() вместо write(), обрабатывал send() и sendto() аналогичным образом. Используйте возвращаемое значение snprintf() вместо вызова strlen(). Замените int на ssize_t, чтобы сохранить возвращаемое значение sendto() (это то, что он возвращает). Убедитесь, что send() и sendto() не будут генерировать SIGPIPE или блокировать (я полагаю, вы уже разобрались с аспектом блокировки, или нет?). Вызовите send() в цикле, чтобы убедиться, что все байты отправлены (их может не быть при одном вызове).

Haris 17.02.2023 16:39

Другие вопросы по теме