libwebsockets for C, can i use websocket file descriptor with select()?

4.8k views Asked by At

I am coding with libwebsockets for client which is websocket library for C.

And i want to use websocket file descriptor with select() so that i can handle websocket while handling other events.

and then only if websocket got an event, i can call libwebsocket_service(); to handle websocket event.

So i tried below steps.

  1. Connect websocket through struct libwebsocket *wsi = libwebsocket_client_connect(..) I did also check if the reture value is NULL or not for error.

  2. Get file descriptor through int fd = libwebsocket_get_socket_fd(wsi);

  3. FD_SET(fd, &readFd); and select(maxFd + 1, &readFd, NULL, NULL, NULL);

But it keeps being blocked, though i think it must be wake up since server send message after connection is completed.

---EDIT----

After more testing.

It seems because select() is called before connection is completed. It means before handling LWS_CALLBACK_CLIENT_ESTABLISHED.

I put libwebsocket_service() one more between libwebsocket_client_connect() and select(), so that it can handle LWS_CALLBACK_CLIENT_ESTABLISHED before calling select().

And then it works with select() when it receive some message from server.

It means socket is normally open after handling LWS_CALLBACK_CLIENT_ESTABLISHED?

2

There are 2 answers

0
abligh On BEST ANSWER

You are trying to push water up hill.

Caveat: I've only written a server that does this, not a client; however, the interface is very similar

Firstly, libwebsockets is written on the basis you will use poll or ppoll rather than select. I'm sure it's possible to use select, but your life will be much easier if you use poll or ppoll; rewriting my select() code to use ppoll took about 10 minutes. If you really want to use select, I suggest you get external polling working using ppoll, then rewrite to use select().

Next, look at test-server.c, and specifically how the code changes if EXTERNAL_POLL is defined. You also want to read this bit of the API documentation:

The next four reasons are optional and only need taking care of if you will be integrating libwebsockets sockets into an external polling array.

LWS_CALLBACK_ADD_POLL_FD

libwebsocket deals with its poll loop internally, but in the case you are integrating with another server you will need to have libwebsocket sockets share a polling array with the other server. This and the other POLL_FD related callbacks let you put your specialized poll array interface code in the callback for protocol 0, the first protocol you support, usually the HTTP protocol in the serving case. This callback happens when a socket needs to be added to the polling loop in contains the fd, and len is the events bitmap (like, POLLIN). If you are using the internal polling loop (the "service" callback), you can just ignore these callbacks.

LWS_CALLBACK_DEL_POLL_FD

This callback happens when a socket descriptor needs to be removed from an external polling array. in is the socket desricptor. If you are using the internal polling loop, you can just ignore it.

LWS_CALLBACK_SET_MODE_POLL_FD

This callback happens when libwebsockets wants to modify the events for the socket descriptor in in. The handler should OR len on to the events member of the pollfd struct for this socket descriptor. If you are using the internal polling loop, you can just ignore it.

LWS_CALLBACK_CLEAR_MODE_POLL_FD

This callback occurs when libwebsockets wants to modify the events for the socket descriptor in in. The handler should AND ~len on to the events member of the pollfd struct for this socket descriptor. If you are using the internal polling loop, you can just ignore it.

What this is saying (simply put) is that libwebsockets will call you with these methods and ask you to manipulate your poll array.

Ignoring some complications about locking, you can see test-server.c has them implemented thus:

    case LWS_CALLBACK_ADD_POLL_FD:

            if (count_pollfds >= max_poll_elements) {
                    lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
                    return 1;
            }

            fd_lookup[pa->fd] = count_pollfds;
            pollfds[count_pollfds].fd = pa->fd;
            pollfds[count_pollfds].events = pa->events;
            pollfds[count_pollfds++].revents = 0;
            break;

    case LWS_CALLBACK_DEL_POLL_FD:
            if (!--count_pollfds)
                    break;
            m = fd_lookup[pa->fd];
            /* have the last guy take up the vacant slot */
            pollfds[m] = pollfds[count_pollfds];
            fd_lookup[pollfds[count_pollfds].fd] = m;
            break;

