diff --git a/.gitignore b/.gitignore index 9d1f796..b202a83 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ a.out client server +*.o diff --git a/Makefile b/Makefile index a8a7eeb..df72073 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,18 @@ -.PHONY: build-server -build-server: - gcc -Wall -Wextra -Wpedantic server.c -o server +CC = gcc +CFLAGS = -Wall -Wextra -Wpedantic -O2 -.PHONY: run-server -run-server: build-server - ./server +.PHONY: all clean -.PHONY: build-client -build-client: - gcc -Wall -Wextra -Wpedantic client.c -o client +all: server client -.PHONY: run-client -run-client:build-client - ./client +server: server.o utils.o + $(CC) $(CFLAGS) -o server server.o utils.o + +client: client.o utils.o + $(CC) $(CFLAGS) -o client client.o utils.o + +%.o: %.c + $(CC) $(CFLAGS) -c $< + +clean: + rm -f server client *.o diff --git a/client.c b/client.c index cafc637..2be325c 100644 --- a/client.c +++ b/client.c @@ -1,52 +1,196 @@ #include -#include +#include #include #include #include #include #include +#include "utils.h" + +#define BUFFER_SIZE 256 + +int create_connected_socket(const char *ip, const char *port); + int main(int argc, char **argv) { - int sock; - if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1) + // Check if the IP address and port number are provided + if (argc != 3) { - perror("client: socket()"); + printf("Usage: %s \n", argv[0]); exit(1); } - struct sockaddr_in server_addr; - server_addr.sin_family = PF_INET; - server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - server_addr.sin_port = htons(8080); - if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) + // Create a connected socket + int socket_fd; + if ((socket_fd = create_connected_socket(argv[1], argv[2])) == -1) { - perror("client: connect()"); - close(sock); + puts("Failed to create a connected socket\n"); exit(1); } - char buf[256]; - fgets(buf, sizeof(buf), stdin); // Null-terminate the string + while (1) + { + char buf[BUFFER_SIZE]; + if (fgets(buf, BUFFER_SIZE, stdin) == NULL) + { + // Check if fgets() reached EOF + if (feof(stdin)) + { + puts("EOF detected\n"); + break; + } + else + { + perror("client: fgets()"); + close_with_retry(socket_fd); + exit(1); + } + } - size_t len = strlen(buf); - if (send(sock, buf, len, 0) == -1) + // Send the input to the server + size_t bytes_read = strlen(buf); + size_t total_sent = 0; + while (total_sent < bytes_read) + { + ssize_t sent_bytes = send(socket_fd, buf + total_sent, bytes_read - total_sent, 0); + if (sent_bytes == -1) + { + if (errno == EINTR) + { + continue; + } + perror("client: send()"); + close_with_retry(socket_fd); + exit(1); + } + total_sent += sent_bytes; + } + + // Receive the response from the server + size_t total_received = 0; + while (total_received < bytes_read) + { + ssize_t received_bytes = recv(socket_fd, buf + total_received, bytes_read - total_received, 0); + if (received_bytes == -1) + { + if (errno == EINTR) + { + continue; + } + perror("client: recv()"); + close_with_retry(socket_fd); + exit(1); + } + else if (received_bytes == 0) + { + puts("Connection closed by server\n"); + close_with_retry(socket_fd); + exit(1); + } + total_received += received_bytes; + } + + // Print the received data + if (fwrite(buf, 1, total_received, stdout) != total_received) + { + perror("client: fwrite()"); + close_with_retry(socket_fd); + exit(1); + } + } + + close_with_retry(socket_fd); + + return 0; +} + +/** + * @brief Create a connected socket to the specified IP address and port. + * + * This function creates a socket, connects it to the specified IP address and port, + * and returns the connected socket file descriptor. + * + * @param[in] ip The IP address to connect to. + * @param[in] port The port number to connect to. + * @return The file descriptor of the connected socket. + * @retval -1 An error occurred during the process. + */ +int create_connected_socket(const char *ip, const char *port_str) +{ + // Parse the IP address + struct sockaddr_in server_addr4; + struct sockaddr_in6 server_addr6; + memset(&server_addr4, 0, sizeof(server_addr4)); + memset(&server_addr6, 0, sizeof(server_addr6)); + int is_ipv4 = inet_pton(PF_INET, ip, &server_addr4.sin_addr); + int is_ipv6 = inet_pton(PF_INET6, ip, &server_addr6.sin6_addr); + if (is_ipv4 <= 0 && is_ipv6 <= 0) { - perror("client: send()"); - close(sock); - exit(1); + printf("Invalid IP address: %s\n", ip); + return -1; } - if (recv(sock, buf, sizeof(buf) - 1, 0) == -1) + // Parse the port number + int port; + if ((port = parse_port(port_str)) == -1) { - perror("client: recv()"); - close(sock); - exit(1); + printf("Invalid port number: %s\n", port_str); + return -1; } - printf("%s", buf); + // Create a socket and connect to the server + int socket_fd; + if (is_ipv4 == 1) + { + if ((socket_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) + { + perror("client: socket()"); + return -1; + } - close(sock); + server_addr4.sin_family = PF_INET; + server_addr4.sin_port = htons(port); - return 0; + while (connect(socket_fd, (struct sockaddr *)&server_addr4, sizeof(server_addr4)) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("client: connect() using IPv4"); + close_with_retry(socket_fd); + return -1; + } + } + else if (is_ipv6 == 1) + { + if ((socket_fd = socket(PF_INET6, SOCK_STREAM, 0)) == -1) + { + perror("client: socket() using IPv6"); + return -1; + } + + server_addr6.sin6_family = PF_INET6; + server_addr6.sin6_port = htons(port); + + while (connect(socket_fd, (struct sockaddr *)&server_addr6, sizeof(server_addr6)) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("client: connect() using IPv6"); + close_with_retry(socket_fd); + return -1; + } + } + else + { + puts("Reached the unreachable"); + return -1; + } + + printf("Connected to %s, %d\n", ip, port); + return socket_fd; } diff --git a/server.c b/server.c index c935a19..df54b25 100644 --- a/server.c +++ b/server.c @@ -1,74 +1,565 @@ -#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include #include +#include "utils.h" + +#define BUFFER_SIZE 256 +#define MAX_LISTENS 20 +#define MAX_CLIENTS 30 +#define MAX_EVENTS 10 + +int pipe_fds[2]; + +int create_listen_sockets(const char *port_str, SocketManager *listen_socket_manager); +int add_listen_sockets_to_epoll(int epoll_fd, SocketManager *listen_socket_manager); +int add_pipe_to_epoll(int epoll_fd); +int handle_new_connection(int listen_socket_fd, int epoll_fd, SocketManager *client_socket_manager); +int handle_client(SocketData *client_socket_data, struct epoll_event event); +void handle_sigint(int sig); + int main(int argc, char **argv) { - int listen_sock; - if ((listen_sock = socket(PF_INET, SOCK_STREAM, 0)) == -1) + // Declare variables that need to be cleaned up later + int epoll_fd = -1; + SocketManager listen_socket_manager; + SocketManager client_socket_manager; + int exit_code = 0; + + // Check if the port number is provided + if (argc != 2) { - perror("server: socket()"); - exit(1); + printf("Usage: %s \n", argv[0]); + exit_code = 1; + goto cleanup; } - struct sockaddr_in server_addr; - server_addr.sin_family = PF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(8080); - if (bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) + // Parse the port number + if (parse_port(argv[1]) == -1) { - perror("server: bind()"); - close(listen_sock); - exit(1); + printf("Invalid port number: %s\n", argv[1]); + exit_code = 1; + goto cleanup; } - if (listen(listen_sock, 0) == -1) + // Create listen sockets + init_socket_manager(&listen_socket_manager, MAX_LISTENS); + if (create_listen_sockets(argv[1], &listen_socket_manager) == -1) { - perror("server: listen()"); - close(listen_sock); - exit(1); + puts("Failed to create listen sockets\n"); + exit_code = 1; + goto cleanup; + } + + printf("Listening on port %s\n", argv[1]); + + // Create an epoll instance + if ((epoll_fd = epoll_create1(0)) == -1) + { + perror("server: epoll_create1()"); + exit_code = 1; + goto cleanup; } + // Add the listen sockets to the epoll instance + if (add_listen_sockets_to_epoll(epoll_fd, &listen_socket_manager) == -1) + { + puts("Failed to add listen sockets to epoll\n"); + exit_code = 1; + goto cleanup; + } + + // Create a pipe for signal handling + if (pipe(pipe_fds) == -1) + { + perror("server: pipe()"); + exit_code = 1; + goto cleanup; + } + + // Add the pipe to the epoll instance + if (add_pipe_to_epoll(epoll_fd) == -1) + { + puts("Failed to add pipe to epoll\n"); + exit_code = 1; + goto cleanup; + } + + puts("Monitoring for events..."); + + struct epoll_event events[MAX_EVENTS]; + init_socket_manager(&client_socket_manager, MAX_CLIENTS); while (1) { - unsigned int client_len; - struct sockaddr_in client_addr; - int conn_sock; - if ((conn_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len)) == -1) + // Wait for events indefinitely + int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); + if (nfds == -1) { - perror("server: accept()"); - close(listen_sock); - exit(1); + if (errno == EINTR) + { + continue; + } + perror("server: epoll_wait()"); + exit_code = 1; + goto cleanup; } - char buf[256]; - ssize_t received_bytes; - while ((received_bytes = recv(conn_sock, buf, sizeof(buf) - 1, 0)) > 0) + for (int i = 0; i < nfds; i++) { - buf[received_bytes] = '\0'; // Null-terminate the string - if (send(conn_sock, buf, received_bytes, 0) == -1) + SocketData *socket_data; + // Check if the event is for the listen socket + if ((socket_data = find_socket(&listen_socket_manager, events[i].data.fd)) != NULL) { - perror("server: send()"); - close(conn_sock); - close(listen_sock); - exit(1); + if ((handle_new_connection(socket_data->socket_fd, epoll_fd, &client_socket_manager)) == -1) + { + exit_code = 1; + goto cleanup; + } + } + // Check if the event is for a client socket + else if ((socket_data = find_socket(&client_socket_manager, events[i].data.fd)) != NULL) + { + if (handle_client(socket_data, events[i]) <= 0) + { + // The server itself does not exit even if the processing with the client ends abnormally. + close_with_retry(socket_data->socket_fd); + remove_socket(&client_socket_manager, socket_data->socket_fd); + } + } + // Check if the event is for the pipe + else if (events[i].data.fd == pipe_fds[0]) + { + int sig; + while (read(pipe_fds[0], &sig, sizeof(sig)) == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + { + continue; + } + perror("server: read()"); + exit_code = 1; + goto cleanup; + } + + if (sig == SIGINT) + { + puts("Received SIGINT, exiting..."); + goto cleanup; + } } } + } + +cleanup: + if (epoll_fd != -1) + { + close_with_retry(epoll_fd); + } + close_all_sockets(&client_socket_manager); + close_all_sockets(&listen_socket_manager); + close_with_retry(pipe_fds[0]); + close_with_retry(pipe_fds[1]); + + if (exit_code != 0) + { + exit(exit_code); + } + return 0; +} - if (received_bytes == -1) +/** + * @brief Create and bind listening sockets for the server. + * + * This function creates and binds listening sockets for both IPv4 and IPv6, sets the sockets + * to reuse the address, and adds them to the provided socket manager. + * + * @param[in] port_str The port number as a string. + * @param[in] listen_socket_manager The socket manager for listening sockets. + * @return The status of the socket creation and binding process. + * @retval 0 The sockets were successfully created and bound. + * @retval -1 An error occurred during the process. + */ +int create_listen_sockets(const char *port_str, SocketManager *listen_socket_manager) +{ + struct addrinfo hints, *res, *res0; + int yes = 1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; // Allow IPv4 or IPv6 + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // For wildcard IP address + + int getaddrinfo_result; + if ((getaddrinfo_result = getaddrinfo(NULL, port_str, &hints, &res0)) != 0) + { + printf("server: getaddrinfo(): %s(%d)", gai_strerror(getaddrinfo_result), getaddrinfo_result); + return -1; + } + + for (res = res0; res != NULL; res = res->ai_next) + { + int listen_socket_fd; + if ((listen_socket_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { - perror("server: recv()"); - close(conn_sock); - close(listen_sock); - exit(1); + perror("server: socket()"); + continue; + } + + // Set the socket option to reuse the address + if (setsockopt(listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { + perror("server: setsockopt(SOL_SOCKET, SO_REUSEADDR)"); + close_with_retry(listen_socket_fd); + continue; + } + + if (res->ai_family == PF_INET6) + { + // Set the socket option to allow only IPv6 connections + if (setsockopt(listen_socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(int)) == -1) + { + perror("server: setsockopt(IPPROTO_IPV6, IPV6_V6ONLY)"); + close_with_retry(listen_socket_fd); + continue; + }; + } + + if (bind(listen_socket_fd, res->ai_addr, res->ai_addrlen) == -1) + { + perror("server: bind()"); + close_with_retry(listen_socket_fd); + continue; + } + + if (listen(listen_socket_fd, 5) == -1) + { + perror("server: listen()"); + close_with_retry(listen_socket_fd); + return -1; } - close(conn_sock); + add_socket(listen_socket_manager, listen_socket_fd); } - close(listen_sock); + freeaddrinfo(res0); + + if (listen_socket_manager->top == listen_socket_manager->max_size - 1) + { + puts("server: failed to bind any sockets\n"); + return -1; + } + + return 0; +} + +/** + * @brief Add the listening sockets to the epoll instance. + * + * This function sets the listening sockets to non-blocking mode and adds them to the epoll instance. + * + * @param[in] epoll_fd The file descriptor of the epoll instance. + * @param[in] listen_socket_manager The socket manager for listening sockets. + * @return The status of the process. + * @retval 0 The sockets were successfully added to the epoll instance. + * @retval -1 An error occurred during the process. + */ +int add_listen_sockets_to_epoll(int epoll_fd, SocketManager *listen_socket_manager) +{ + struct epoll_event event; + for (int i = 0; i < listen_socket_manager->max_size; i++) + { + int listen_socket_fd = listen_socket_manager->sockets[i].socket_fd; + if (listen_socket_fd == -1) + { + continue; + } + + // Get the current flags for the listen socket + int flags; + while ((flags = fcntl(listen_socket_fd, F_GETFL, 0)) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("server: fcntl()"); + return -1; + } + + // Set the listen socket to non-blocking mode + while (fcntl(listen_socket_fd, F_SETFL, flags | O_NONBLOCK) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("server: fcntl()"); + return -1; + } + + // Add the listen socket to the epoll instance + event.events = EPOLLIN; + event.data.fd = listen_socket_fd; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_socket_fd, &event) == -1) + { + perror("server: epoll_ctl()"); + return -1; + } + } return 0; } + +/** + * @brief Add the pipe to the epoll instance. + * + * This function sets the pipe read end to non-blocking mode and adds it to the epoll instance. + * + * @param[in] epoll_fd The file descriptor of the epoll instance. + * @return The status of the process. + * @retval 0 The pipe was successfully added to the epoll instance. + * @retval -1 An error occurred during the process. + */ +int add_pipe_to_epoll(int epoll_fd) +{ + // Get the current flags for + int flags; + while ((flags = fcntl(pipe_fds[0], F_GETFL, 0)) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("server: fcntl()"); + return -1; + } + + // Set the pipe read end to non-blocking mode + while (fcntl(pipe_fds[0], F_SETFL, flags | O_NONBLOCK) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("server: fcntl()"); + return -1; + } + + // Set up a signal handler for SIGINT + struct sigaction sa; + sa.sa_handler = handle_sigint; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGINT, &sa, NULL) == -1) + { + perror("server: sigaction()"); + return -1; + } + + // Add the pipe read end to the epoll instance + struct epoll_event event; + event.events = EPOLLIN; + event.data.fd = pipe_fds[0]; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipe_fds[0], &event) == -1) + { + perror("server: epoll_ctl()"); + return -1; + } + + return 0; +} + +/** + * @brief Handle a new connection on the listening socket. + * + * This function accepts a new connection on the listening socket, sets the new connection + * socket to non-blocking mode, adds it to the epoll instance, and registers it with the + * client socket manager. + * + * @param[in] listen_socket_fd The file descriptor of the listening socket. + * @param[in] epoll_fd The file descriptor of the epoll instance. + * @param[in] client_socket_manager The socket manager for client sockets. + * @return The status of the new connection. + * @retval 0 The connection was successfully accepted and registered. + * @retval -1 An error occurred during the process. + */ +int handle_new_connection(int listen_socket_fd, int epoll_fd, SocketManager *client_socket_manager) +{ + struct sockaddr_storage client_addr; + socklen_t addr_len = sizeof(client_addr); + int conn_socket_fd; + + if ((conn_socket_fd = accept(listen_socket_fd, (struct sockaddr *)&client_addr, &addr_len)) == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + { + return 0; + } + else + { + perror("server: accept()"); + return -1; + } + } + + // Get the current flags for the connection socket + int flags; + while ((flags = fcntl(conn_socket_fd, F_GETFL, 0)) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("server: fcntl(conn_sock)"); + close_with_retry(conn_socket_fd); + return -1; + } + + // Set the connection socket to non-blocking mode + while (fcntl(conn_socket_fd, F_SETFL, flags | O_NONBLOCK) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("server: fcntl(conn_sock)"); + close_with_retry(conn_socket_fd); + return -1; + } + + // Fulfilled the maximum number of clients + if (add_socket(client_socket_manager, conn_socket_fd) == -1) + { + puts("No more room for clients\n"); + close_with_retry(conn_socket_fd); + return 0; + } + + struct epoll_event event; + event.events = EPOLLIN | EPOLLOUT; + event.data.fd = conn_socket_fd; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_socket_fd, &event) == -1) + { + perror("server: epoll_ctl()"); + close_with_retry(conn_socket_fd); + return -1; + } + + char client_ip[INET6_ADDRSTRLEN]; + if (client_addr.ss_family == PF_INET) + { + struct sockaddr_in *client_addr4 = (struct sockaddr_in *)&client_addr; + if (inet_ntop(PF_INET, &client_addr4->sin_addr, client_ip, sizeof(client_ip)) == NULL) + { + perror("server: inet_ntop(PF_INET)"); + return -1; + } + printf("Connection from %s, %d\n", client_ip, ntohs(client_addr4->sin_port)); + } + else if (client_addr.ss_family == PF_INET6) + { + struct sockaddr_in6 *client_addr6 = (struct sockaddr_in6 *)&client_addr; + if (inet_ntop(PF_INET6, &client_addr6->sin6_addr, client_ip, sizeof(client_ip)) == NULL) + { + perror("server: inet_ntop(PF_INET6)"); + return -1; + } + printf("Connection from %s, %d\n", client_ip, ntohs(client_addr6->sin6_port)); + } + + return 0; +} + +/** + * @brief Handle the client socket + * + * This function handles communication with a connected client socket. It reads data from the client, + * processes it, and sends a response back to the client. The function continues to read and send data + * until the client disconnects or an error occurs. + * + * @param[in] client_socket_data The socket data for the client socket. + * @param[in] event The epoll event for the client socket. + * @return The status of the client connection. + * @retval 1 The client is still connected. + * @retval 0 The client has disconnected. + * @retval -1 An error occurred during communication. + */ +int handle_client(SocketData *client_socket_data, struct epoll_event event) +{ + // Check if the client socket is ready to read + if (event.events & EPOLLIN) + { + ssize_t received_bytes; + if ((received_bytes = recv(client_socket_data->socket_fd, client_socket_data->buffer, BUFFER_SIZE - 1, 0)) > 0) + { + printf("received_bytes: %ld (fd: %d)\n", received_bytes, client_socket_data->socket_fd); + client_socket_data->buffer[received_bytes] = '\0'; // Null-terminate the string + } + else if (received_bytes == 0) + { + puts("Connection closed\n"); + return 0; + } + else + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + { + return 1; + } + perror("server: recv()"); + return -1; + } + } + + // Check if the client socket is ready to write + if (event.events & EPOLLOUT) + { + ssize_t sent_bytes; + if ((sent_bytes = send(client_socket_data->socket_fd, client_socket_data->buffer, strlen(client_socket_data->buffer), 0)) >= 0) + { + memmove(client_socket_data->buffer, client_socket_data->buffer + sent_bytes, strlen(client_socket_data->buffer) - sent_bytes); + client_socket_data->buffer[strlen(client_socket_data->buffer) - sent_bytes] = '\0'; + } + else + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + { + return 1; + } + perror("server: send()"); + return -1; + } + } + + return 1; +} + +/** + * @brief Handle the SIGINT signal + * + * This function writes the signal number to the pipe to notify the main loop to exit. + * + * @param[in] sig The signal number. + */ +void handle_sigint(int sig) +{ + while (write(pipe_fds[1], &sig, sizeof(sig)) == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + { + continue; + } + perror("server: write()"); + exit(1); + } +} diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..a476f4d --- /dev/null +++ b/utils.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include + +#include "utils.h" + +void init_socket_manager(SocketManager *manager, int max_size) +{ + manager->sockets = (SocketData *)malloc(max_size * sizeof(SocketData)); + manager->free_indices = (int *)malloc(max_size * sizeof(int)); + manager->max_size = max_size; + manager->top = max_size - 1; + + for (int i = 0; i < max_size; i++) + { + manager->sockets[i].socket_fd = -1; + manager->free_indices[i] = i; + } +} + +SocketData *find_socket(SocketManager *manager, int socket_fd) +{ + for (int i = 0; i < manager->max_size; i++) + { + if (manager->sockets[i].socket_fd == socket_fd) + { + return &manager->sockets[i]; + } + } + return NULL; +} + +int add_socket(SocketManager *manager, int socket_fd) +{ + if (manager->top < 0) + { + return -1; + } + int index = manager->free_indices[manager->top--]; + manager->sockets[index].socket_fd = socket_fd; + return index; +} + +int remove_socket(SocketManager *manager, int socket_fd) +{ + for (int i = 0; i < manager->max_size; i++) + { + if (manager->sockets[i].socket_fd == socket_fd) + { + manager->sockets[i].socket_fd = -1; + manager->free_indices[++manager->top] = i; + return 0; + } + } + return -1; +} + +int close_all_sockets(SocketManager *manager) +{ + for (int i = 0; i < manager->max_size; i++) + { + if (manager->sockets[i].socket_fd != -1) + { + close_with_retry(manager->sockets[i].socket_fd); + } + } + free(manager->sockets); + free(manager->free_indices); + return 0; +} + +int parse_port(const char *port_str) +{ + char *endptr; + errno = 0; + long port = strtol(port_str, &endptr, 10); + if (endptr == port_str || *endptr != '\0' || errno == ERANGE || port <= 0 || port > 65535) + { + return -1; + } + return (int)port; +} + +int close_with_retry(int fd) +{ + while (close(fd) == -1) + { + if (errno == EINTR) + { + continue; + } + perror("close()"); + return -1; + } + return 0; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..f3d1640 --- /dev/null +++ b/utils.h @@ -0,0 +1,30 @@ +#include + +#ifndef UTILS_H +#define UTILS_H + +#define BUFFER_SIZE 256 + +typedef struct +{ + int socket_fd; + char buffer[BUFFER_SIZE]; // TODO: Use dynamic memory allocation for the buffer +} SocketData; + +typedef struct +{ + SocketData *sockets; + int *free_indices; + int max_size; + int top; +} SocketManager; + +void init_socket_manager(SocketManager *manager, int max_size); +SocketData *find_socket(SocketManager *manager, int socket_fd); +int add_socket(SocketManager *manager, int socket_fd); +int remove_socket(SocketManager *manager, int socket_fd); +int close_all_sockets(SocketManager *manager); + +int parse_port(const char *port_str); +int close_with_retry(int fd); +#endif