kqueue socket stream

Hello,

I am currently breaking my head on a TCP socket stream with kqueue.

My problems are:
  1. It does not work (nothing happens)
  2. I have no idea how I could manage multiple clients

Code:
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/socket.h>

int r;

int kq;

int nev;

int sockfd;

int conns = 0;

int clients[10];

char buffer[1024];

struct kevent events[2];
struct kevent changes[2];

struct client_s {
    int fd;
    int type;
    socklen_t addrlen;
    struct sockaddr addr;
};

int main(int argc, const char ** argv) {
    struct addrinfo * addr = malloc(sizeof(struct addrinfo));
    struct addrinfo * hints = malloc(sizeof(struct addrinfo));

    hints->ai_family = AF_INET;
    hints->ai_socktype = SOCK_STREAM;
    hints->ai_protocol = IPPROTO_TCP;

    r = getaddrinfo("127.0.0.1", "3000", hints, &addr);

    if (r == -1) {
        perror("Error on resolving address.\n");
        exit(EXIT_FAILURE);
    }

    sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);

    if (sockfd == -1) {
        perror("Error on creating socket descriptor.\n");
        exit(EXIT_FAILURE);
    }
 
    r = bind(sockfd, addr->ai_addr, addr->ai_addrlen);

    if (r == -1) {
        perror("Error on binding to address.\n");
        exit(EXIT_FAILURE);
    }

    r = listen(sockfd, 1);

    if (r == -1) {
        perror("Error on listening on socket.\n");
        exit(EXIT_FAILURE);
    }

    kq = kqueue();

    if (kq == -1) {
        perror("Error on creating kqueue.\n");
        exit(EXIT_FAILURE);
    }

    EV_SET(&changes[0], sockfd, EVFILT_READ, 
            EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, 0);

    for (;;) {
        nev = kevent(kq, changes, 2, events, 2, NULL);

        if (nev == -1) {
            perror("Error on resolving kevents.\n");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < nev; i++) {
            struct client_s * client = malloc(sizeof(struct client_s));
            
            if (events[i].ident == sockfd) {
                client->fd = accept(sockfd, &client->addr, &client->addrlen);

                if (client->fd == -1) {
                    perror("Error on accepting client.\n");
                    exit(EXIT_FAILURE);
                }

                client->type = 2;

                EV_SET(&changes[1], client->fd, EVFILT_READ, 
                        EV_ADD | EV_ENABLE, 0, 0, client);
            }
            
            if (events[i].udata) {
                client = events[i].udata;

                printf("fd %d, type %d\n", client->fd, client->type);

                if (client->type == 2) {
                    recv(client->fd, buffer, 1024, MSG_WAITALL);
                    printf("client says: %s\n", buffer);
                }
            }
        }
    }

    close(sockfd);

    freeaddrinfo(addr);

    return EXIT_SUCCESS;
}

Can it also be that it is not possible to use kqueue on sockets on OS X?

Regards,
Bodo
 
We support FreeBSD, not OS X (and that includes bits of FreeBSD on OS X). Have you tried asking on an OS X forum?
 
Corrected your source snippet to work: http://pastebin.com/kVeRUr8W

Main problem was that you did not clear the change array so it contained memory residue, also you should not pass changes to kevent() that were not initialized via EV_SET macro.

Keep in mind you still need to implement client disconnects and cleanup for their socket events.

P.S.

Should not make a difference in this case if its *BSD or OSX. Kqueue API was taken directly from FreeBSD.
 
@DutchDaemon, there is no system development OS X forum. kqueue is traditionally FreeBSD.

@expl, thank you for correcting the source code!

I am currently not 100% happy with

Code:
int c_n = 1;
if (changes[1].ident) {
    c_n = 2;
}

to filter socket events. I tried to use separate event lists for this but always had some wrong events.

Do you have a recommendation for further reading (especially examples) about this use case?
 
Last edited by a moderator:
Why don't you separate the registration from the retrieval? e.g.
Code:
//register the first event

EV_SET(&changes[0], sockfd, EVFILT_READ, 
       EV_ADD | EV_ENABLE | /*EV_ONESHOT*/ [U][B]EV_CLEAR | EV_RECEIPT[/B][/U], 0, 0, 0);

kevent(kq, changes, 1, events, 2, NULL);

//...

//retrieve events
nev = kevent(kq, NULL, 0, events, 2, NULL);

//...

//register the second event

EV_SET(&changes[1], client->fd, EVFILT_READ, 
       EV_ADD | EV_ENABLE [U][B]| EV_CLEAR | EV_RECEIPT[/B][/U], 0, 0, client);

kevent(kq, changes + 1, 1, events, 2, NULL);
Kevin Barry
 
@ta0kira, this looks good! Will use this approach.

@nslay, this is a quite big dependency for someone who just wants to test out some C programming. But thank you for the recommendation.
 
Last edited by a moderator:
bsus said:
@ta0kira, this looks good! Will use this approach.

@nslay, this is a quite big dependency for someone who just wants to test out some C programming. But thank you for the recommendation.

It's really not a big dependency. The whole library is basically a loop implemented very carefully. And this event loop is what you would normally implement in a standard socket program anyway. And on top of that, it has some helper functions.

Event loops themselves are easy to implement. Event loops with timer queues and signal handling are also easy to implement. It's also easy to introduce bugs that are extremely hard to find and debug (which is why you shouldn't implement your own fancy event loop to begin with).

Anyway, just from personal experience, I really recommend using something like libevent instead of using the low level multiplexers directly. You gain correctness, portability, and high performance with little to no overhead (and it's easier to use). It's also easier to think about IO in terms of events and callbacks.
 
Last edited by a moderator:
Back
Top