Обновления
Кажется, что tcp-сервер может обрабатывать файловые дескрипторы до 512. Поскольку первый клиент для подключения получает файловый дескриптор 4, максимальное количество клиентов, которые могут подключиться, составляет 509 (я могу сделать io междусервер и клиент даже для 509-го файлового дескриптора).Я не совсем уверен, откуда исходит этот предел 512?Даже если ограничить число клиентов ниже 509, если одновременно подключается более 509 клиентов, не все из них смогут получить сообщение о том, что, к сожалению, к серверу подключено слишком много клиентов.
У меня все еще есть проблема, что, когда у меня есть MAX_CONNECTIONS = 500
и CLIENTS_TO_DISCONNECT = 500
(или CLIENTS_TO_DISCONNECT = 400
), тогда программа test.cc
не завершается, и кучу процессов telnet необходимо завершить вручную.Кто-нибудь запускал код на своей машине?Если это так, было бы полезно узнать в любом случае, получают ли люди ту же проблему.
Примеры, которые я могу найти, используя вместо этого epoll, кажутся мне гораздо более сложными.Вероятно, это необходимо, но кто-нибудь знает какие-нибудь достаточно простые многопользовательские tcp-серверы, использующие epoll?
Спасибо тем, кто нашел время, чтобы прочитать этот пост, особенно тем, кто ответил.
Обновления 2
Я ошибся, сервер может обрабатывать файловые дескрипторы размером более 512. Если я запускаю сервер, то запускаю две копии test.cc
с MAX_CONNECTIONS = 400
, затемк серверу подключено 800 клиентов.Сервер может обрабатывать файловые дескрипторы только до 1023, хотя может одновременно подключать 1020 клиентов.
Это означает, что ограничение в 509 подключений, которое я устанавливал до этого, является ограничением клиента test.cc
, что довольно странно, потому что я ожидал, что ограничение будет 512, я предполагаю, что каким-то образом client.cc
также использует числа, похожие на файловые дескрипторы на сервере, и попадает в аналогичную стену.Я попытался использовать более 512 переменных redi :: pstream только для запуска echo 'hello', и это, похоже, не вызывает никаких проблем, поэтому я не уверен, откуда исходит ограничение.
У меня также все еще возникают проблемы с закрытием redi :: pstream на клиентах, подключенных после 419-го.Это происходит как с одним экземпляром test.cc
, так и с несколькими запущенными экземплярами test.cc
.
Мне также удалось внести некоторые исправления в другой код многопользовательского tcp-сервера, который использует опрос вместо select (см. здесь для кода).Интересно, что у него точно такие же проблемы (один экземпляр test.cc
работает может подключить максимум 509 клиентов, сервер может иметь максимум 1020 клиентов, и у меня возникают проблемы с закрытием redi :: pstream на клиентах, подключенных после 419-го).Я думаю, что это наводит на мысль, что проблема с максимум 509 клиентами, подключающимися с использованием одного экземпляра test.cc
, заключается в коде test.cc
, а не в коде сервера, и, вероятно, также из-за проблем с закрытием redi :: pstream на клиентахподключился после 419-го.
Обновления 3
Второй tcp-сервер занимает в два раза больше времени, чем первый, отправляя и получая сообщения туда-сюда с клиентами, поэтому я буду использоватьисходный код, который я нашел (хотя может также увидеть, смогу ли я найти решение epoll, которое может обрабатывать более 1020 подключенных клиентов).
Если вы избавитесь от операторов закрытия pstream (redi::pstream
), тогдакажется, что тестовая программа завершается правильно (и клиенты все еще отключаются до завершения тестовой программы).Однако, если я позволю слишком большому количеству входных данных на redi::pstream
без чтения, тестовая программа не сможет завершиться.
Я также попробовал libexecstream вместо pstream.libexecstream обрывается, когда я пытаюсь открыть более 337 потоков.Таким образом, я могу подключить до 337 клиентов к серверу, используя libexecstream, используя только одну программу.Однако, если я запускаю одну и ту же программу несколько раз, она может подключить больше клиентов к серверу.
С pstreams у меня проблема в том, что клиенты, подключенные после 419, не отключаются и не закрываются должным образом, программа останавливается.У меня нет этой проблемы с libexecstreams, процессы / потоки закрываются должным образом.Когда я подключаюсь, скажем 300 клиентов, использующих libexecstreams.Я могу подключить еще 400 клиентов, используя pstream, но снова столкнуться с проблемами с закрытием pstream для клиентов, подключенных после 420 к серверу.Хотя это можно исправить с помощью pstreams, как предложено выше, просто не вызывая close на pstream.
У вас также есть тот факт, что входные данные на сервер от клиентов «группируются», т.е.если более чем одно сообщение, поступающее до выбора / опроса, обнаруживает, что сообщения поступили, тогда read / recv прочитает их все в предоставленный массив буферов.Если объединенное сообщение слишком длинное для буфера, то сообщение в конце буфера может быть «разрезано пополам», и его нелегко собрать обратно.Я бы предположил, что это довольно большая проблема, если вы не можете иметь достаточно большой размер буфера для обработки всех сгруппированных сообщений, которые будут поступать в течение определенного периода времени.К счастью, при использовании io действительно большого размера буфера не наблюдается каких-либо серьезных изменений во время выполнения.
Однако следует обратить внимание на то, что если размер буфера превышает 3000. Где-то выше этого значения вы больше не можете рассматривать массив символов как строку, выводить его и устанавливать равным строке не работает,Вы должны пройтись по массиву char и отдельно добавить символы в строку.(Обратите внимание, что вам не нужно делать это при отправке данных обратно клиенту, но вам нужно делать это, если вы хотите получить строковую версию буферного массива char, содержащего ввод от клиента).
Извините за длинный пост, но это поставило меня в тупик.Я открыт для использования другого кода для tcp-сервера, если люди знают что-либо, что может обрабатывать больше клиентов без ошибок (хотя ошибочность здесь, вероятно, моя ошибка, и мне нужно иметь возможность установить тайм-аут при проверке вводаот клиентов), и если кто-то повторяет ошибки, о которых я упоминаю в этом посте, пожалуйста, постите, чтобы сообщить, что вы тоже их испытываете, это полезно, даже если вы не можете понять, почему ошибки возникают.
Я пытаюсь узнать, как настроить многопользовательский tcp-сервер, однако у меня возникают проблемы, когда я пытаюсь проверить, сколько пользователей могут подключиться к коду tcp-сервера, который я использую.
Код tcp-сервера, который я использую, приведен ниже и представляет собой слегка измененную версию кода tcp-сервера, доступную здесь .
Примечание: модификации выводят FD_SETSIZE в строке 36 (на моем компьютере это 1024), меняя max_clients на 1500, отслеживая, сколько клиентов подключено (no_clients_connected
), закрывая соединение для новых клиентов, когда max_clientsуже подключены и выводят количество подключенных клиентов как при новом соединении, так и при отключении клиента.
Вы можете скомпилировать код tcp-сервера (при вызове server.cc
), используя:
g++ -std=c++11 -Wall -Wextra -pedantic -c -o server.o server.cc
g++ -std=c++11 -Wall -Wextra -pedantic server.cc -o server
Примечание. Кто-нибудь знает, что делать с предупреждением в строке 34 об устаревшем преобразовании из string
константы в char*
?(Гонки Легкости на Орбите указали, как это исправить).
Если вы скомпилируете и запустите код tcp-сервера, вы сможете подключиться к нему, запустив telnet localhost 8888
из окна терминала.Чтобы выйти, введите ctrl+]
, а затем quit
в командной строке telnet.
//Example code: A simple server side code, which echos back the received message.
//Handle multiple socket connections with select and fd_set on Linux
#include <iostream>
#include <stdio.h>
#include <string.h> //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h> //close
#include <arpa/inet.h> //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros
#define TRUE 1
#define FALSE 0
#define PORT 8888
int main()
{
int no_clients_connected = 0;
int opt = TRUE;
int master_socket , addrlen , new_socket , client_socket[1500] ,
max_clients = 1500 , activity, i , valread , sd;
int max_sd;
struct sockaddr_in address;
char buffer[1025]; //data buffer of 1K
//set of socket descriptors
fd_set readfds;
//a message
const char *message = "ECHO Daemon v1.0 \r\n";
std::cout << "FD_SETSIZE " << FD_SETSIZE << std::endl;
//initialise all client_socket[] to 0 so not checked
for (i = 0; i < max_clients; i++)
{
client_socket[i] = 0;
}
//create a master socket
if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
//set master socket to allow multiple connections ,
//this is just a good habit, it will work without this
if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt,
sizeof(opt)) < 0 )
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
//type of socket created
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( PORT );
//bind the socket to localhost port 8888
if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0)
{
perror("bind failed");
exit(EXIT_FAILURE);
}
printf("Listener on port %d \n", PORT);
//try to specify maximum of 3 pending connections for the master socket
if (listen(master_socket, 3) < 0)
{
perror("listen");
exit(EXIT_FAILURE);
}
//accept the incoming connection
addrlen = sizeof(address);
puts("Waiting for connections ...");
while(TRUE)
{
//clear the socket set
FD_ZERO(&readfds);
//add master socket to set
FD_SET(master_socket, &readfds);
max_sd = master_socket;
//add child sockets to set
for ( i = 0 ; i < max_clients ; i++)
{
//socket descriptor
sd = client_socket[i];
//if valid socket descriptor then add to read list
if(sd > 0)
FD_SET( sd , &readfds);
//highest file descriptor number, need it for the select function
if(sd > max_sd)
max_sd = sd;
}
//wait for an activity on one of the sockets , timeout is NULL ,
//so wait indefinitely
activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL);
if ((activity < 0) && (errno!=EINTR))
{
printf("select error");
}
//If something happened on the master socket ,
//then its an incoming connection
if (FD_ISSET(master_socket, &readfds))
{
if ((new_socket = accept(master_socket,
(struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
{
perror("accept");
exit(EXIT_FAILURE);
}
//inform user of socket number - used in send and receive commands
printf("New connection , socket fd is %d , ip is : %s , port : %d\n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
if(no_clients_connected >= max_clients)
{
close(new_socket);
std::cout << "kicked them because too many clients connected" << std::endl;
}
else
{
no_clients_connected++;
//send new connection greeting message
if( (size_t) send(new_socket, message, strlen(message), 0) != strlen(message) )
{
perror("send");
}
puts("Welcome message sent successfully");
//add new socket to array of sockets
for (i = 0; i < max_clients; i++)
{
//if position is empty
if( client_socket[i] == 0 )
{
client_socket[i] = new_socket;
printf("Adding to list of sockets as %d\n" , i);
break;
}
}
}
std::cout << "number of clients connected is " << no_clients_connected << std::endl;
}
//else its some IO operation on some other socket
for (i = 0; i < max_clients; i++)
{
sd = client_socket[i];
if (FD_ISSET( sd , &readfds))
{
//Check if it was for closing , and also read the
//incoming message
if ((valread = read( sd , buffer, 1024)) == 0)
{
//Somebody disconnected , get his details and print
getpeername(sd , (struct sockaddr*)&address , \
(socklen_t*)&addrlen);
printf("Host disconnected , ip %s , port %d \n" ,
inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
no_clients_connected--;
std::cout << "number of clients connected is " << no_clients_connected << std::endl;
//Close the socket and mark as 0 in list for reuse
close( sd );
client_socket[i] = 0;
}
//Echo back the message that came in
else
{
//set the string terminating NULL byte on the end
//of the data read
send(sd, buffer, valread, 0);
//buffer[valread] = '\0';
//send(sd , buffer , strlen(buffer) , 0 );
}
}
}
}
return 0;
}
Код, который я использую для проверки количества клиентов, которых я могу подключить, приведен ниже и использует pstreams .В Ubuntu вы можете получить pstream с помощью sudo apt-get install libpstreams-dev
, или вы можете скачать его здесь .
Вы можете скомпилировать приведенный ниже код (при вызове test.cc
), используя:
g++ -std=c++11 -pthread -c test.cc -o test.o
g++ -o test test.o -pthread
Если вы запускаете тестовый код с уже запущенным сервером, он должен установить MAX_CONNECTIONS = 400 подключений к серверу.Если вы вернетесь и проверьте, где работает сервер, то теперь к нему должно быть подключено 400 клиентов.Если вы затем вернетесь туда, где выполняется тестовый код, и введете строку (она читает целую строку), она должна пройти и отключить CLIENTS_TO_DISCONNECT = 400 клиентов, и (на моем компьютере) программа завершится без проблем.
На моей машине (2012 11 "macbook air под управлением Ubuntu), если я изменю CLIENTS_TO_DISCONNECT на 350 и снова сделаю то же самое, 400 клиентов нормально подключаются к серверу, и (после ввода строки) 350 клиентов отключаются нормальнои я получаю целую кучу строк «Соединение закрыто сторонним хостом», выведенных от клиентов, которых я не отключал, хотя тестовая программа по-прежнему завершается без проблем.
Если я изменю MAX_CONNECTIONS на 500 и CLIENTS_TO_DISCONNECTдо 400. 500 клиентов подключаются к серверу, и когда я ввожу строку для 400 клиентов, чтобы отключить, 400 клиентов действительно отключаются, но тестовая программа не заканчивается, и не многие из оставшихся подключений закрываются сторонним хостом, поэтому сервер по-прежнемудумает, что у него есть куча клиентови тестовая программа должна быть принудительно завершена (иногда оставляя после себя процессы telnet, которые также должны быть уничтожены вручную).
Если я изменю MAX_CONNECTIONS на 550, то я даже не смогу подключить 550 клиентов ксервер.Однако на этой странице в разделе BUGS написано:
POSIX позволяет реализации определять верхний предел, объявленный через константу FD_SETSIZE, для диапазона дескрипторов файлов, которые могутбыть указан в наборе файловых дескрипторов.Ядро Linux не накладывает фиксированного ограничения, но реализация glibc делает fd_set типом фиксированного размера с FD_SETSIZE, определенным как 1024, и макросами FD _ * (), работающими в соответствии с этим пределом.Чтобы отслеживать файловые дескрипторы больше 1023, используйте poll (2).
Так что я ожидал, что у меня будет как минимум 1024 клиента, использующих select () и, возможно, больше, если я переключусь на использование poll(2) вместо?Хотя ни выбор, ни опрос не имеют ничего общего с клиентами, которые фактически подключаются к серверу, они имеют отношение к мониторингу активности дескрипторов файлов для подключенных клиентов.(Гонки Легкости на Орбите указали, что предыдущее предложение неверно, так как select используется для мониторинга входящих соединений).
Если кто-то может понять, почему происходит какое-то странное поведение, это было бы невероятно полезнои ценится.
#include <cstdio>
#include <iostream>
#include <pstreams/pstream.h>
const char ESCAPE_CHAR = 0x1d; //this is 'ctrl+]'
const int MAX_CONNECTIONS = 400;
const int CLIENTS_TO_DISCONNECT = 400;
int main()
{
redi::pstream servers[MAX_CONNECTIONS];
for(int i=0; i<MAX_CONNECTIONS; i++)
servers[i].open("telnet localhost 8888");
std::cout << "'connected'" << std::endl;
std::string s;
getline(std::cin, s);
for(int i=0; i<CLIENTS_TO_DISCONNECT; i++)
{
//std::cout << i << std::endl;
servers[i] << ESCAPE_CHAR << " quit" << std::endl;
servers[i].close();
}
std::cout << "made it to here" << std::endl;
return 0;
}