I don't believe (sever side) you need to implement the second two callbacks, test-server.c does not, and I do not.

After you call poll, you need to request libwebsockets to service its own FDs, like so (again from test-server.c):

            /*
             * this represents an existing server's single poll action
             * which also includes libwebsocket sockets
             */

            n = poll(pollfds, count_pollfds, 50);
            if (n < 0)
                    continue;


            if (n)
                    for (n = 0; n < count_pollfds; n++)
                            if (pollfds[n].revents)
                                    /*
                                    * returns immediately if the fd does not
                                    * match anything under libwebsockets
                                    * control
                                    */
                                    if (libwebsocket_service_fd(context,
                                                              &pollfds[n]) < 0)
                                            goto done;

So, let's go back to your specific questions:

So i tried below steps.

  1. Connect websocket through struct libwebsocket *wsi = libwebsocket_client_connect(..) I did also check if the reture value is NULL or not for error.

  2. Get file descriptor through int fd = libwebsocket_get_socket_fd(wsi);

  3. FD_SET(fd, &readFd); and select(maxFd + 1, &readFd, NULL, NULL, NULL);

But it keeps being blocked, though i think it must be wake up since server send message after connection is completed.

Well, apart from the impedance mismatch between select and poll, your problem here would appear to be that

a) you are unconditionally saying the websocket is to be polled for reading, and

b) you are never saying you have data to write to the web socket.

You need to do FD_SET (and FD_CLEAR) on both the read and write FDs as and when you get the appropriate callbacks. You are not doing that. This will cause problems.

0
Diego Betancor On

With select (external select)

in the http.c backup function

case LWS_CALLBACK_ADD_POLL_FD:
  FD_SET(pa->fd, &lista_zocalos);
      if (pa->fd > fd_max)
        fd_max = pa->fd;
  break;

case LWS_CALLBACK_DEL_POLL_FD:
  FD_CLR(pa->fd, &lista_zocalos);
  FD_CLR(pa->fd, &lista_zocalos_escritura);
  break;

case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
  if (!pa->events) //a veces viene a 0                                                                                                       
    break;
  if(pa->events & POLLOUT)
    FD_SET(pa->fd, &lista_zocalos_escritura);
  else
    FD_CLR(pa->fd, &lista_zocalos_escritura);
  break;

in the server.c:

struct lws_pollfd pollfds;
fd_set lista_zocalos, readfds, lista_zocalos_escritura, writefds;
int fd_max;


while(){ //whatever in you while
  struct timeval tiempo;
  tiempo.tv_usec = 50000;  //1 segundo                                                                                                         
  tiempo.tv_sec = 0;

  readfds = lista_zocalos;
  writefds = lista_zocalos_escritura;
  status = select(fd_max + 1, &readfds, &writefds, (fd_set *)0, &tiempo);
  if (status >= 0){
   for (i = 0; i <= fd_max; i++){
     int manda_lws = 0;
     pollfds.revents = 0;
     if (FD_ISSET(i, &readfds)) {
       pollfds.revents = POLLIN;
       ioctl(i, FIONREAD, &caracteres);
       if (caracteres == 0)
         pollfds.revents |= POLLHUP;
       manda_lws++;
     }
     if (FD_ISSET(i, &writefds)) {
       pollfds.revents |= POLLOUT;
       manda_lws++;
     }
     if (manda_lws){
       pollfds.fd = i;
       pollfds.events = (FD_ISSET(i, &lista_zocalos) ? POLLIN : 0) | (FD_ISSET(i, &lista_zocalos_escritura) ? POLLOUT : 0);
       lws_service_fd(context, &pollfds);
     }
   }
 }

}

Hope it helps