#c #sockets #tcp
#c #розетки #tcp
Вопрос:
Это моя реализация будущего HTTP-сервера.
void Webserv::resetFdSets()
{
int fildes;
FD_ZERO(amp;to_read);
FD_ZERO(amp;to_write);
max_fd = -1;
Server_map::iterator it;
for (it = servers.begin(); it != servers.end(); it) //set server sockets to be read
{
fildes = it->second.getFd();
FD_SET(fildes, amp;to_read);
if (fildes > max_fd)
max_fd = fildes;
}
std::list<int>::iterator iter;
for (iter = accepted.begin(); iter != accepted.end(); iter) // set client sockets if any
{
fildes = (*iter);
FD_SET(fildes, amp;to_read);
FD_SET(fildes, amp;to_write);
if (fildes > max_fd)
max_fd = fildes;
}
}
принять ()
void Webserv::acceptConnections()
{
int client_fd;
sockaddr_in cli_addr;
socklen_t cli_len = sizeof(sockaddr_in);
Server_map::iterator it;
for (it = servers.begin(); it != servers.end(); it)
{
ft_bzero(amp;cli_addr, sizeof(cli_addr));
if (FD_ISSET(it->second.getFd(), amp;to_read)) // if theres data in server socket
{
client_fd = accept(it->second.getFd(), reinterpret_cast<sockaddr*>(amp;cli_addr), amp;cli_len);
if (client_fd > 0) // accept and add client
{
fcntl(client_fd, F_SETFL, O_NONBLOCK);
accepted.push_back(client_fd);
}
else
throw (std::runtime_error("accept() failed"));
}
}
}
recv()
void Webserv::checkReadSockets()
{
char buf[4096];
std::string raw_request;
ssize_t bytes;
static int connections;
std::list<int>::iterator it;
for (it = accepted.begin(); it != accepted.end(); it)
{
if (FD_ISSET(*it, amp;to_read))
{
connections;
std::cout << "Connection counter : " << connections << std::endl;
while ((bytes = recv(*it, buf, sizeof(buf) - 1, MSG_DONTWAIT)) > 0)
{
buf[bytes] = '';
raw_request = buf;
}
std::cout << raw_request << std::endl;
}
}
}
отправить ()
void Webserv::checkWriteSockets()
{
char buf[8096];
char http_success[] = {
"HTTP/1.1 200 OKrn"
"Server: This op serverrn"
"Content-Length: 580rn"
"Content-Type: text/htmlrn"
"Connection: Closedrnrn"
};
std::list<int>::iterator it = accepted.begin();
while (it != accepted.end())
{
int cliSock = (*it);
if (FD_ISSET(cliSock, amp;to_write))
{
int response_fd = open("hello.html", O_RDONLY);
int bytes = read(response_fd, buf, sizeof(buf));
send(cliSock, http_success, sizeof(http_success) - 1, MSG_DONTWAIT); // header
send(cliSock, buf, bytes, MSG_DONTWAIT); // hello world
close(cliSock);
close(response_fd);
it = accepted.erase(it);
}
else
it;
}
}
main loop:
void Webserv::operate()
{
int rc;
while (true)
{
resetFdSets(); // add server and client fd for select()
if ((rc = select(max_fd 1, amp;to_read, amp;to_write, NULL, NULL)) > 0) // if any of the ports are active
{
acceptConnections(); // create new client sockets with accept()
checkReadSockets(); // parse and route client requests recv() ...
checkWriteSockets(); // send() response and delete client
}
else if (rc < 0)
{
std::cerr << "select() failed" << std::endl;
exit(1);
}
}
}
Тот же код выделил определение класса Webserv в pastebin: https://pastebin.com/9B8uYumF
На данный момент алгоритм таков:
- сбросьте настройки fd_sets для чтения и записи.
- если какой-либо из файловых дескрипторов запускает select, мы проверяем, установлены ли FD сервера, принимаем соединения и сохраняем их в списке целых чисел.
- если какой-либо из клиентских FD является FD_ISSET() для чтения — мы считываем их запрос и печатаем его.
- наконец, если какой-либо из клиентских FD готов к приему — мы помещаем туда html-страницу и закрываем соединение.
При проверке всех дескрипторов клиента FD_ISSET() возвращает false в 80% случаев, когда этого не должно быть. Следовательно, я не могу получать запросы клиентов последовательно. Как ни странно, он возвращает true с помощью fd_set для сокетов записи гораздо чаще. Вот демонстрационная версия с 2 из 10 успешных чтений с использованием приведенного выше кода: https://imgur.com/a/nzc21LV
Я знаю, что новые клиенты всегда принимаются, потому что он всегда вводит условие if в acceptConnections() . Другими словами, listen FD инициализируются правильно.
редактировать: Вот как он инициализируется:
void Server::init()
{
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
throw (std::runtime_error("socket() failed"));
}
fcntl(listen_fd, F_SETFL, O_NONBLOCK);
int yes = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, amp;yes, sizeof(int));
if (bind(listen_fd, reinterpret_cast<sockaddr*>(amp;server_addr), sizeof(server_addr)))
{
throw (std::runtime_error("bind() failed"));
}
if (listen(listen_fd, 0))
throw (std::runtime_error("listen() failed"));
}
Комментарии:
1. Это даже отдаленно не похоже на действительный HTTP-сервер. Вы вообще не анализируете HTTP-запросы, и в вашем HTTP-ответе содержится ошибка. Но что касается обработки
fd_set
s, вы игнорируете возвращаемое значениеrecv()
для обнаружения отключений / ошибок. И причинаFD_ISSET
, по которой возвращает true для возможности записи больше, чем для чтения, заключается просто в том, что сокеты обычно находятся в состоянии, доступном для записи, только когда заполняется входящий буфер удаленного узла, они не находятся в состоянии, доступном для записи, пока в буфере не освободится место.2. Ваша обработка HTTP неверна, что приводит к тому, что остальная часть вашего кода демонстрирует поведение, которого вы не ожидаете. Исправьте обработку HTTP-запросов, чтобы определять, когда чтение запросов фактически завершено (см. RFC 2616, Раздел 4.4 ) перед отправкой ответов, не полагайтесь на
select()
сообщение о том, когда отправлять ответы. Это неправильная логика для использования. Но поскольку вы используете неблокирующие сокеты, вам нужно будет буферизировать свои ответы иselect()
сообщать вам, когда вы можете отправлять буферы.3. @RemyLebeau Спасибо за ответ. Я знаю, что над этим еще нужно поработать, но для анализа HTTP-запросов мне нужно, чтобы они действительно считывались последовательно, не так ли? 🙂 Я намеренно игнорирую возвращаемое значение: поскольку сокеты неблокирующие, recv() в какой-то момент вернет -1 с EAGAIN . Итак, мои мысли были прочитаны, пока я могу> проанализировать все, что было прочитано> ответить, но я думаю, что это не сработает.
4. вы не сбрасываете
raw_request
настройки для каждого нового клиента, с которого вы читаете. Вы не проводите синтаксическийraw_request
анализ, чтобы узнать, КОГДА прекратить чтение, согласно RFC (использование тайм-аута блокировки — это не выход). Вы не проверяетеrecv()
сбой / отключение. Любая ошибка чтения, отличная отEWOULDBLOCK
илиEAGAIN
(которые не всегда имеют одинаковое значение на всех платформах), требует немедленного закрытия клиентского сокета и удаления его изaccepted
списка. Вся вашаcheckWriteSockets()
обработка неверна и подвержена ошибкам. Даже перед исправлением HTTP-содержимого, ваша базовая обработка TCP требует дополнительной работы.5. Я бы предложил изменить ваш
accepted
список на хранение объектов, где каждый объект имеет дескриптор сокета и 2string
буфера, один для входящих и один для исходящих. Всеrecv()
возвращаемые данные поступают во входящий буфер и анализируются на предмет запросов. Для каждого завершенного запроса удаляйте его из входящего буфера, обрабатывайте и помещайте ответ в исходящий буфер. Проверяйтеselect()
возможность записи только для клиентов с непустым исходящим буфером. Когдаselect()
отчеты клиента доступны для записи, отправляйте и удаляйте данные из исходящего буфера этого клиента до тех пор, пока они не будут очищены илиsend()
не завершатся сбоем.