Files
NXST/source/server.cpp
T
2026-04-24 19:15:58 +03:00

332 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <arpa/inet.h>
#include <cstring>
#include <fcntl.h>
#include <iomanip>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <netinet/in.h>
#include <ostream>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef __SWITCH__
#include <server.hpp>
#include <switch.h>
#include <main.hpp>
#endif
#define PORT 8080
#define BUFFER_SIZE 65536
#define MULTICAST_PORT 8081
#define MULTICAST_GROUP "239.0.0.1" // Multicast group IP
namespace fs = std::filesystem;
#ifdef __SWITCH__
std::string replaceUsername(const std::string &path) {
std::string replacedString = StringUtils::removeNotAscii(
StringUtils::removeAccents(Account::username(g_currentUId)));
// Найдём позицию последнего символа '/'
size_t lastSlashPos = path.rfind('/');
// Если нет '/', возвращаем исходный путь
if (lastSlashPos == std::string::npos) {
return path;
}
// Найдём позицию предыдущего символа '/' (начало последней папки)
size_t prevSlashPos = path.rfind('/', lastSlashPos - 1);
// Если предыдущий '/' не найден, значит путь состоит из одной папки и файла
// Заменим последнюю папку и вернём полный путь
if (prevSlashPos == std::string::npos) {
return replacedString + path.substr(lastSlashPos);
}
// Собираем путь, заменяя последнюю папку на "name"
return path.substr(0, prevSlashPos + 1) + replacedString +
path.substr(lastSlashPos);
}
#endif
// Читает ровно len байт из сокета, повторяя read при частичном получении.
static bool recv_all(int sock, void *buf, size_t len) {
size_t received = 0;
while (received < len) {
ssize_t n = read(sock, static_cast<char *>(buf) + received, len - received);
if (n <= 0) return false;
received += n;
}
return true;
}
// Создаёт все компоненты пути через POSIX mkdir.
// std::filesystem::create_directories не работает с devkitPro-путями sdmc:/.
static void mkdirs(const std::string &path) {
for (size_t i = 1; i < path.size(); i++) {
if (path[i] == '/') {
std::string component = path.substr(0, i);
int rc = mkdir(component.c_str(), 0777);
std::cout << "mkdirs: mkdir [" << component << "] rc=" << rc << " errno=" << errno << std::endl;
}
}
int rc = mkdir(path.c_str(), 0777);
std::cout << "mkdirs: mkdir [" << path << "] rc=" << rc << " errno=" << errno << std::endl;
}
// Функция для получения файла
void receive_file(int sock, const std::string &relative_path,
size_t file_size) {
std::cout << "relative_path is: " << relative_path << std::endl;
// Печатаем путь побайтово — ловим невидимые символы
std::cout << "receive_file len=" << relative_path.size() << " path=[";
for (unsigned char c : relative_path) {
if (c >= 0x20 && c <= 0x7e) std::cout << c;
else std::cout << "\\x" << std::hex << std::setw(2) << std::setfill('0') << (int)c << std::dec;
}
std::cout << "]" << std::endl;
size_t last_slash = relative_path.rfind('/');
std::string dir = (last_slash != std::string::npos)
? relative_path.substr(0, last_slash)
: "";
std::cout << "receive_file dir=[" << dir << "]" << std::endl;
if (!dir.empty()) mkdirs(dir);
// Проверяем stat на папке перед fopen
struct stat st;
int statrc = stat(dir.c_str(), &st);
std::cout << "stat(dir) rc=" << statrc << " is_dir="
<< (statrc == 0 && S_ISDIR(st.st_mode)) << std::endl;
FILE *outfile = fopen(relative_path.c_str(), "wb");
if (!outfile) {
int saved_errno = errno;
std::cerr << "Failed to open for writing: " << relative_path
<< " dir=[" << dir << "] fopen_errno=" << saved_errno << std::endl;
// Дренируем байты, чтобы отправитель не завис
char* drain_buf = new char[BUFFER_SIZE];
size_t remaining = file_size;
while (remaining > 0) {
ssize_t n = read(sock, drain_buf, remaining < (size_t)BUFFER_SIZE ? remaining : (size_t)BUFFER_SIZE);
if (n <= 0) break;
remaining -= n;
}
delete[] drain_buf;
return;
}
char* buffer = new char[BUFFER_SIZE]();
size_t total_bytes_received = 0;
while (total_bytes_received < file_size) {
size_t to_read = std::min((size_t)BUFFER_SIZE, file_size - total_bytes_received);
ssize_t bytes_received = read(sock, buffer, to_read);
std::cout << "Bytes received: " << bytes_received << std::endl;
if (bytes_received <= 0) {
std::cerr << "Error reading file data from socket." << std::endl;
break;
}
fwrite(buffer, 1, bytes_received, outfile);
total_bytes_received += bytes_received;
}
std::cout << "File received successfully: " << relative_path << std::endl;
delete[] buffer;
fclose(outfile);
}
void *handle_client(void *socket_desc) {
int client_socket = *(int *)socket_desc;
free(socket_desc);
std::cout << "Обработка нового клиента в потоке " << pthread_self() << "\n";
while (true) {
uint32_t filename_len;
ssize_t bytes_read =
read(client_socket, &filename_len, sizeof(filename_len));
// Check for end-of-transmission signal
if (bytes_read <= 0 || filename_len == 0) {
std::cout << "End of transmission detected." << std::endl;
break;
pthread_exit(nullptr);
}
// Receive filename
char *filename = new char[filename_len + 1]();
if (!recv_all(client_socket, filename, filename_len)) {
std::cerr << "Short read on filename, aborting." << std::endl;
delete[] filename;
break;
}
filename[filename_len] = '\0';
std::string filename_str(filename);
delete[] filename;
std::cout << "Received filename_str is " << filename_str << std::endl;
#ifdef __SWITCH__
std::cout << "Replaced filename from " << filename_str << std::endl;
filename_str = replaceUsername(filename_str);
std::cout << "to " << filename_str << std::endl;
#endif
size_t file_size;
if (!recv_all(client_socket, &file_size, sizeof(file_size))) {
std::cerr << "Short read on file_size, aborting." << std::endl;
break;
}
std::cout << "file size is: " << file_size << std::endl;
receive_file(client_socket, filename_str, file_size);
}
close(client_socket);
pthread_exit(nullptr);
}
void *broadcast_listener(void *) {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFFER_SIZE + 1];
struct ip_mreq group;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
pthread_exit(nullptr);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(MULTICAST_PORT);
if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("binding datagram socket");
close(sockfd);
pthread_exit(nullptr);
}
group.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP);
group.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group,
sizeof(group)) < 0) {
perror("setsockopt failed");
close(sockfd);
pthread_exit(nullptr);
}
std::cout << "Broadcast listener started" << std::endl;
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
while (true) {
int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&client_addr, &addr_len);
if (n < 0) {
perror("recvfrom failed");
continue;
}
std::cout << buffer << std::endl;
buffer[n] = '\0';
if (strcmp(buffer, "DISCOVER_SERVER") == 0) {
const char *message = "SERVER_HERE";
sendto(sockfd, message, strlen(message), 0,
(const struct sockaddr *)&client_addr, addr_len);
std::cout << "Server discovery response sent to multicast group"
<< std::endl;
pthread_exit(0);
}
}
close(sockfd);
pthread_exit(nullptr);
}
int startSendingThread() {
pthread_t broadcast_thread;
if (pthread_create(&broadcast_thread, nullptr, broadcast_listener, nullptr) <
0) {
perror("Thread creation failed");
return 1;
}
int server_fd, new_socket;
struct sockaddr_in address;
socklen_t addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
std::cout << "Wait for broadcast thread done " << std::endl;
pthread_join(broadcast_thread, NULL);
std::cout << "Broadcast thread done " << std::endl;
std::cout << "Server listening on port " << PORT << std::endl;
while (true) {
sockaddr_in client_address;
socklen_t client_len = sizeof(client_address);
int client_socket =
accept(server_fd, (sockaddr *)&client_address, &client_len);
if (client_socket < 0) {
std::cerr << "Ошибка принятия подключения\n";
continue;
}
// Создаем новый поток для обработки клиента
pthread_t thread_id;
int *pclient = new (std::nothrow) int(client_socket);
if (!pclient) {
std::cerr << "Ошибка выделения памяти\n";
close(client_socket);
continue;
}
if (pthread_create(&thread_id, nullptr, handle_client, pclient) != 0) {
std::cerr << "Ошибка создания потока\n";
delete pclient; // Освобождаем память при ошибке
} else {
pthread_join(thread_id, NULL);
break;
}
}
close(server_fd);
return 0;
}
#ifndef __SWITCH__ // for desktop
int main() {
return startSendingThread();
}
#endif