socketsunixselectfile-descriptorkqueue

read event is received before the write one in kqueue


I'm having an issue with my code, I'm trying to create an HTTP server using C++ 98 on MacOS, and I'm expecting the block of read to be executed before the write one, but the opposite is happening and I don't know why. So I'm expecting to read first the request, then send a response. but the opposite is happening here, this my code:

/************************ CONSTRUCTORS/DESTRUCTOR ************************/
webserv::Server::Server(int addr, int port)
{
    this->sock.create_socket(AF_INET, SOCK_STREAM, 0);
    this->sock.bind_socket(addr, port);
    this->sock.listen_socket(__MAX_BACKLOG__);
}

webserv::Server::~Server() {}

/************************ MEMBER FUNCTION ************************/
void    webserv::Server::lunch()
{
    this->kq.create_event(this->sock.getSocket(), EVFILT_READ);
    while (1)
        this->_lunch_worker();
}

void    webserv::Server::_lunch_worker(void)
{
    int ev_count = this->kq.get_event();
    static const char* index_html = "HTTP/1.0 200 OK\r\n" \
                                    "Content-Length: 86\r\n\r\n" \
                                    "<!DOCTYPE html>" \
                                    "<html><head>Hello, world!</head><body><h1>cdn-ish...</h1></body></html>\r\n";
    char buf[10000];

    for (int i = 0; i < ev_count; i++) {
        int fd = this->kq.get_fd(i);

        if (fd < 0) continue;
        if (fd == this->sock.getSocket()) {
            
            int clientaddr_size = sizeof(this->sock.getAddress());

            int clientfd = this->sock.accept_socket();
            if (clientfd < 0) {
                perror("accept");
                close(fd);
                return ;
            }
            this->kq.create_event(clientfd, EVFILT_READ);
            if (fcntl(clientfd, F_SETFL, O_NONBLOCK) < 0) {
                perror("fcntl");
                close(clientfd);
                close(fd);
            }
            this->kq.create_event(clientfd, EVFILT_WRITE, EV_ADD | EV_ONESHOT);
        // EXPECTING THIS BLOCK TO BE CHECKED/EXECUTED FIRST
        // BUT INSTEAD THE NEXT BLOCK (WRITE BLOCK) IS EXECUTED FIRST
        // THEN IN THE SECOND ITERATION THE READ BLOCK IS BEING EXECUTED
        } else if (this->kq.is_read_available(i)) {
            int len;
            std::cout << "READ" << std::endl;
            // memset(buf, 0, sizeof(buf));
            if ((len = recv(fd, buf, sizeof(buf), 0)) == 0) {
                std::cout << "READ: FD CLOSED = " << fd << std::endl;
                close(fd);
            }
            else if (len > 0)
            {
            }
            std::cout << "READ: LEN = " << len << std::endl;
        } else if (this->kq.is_write_available(i)) {
            int len = 0;
            if ((len = send(fd, index_html, strlen(index_html), 0)) != 0) {
            }
            std::cout << "WRITE: LEN = " << len << std::endl;
        }
    }
}

and kqueue class:

/***********************************************************************
* FILENAME :        Kqueue.cpp
*
* DESCRIPTION :
*       This File is the implementation of the functions
*       Defined in Kqueue.hpp
*
**/

# include "./Kqueue.hpp"
# include "../../OutputColors.hpp"

/************************ CONSTRUCTORS/DESTRUCTOR ************************/
webserv::Kqueue::Kqueue()
{
    this->_kq = kqueue();
    std::cout << "KQUEUE CREATED" << std::endl;
    this->test_error(this->_kq, "Creating Kqueue :");
    this->_n_ev = 0;
}

webserv::Kqueue::~Kqueue()
{
    close(this->_kq);
}

/************************ MEMBER FUNCTIONS ************************/
void    webserv::Kqueue::set_event(int fd, int filter, int flags, void *udata)
{
    EV_SET(&this->_ev_set, fd, filter, flags, 0, 0, udata);
}

void    webserv::Kqueue::add_event(void)
{
    int ret;

    ret = kevent(this->_kq, &this->_ev_set, 1, NULL, 0, NULL);
    this->test_error(ret, "Kqueue/add_even functions");
}

int     webserv::Kqueue::get_event(void)
{
    this->_n_ev = kevent(this->_kq, NULL, 0, this->_ev_list, __EV_LIST_SIZE__, NULL);
    this->test_error(this->_n_ev, "Kqueue/get_event function:");
    return (this->_n_ev);
}

void    webserv::Kqueue::create_event(int fd, int filter, int flags, void *udata)
{
    this->set_event(fd, filter, flags, udata);
    this->add_event();
}

bool    webserv::Kqueue::isEOF(int index)
{
    if (this->_n_ev <= index)
        this->test_error(-1, "Kqueue/isEOF function:");
    return (this->_ev_list[index].flags & EV_EOF);
}

bool    webserv::Kqueue::is_read_available(int index)
{
    if (this->_n_ev <= index)
        this->test_error(-1, "Kqueue/is_read_available function:");
    return (this->_ev_list[index].filter == EVFILT_READ);
}

bool    webserv::Kqueue::is_write_available(int index)
{
    if (this->_n_ev <= index)
        this->test_error(-1, "Kqueue/is_write_available function:");
    return (this->_ev_list[index].filter == EVFILT_WRITE);
}

void    webserv::Kqueue::test_error(int fd, const std::string &str)
{
    if (fd < 0)
    {
        std::cerr << RED << str << " ";
        perror("The following error occured: ");
        std::cerr << RESET;
        exit(EXIT_FAILURE);
    }
}

/************************ GETTERS/SETTERS ************************/
struct kevent   *webserv::Kqueue::get_event_list()
{
    return (this->_ev_list);
}

int &webserv::Kqueue::get_kq()
{
    return (this->_kq);
}

int             webserv::Kqueue::get_fd(int index)
{
    if (this->_n_ev <= index)
        this->test_error(-1, "Kqueue/get_ev_list function:");
    return (this->_ev_list[index].ident);
}

void    webserv::Kqueue::set_kqueue(int fd)
{
    this->_kq = fd;
}

Does anyone know why the WRITE comes before the READ in my case, I'm expecting to read the request, then sending the response


Solution

  • In order for it to be possible to read from the socket, first the other side has to detect that the connection is complete, then it has to compose its query and put it on the wire. Then your side has to receive the information and process it. Only then it is possible to read from the socket.

    In order for it to be possible to write to the socket, you have to detect that the connection is complete. That's it. If the connection is complete, it's possible to write.

    It's not surprising that it's possbile to write to the socket before it's possible to read from it. The question is -- why is your code checking if it's possible to write to the socket when you don't want to write to the socket? Also, why does your code write to the socket just because it's possible to do so even when you haven't even received a query from the other side?

    If you have no data to write to the other side because you haven't even received a query from the other side yet, why are you checking whether or not it's possible to write on the socket? You don't want to write even if it's possible, so why check?