Мой лектор дал нам свой реализованный дневной протокол на 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");
}
Хорошо, я уменьшил код
Этот обработчик сигналов или этот parse_arguments также не связаны с вашей проблемой. Пожалуйста, уберите все, что не добавляет никакой ценности. Процесс удаления несвязанного кода может уже привести вас к некоторым выводам, которые вы, возможно, упустили из виду.
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?
cfg->sockfd
— это сокет, к которому привязан сервер и который он прослушивает. fd
— это новый клиентский сокет, возвращаемый вызовом accept()
(при условии TCP-соединения), или длина сообщения в байтах, возвращаемого recvfrom()
(при условии, что используется UDP-соединение).
Вы бы сделали какие-либо другие изменения в функции send_datetime()?
Я бы проверил возвращаемые значения time() и ctime() и обработал их соответствующим образом, использовал send() вместо write(), обрабатывал send() и sendto() аналогичным образом. Используйте возвращаемое значение snprintf() вместо вызова strlen(). Замените int на ssize_t, чтобы сохранить возвращаемое значение sendto() (это то, что он возвращает). Убедитесь, что send() и sendto() не будут генерировать SIGPIPE или блокировать (я полагаю, вы уже разобрались с аспектом блокировки, или нет?). Вызовите send() в цикле, чтобы убедиться, что все байты отправлены (их может не быть при одном вызове).
Добро пожаловать в СО. Пожалуйста, прочитайте Как задать вопрос и отредактируйте свой вопрос, чтобы показать Минимально воспроизводимый пример. Фокус здесь на минимальном, что означает, что не включайте весь несвязанный код об использовании и т. д., а показывайте только релевантный код.