Theldus / wsServer

wsServer - a tiny WebSocket server library written in C
https://theldus.github.io/wsServer
GNU General Public License v3.0
422 stars 80 forks source link

How does server send message to the browser at any time? #34

Closed TrespassingoO closed 2 years ago

TrespassingoO commented 2 years ago

How to modify the code to implement that the server send message to the browser at any time once a it 'onopen'? Forgive me for asking this question which may seem stupid, but it is still hard for me.>_<

Theldus commented 2 years ago

No worries, your question is relevant =)

Once the client has connected, you have 2 options for sending messages:

Option a) is event-based and follows the default server mode. To send messages asynchronously (b) you need to start the server in a separate thread, so you can continue running your program normally after invoking ws_socket. To do this, call ws_socket with the third parameter equal to 1, that is: ws_socket(&evs, 8080, 1).

A similar question was raised a few months ago, but in summary, you could do something like:

int main(void)
{
    int i;
    struct ws_events evs;
    evs.onopen    = &onopen;
    evs.onclose   = &onclose;
    evs.onmessage = &onmessage;
    ws_socket(&evs, 8080, 1); /* listen to connections in a new thread. */

    do_something(); 
    ws_sendframe_txt(fd, "Hello, World!", true);

    return (0);
}
TrespassingoO commented 2 years ago

Appreciate the reply ! As the option b) , how can I get the fd from the onopen() once the client connected, actually, I'm coding to encapsulate this ws_sendframe_txt() and provide an interface send_to_web(), so that another func can use it to send data(jason format) to browser by send_to_web(data), I have no idea, sorry...

Theldus commented 2 years ago

how can I get the fd from the onopen() once the client connected

onopen method tells you that... see its signature: void onopen(int fd);.

Unfortunately wsServer doesn't publicly export the list of connected clients (this is a really good idea, I'll implement it), so what you can do for the moment is save the client's fd manually.

You can save this fd in several ways (not tested):

static void onopen(int fd) { struct list p = malloc(sizeof(p)); p->client_fd = fd; p->next = head; head = p; }

int main(void) { head = calloc(1, sizeof(*head)); }

- as an element of a static array (recommended):
```c
static int clients[MAX_CLIENTS];
static int client_idx;

static void onopen(int fd) {
    clients[client_idx++] = fd;
}

Alternatively, you can also send a broadcast message to all connected clients so far, the last parameter of ws_sendframe_txt signals whether broadcast or not, the signature: ws_sendframe_txt(int fd, const char *msg, bool broadcast); but I don't know how useful that would be for your use case.

Please do not close this issue, I intend to work on this feature (export clients list) as soon as possible.

TrespassingoO commented 2 years ago

oh, you are angel ! actually there are only one client connects, for showing real-time data from server. I just prepared to maintain a global queue of string, and then keep sending the data in the queue to the browser, just like:

void onopen(int fd)
{
    while(1) {
        if(!queue.empty()) {
            msg = queue.top();
            queue.pop();
            ws_sendframe_txt(fd, msg, false);
        }
    }
}

and at the same time put the data in the queue in another place, I konw it looks rough, and then I don't know how to implement the queue of dynamic array of char(just like queue\<string> in c++) , it is another question. but your ways looks more appropriate, I will try it:

int client_fd;
static void onopen(int fd)
{
    client_fd = fd;
}
int main(void)
{
    struct ws_events evs;
    evs.onopen    = &onopen;
    evs.onclose   = &onclose;
    evs.onmessage = &onmessage;
    ws_socket(&evs, 8080, 1);
    while(1)
        {
            if(client_fd != 0 && !queue.empty())
            {
                ws_sendframe_txt(client_fd, queue.front(), true);
                queue.pop();
            }
        }
    return (0);
}

sorry, I used to using C++, long time no use C...

TrespassingoO commented 2 years ago

How to convert this library to c++, of course this library is ok, but I want to modify and merge it to a c++ project, and then create an another thread to run the function which get data and push data to queue, I can use queue\<string> easily as well. maybe I ask too much...

Theldus commented 2 years ago

I just prepared to maintain a global queue of string, and then keep sending the data in the queue to the browser, just like:

Please do not put infinite loops inside onopen!. The onopen, onmessage and onclose methods run on the same thread as the server executes, so as long as you don't return from these methods, the server is unable to process new messages, so don't do that.

Prefer to set flags and take quick actions within these methods, if you want to process something longer and time consuming, consider creating a worker thread and add each 'onmessage' to a job queue.

but your ways looks more appropriate, I will try it:

Your second approach is much better and more in line with what I said above.

A small improvement I would make would be regarding your infinite loop... if your client connects right away, it's 'ok', but if you have no idea how long your client will take to connect (if client_fd != 0), don't waste CPU cycles in an infinite loop for nothing, use a barrier instead.

I would do something like:

#define _POSIX_C_SOURCE 200112L
#include <pthread.h>

pthread_barrier_t has_client;
int client_fd;

static void onopen(int fd)
{
    client_fd = fd;
    pthread_barrier_wait(&has_client);
}
int main(void)
{
    struct ws_events evs;
    evs.onopen    = &onopen;
    evs.onclose   = &onclose;
    evs.onmessage = &onmessage;
    ws_socket(&evs, 8080, 1);

    /* initialize our barrier. */
    pthread_barrier_init(&has_client, NULL, 2));

    /* wait for a client. */
    pthread_barrier_wait(&has_client);

    /* fill our queue. */

    while(1)
    {
        if(!queue.empty())
        {
            ws_sendframe_txt(client_fd, queue.top(), true);
            queue.pop();
        }
    }    

    pthread_barrier_destroy(&has_client);
    return (0);
}

How to convert this library to c++, of course this library is ok, but I want to modify and merge it to a c++ project, and then create an another thread to run the function which get data and push data to queue, I can use queue easily as well. maybe I ask too much...

Oh, you're right... so far wsServer would not be able to compile in C++ projects as it was not using "C linkage". I just pushed a commit (baee7bd4daaf008293ea31e0171a4fc21b62d997) that adds support for it.

Now you can compile it against C++ projects in the usual way (assuming wsServer is installed on the system):

g++ myexample.cc -o myexample -lws

Now if your question concerns how to turn wsServer into OOP and add classes and etc to it, then that's out of my reach... I'm definitely not a C++ programmer.

But feel free if you want to do that in your project, wsServer is quite simple, has few methods and etc... it shouldn't be complicated for a well-versed C++ programmer.

TrespassingoO commented 2 years ago

Oh, angel ! I truly appreciate your answers and patiences. By taking your advice, I get what I want. Actually, I almost gave up and went to find other ways, you gave me hope to continue to modify the code whenever you give an advice(and more than one !) I couldn’t have done it without you. The advice about 'onopen', adding 'barrier' and "C linkag" all definely solve my problem which are, or would meeting. I learn a lot from this issue and you. Indeed, it is my first time to issue, also is an amazing experience. Thank you for your patience again !✨

Theldus commented 2 years ago

Thank you for the kind words, means a lot to me =). I'm glad that you managed to make progress with the library, maybe my project lacks examples, so the learning step could be more easier/smooth.

I learn a lot from this issue and you. Indeed, it is my first time to issue, also is an amazing experience.

That's the spirit of open source, always learning and sharing knowledge, ;-).

Theldus commented 2 years ago

I think I can close this issue now.

The main issue: knowing the client's fd beforehand to send the message) was solved in #45. In #45 the broadcast to all clients no longer requires prior knowledge of any connected client, which I believe solves the problem of 'saving the fd' of a connected client.

Feel free to reopen it if you have additional questions or create another issue if necessary.