#cpp #linux #сервер #unix
У меня есть сервер написанный на C++ для *UNX, основанный на "неблокирующих сокетах с использованием select" - Пример данный реализации есть в интернете, я использовал эту конструкцию для своего сервера и хочу удостоверится оптимальная ли логика работы сервера для моей задачи. Задача: Что должен делать мой сервер: 1) Принимать клиентов СТРОГО по авторизации и держать с ними соединение. 2) Выполнять действия по команде клиента (Запрос в бд, чтение запись в файл, и т.д) 3) Передавать файлы клиенту по его запросу. Число клиентов от 15 000 тысяч. Пример конструкции на которой основан моей сервер. int main() { int listener; struct sockaddr_in addr; char buf[1024]; int bytes_read; listener = socket(AF_INET, SOCK_STREAM, 0); if(listener < 0) { perror("socket"); exit(1); } fcntl(listener, F_SETFL, O_NONBLOCK); addr.sin_family = AF_INET; addr.sin_port = htons(3425); addr.sin_addr.s_addr = INADDR_ANY; if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); exit(2); } listen(listener, 2); setclients; clients.clear(); while(1) { // Заполняем множество сокетов fd_set readset; FD_ZERO(&readset); FD_SET(listener, &readset); for(set ::iterator it = clients.begin(); it != clients.end(); it++) FD_SET(*it, &readset); // Задаём таймаут timeval timeout; timeout.tv_sec = 15; timeout.tv_usec = 0; // Ждём события в одном из сокетов int mx = max(listener, *max_element(clients.begin(), clients.end())); if(select(mx+1, &readset, NULL, NULL, &timeout) <= 0) { perror("select"); exit(3); } // Определяем тип события и выполняем соответствующие действия if(FD_ISSET(listener, &readset)) { // Поступил новый запрос на соединение, используем accept int sock = accept(listener, NULL, NULL); if(sock < 0) { perror("accept"); exit(3); } fcntl(sock, F_SETFL, O_NONBLOCK); clients.insert(sock); } for(set ::iterator it = clients.begin(); it != clients.end(); it++) { if(FD_ISSET(*it, &readset)) { // Поступили данные от клиента, читаем их bytes_read = recv(*it, buf, 1024, 0); if(bytes_read <= 0) { // Соединение разорвано, удаляем сокет из множества close(*it); clients.erase(*it); continue; } // Отправляем данные обратно клиенту send(*it, buf, bytes_read, 0); } } } return 0; } Авторизация и приём команд происходит в блоке for(set ::iterator it = clients.begin(); it != clients.end(); it++) { if(FD_ISSET(*it, &readset)) { // Поступили данные от клиента, читаем их bytes_read = recv(*it, buf, 1024, 0); if(bytes_read <= 0) { // Соединение разорвано, удаляем сокет из множества close(*it); clients.erase(*it); continue; } /* Проверяем что за команда и дальнейшие действия с ней */ } Если от клиента пришла команда загрузки файла, то сервер создаёт новый поток для отправки файла. Команды и файлы отправляются через один порт что не очень хорошо. Вопросы: 1) Подойдёт ли данная конструкция сервера для реализации моей задачи? 2) Какие ещё есть варианты сервера для реализации моей задачи? (Можно и с потоками там форк или аналоги) Приоритеты: Обычно тут идёт выбор между производительностью и ресурсами, я предпочитаю выбор производительности и надёжности сервера. Готов к критики новым идеям и технологиям в рамках моей темы, но не личности (Шутка).
Ответы
Ответ 1
если Вы планируете работать с 15000 пользователей одновременно, то этот код работать не будет. В целом, написание серверов, которые держут 10к пользователей одновременно - уже не такая тривиальная задача. Но Ваш код будет держать одновременно не более 1024 пользователей (на самом деле где то 900-950, нужно глубже код копать). Вся суть в select. Он просто не умеет больше (там просто массив на 1024 элемента). Но главное, что нужно понимать, что это значит не то, select умеет управлять 1024 дескрипторами, а дескрипторами, номера которых меньше 1024. Если нужно много клиентов, то первое, что нужно сделать, это заменить select на poll (замена на самом деле не очень сложная, но работать будет лучше, хотя на больших наборах медленнее - poll тратит 8 байт на сокет). Если нужно ещё быстрее - есть epoll и подобные вещи. Следующая ошибка - Вы читаете с помощью recv, но похоже надеетесь, что все данные придут сразу. Это очень распространенная ошибка. Если с одной стороны отправить send на 100 байт, то с другой стороны может прочитаться как 100 байт одновременно, а может и 20, а вторым пакетом 80. То есть, данные могут разрываться как угодно, гарантируется только порядок байт и то, что байты не будут "утеряны произвольно". Поэтому, прочитав данные, нужно проанализировать буфер. Если там данных недостаточно, то ждать следующего чтения, которым дописать данные в конец буфера и снова анализировать. Вполне возможно, что после этого в буфере будет вся первая команда и начало второй. Или даже первая, вторая и начало третей. Точно также с командой send. Она не гарантирует, что все данные будут отправлены. Она возвращает кол-во байт, которые были отправлены. Поэтому нужно организовывать досылку данных.
Комментариев нет:
Отправить комментарий