Related
So I'm trying to learn to write Linux modules and right now I'm experimenting with a basic "Hello World" module:
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void){
printk(KERN_ALERT "Hello, world.\n");
return 0;
}
static void hello_exit(void){
printk(KERN_ALERT "goodbye.\n");
}
module_init(hello_init);
module_exit(hello_exit);
And I've finally gotten this module to work! When I add with insmod it prints "hello" to kernel.log and when I remove it with remmod it prints "goodbye" to kernel.log.
My trouble is that I decided I want to try and get the output to also print to the console. From what I understand about printk(), is that in order for messages to show up in the console, the console must be set to the appropriate message level in /proc/sys/kernel/printk. (This is all according to https://elinux.org/Debugging_by_printing). My console is set to level 4.
cat /proc/sys/kernel/printk:
4 4 1 7
Since KERN_ALERT is level 2 and my console is set to print out level 4 and below messages, why are the printk messages not appearing on my console? When I run dmesg I can see the messages are clearly in the buffer, but never go to the console. It's not I really need them to print to the console, but I really want to understand how this all works.
I hope i can answer to your question. I also faced same issue and tried my level best to print kernel message to console, but nothing works. Then I started searching for the reason...
The reason is, If klogd is not running, the message won’t reach user space unless you read /proc/kmsg. Reference: oreilly or /dev/kmsg. klogd reads kernel log messages and helps process and send those messages to the appropriate files, sockets or users. since absence of daemon, it won't send to standard output. Unless you read the message from ring buffer or buffer overflow, it will remains.
I am currently working on a C program running on a Raspberry Pi 3 (Linux Ubuntu) that is intended to provide a web page interface for configuring networking on an embedded system.
The code is being developed using Code::Blocks with the GDB debugger. I'm using microhttpd for the web server and that, plus the various web pages, are all working great. I'm now working on the USB Serial link to the embedded system using information in "Serial Programming Guide for POSIX Operating Systems".
The code below is responsible for opening the USB Serial link to the target system and seems to work fine - once. If I close the program and restart it (either standalone on the command line or from within Code::Blocks) the second time microhttpd is hosed - browser windows will no longer connect. Further, from within Code::Blocks the debugger is also hosed - once the program is started it cannot be paused or stopped. The only way is to kill it by closing the project.
The problem is clearly within the function since I can comment out the call to it and everything works as it did previously. Unfortunately, once the problem happens the only solution seems to be to reboot the Pi.
I've done things like this before using a scripting language (Tcl) but this time around I'm looking for a performance boost from a non-interpreted language since the Pi will also be running a high bandwidth data logging program through a similar USB serial interface.
The code is shown below:
/******************************************************************************/
/* This function scans through the list of USB Serial ports and tries to */
/* establish communication with the target system. */
/******************************************************************************/
void tapCommInit(void) {
char line[128];
char port[15]; // this is always of the form "/dev/TTYACMn"
char *ptr;
FILE *ifd;
struct termios options;
uint8_t msgOut[3], msgIn[4];
msgOut[0] = REQ_ID; // now prepare the message to send
msgOut[1] = 0; // no data so length is zero
msgOut[2] = 0;
/**************************************************************************/
/* First, get the list of USB Serial ports. */
/**************************************************************************/
system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list
ifd = fopen("usbSerial", "r");
logIt(fprintf(lfd, "serial ports: \n"));
/**************************************************************************/
/* The main loop iterates through the file looking for lines containing */
/* "tty" which should be a valid USB Serial port. The port is configured */
/* in raw mode as 8N1 and an ID request command is sent, which has no */
/* data. If a response is received it's checked to see if the returned */
/* ID is a match. If not, the port is closed and we keep looking. If a */
/* match is found, tapState is set to "UP" and the function returns. If */
/* no match is found, tapState is left in the initial "DOWN" state. */
/**************************************************************************/
while(1) {
if (fgets(line, 127, ifd) == NULL) { // end of file?
break; // yes - break out and return
}
ptr = strstr(line, "tty"); // make sure the line contains a valid entry
if (ptr == NULL) {
continue; // nothing to process on this line
}
strcpy(port, "/dev/"); // create a correct pathname
strcat(port, ptr); // append the "ttyACMn" part of the line
port[strlen(port)-1] = 0; // the last character is a newline - remove it
logIt(fprintf(lfd," %s\n", port)); // we have a port to process now
cfd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); // cfd is a global int
if (cfd == -1) {
logIt(fprintf(lfd, "Could not open port: %s\n", port));
continue; // keep going with the next one (if any)
}
fcntl(cfd, F_SETFL, 0); // blocking mode
tcgetattr(cfd, &options); // get the current port settings
options.c_cflag |= (CLOCAL | CREAD); // ena receiver, ignore modem lines
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw, no echo
options.c_oflag &= ~OPOST; // no special output processing
options.c_cc[VMIN] = 0; // minimum number of raw read characters
options.c_cc[VTIME] = 10; // timeout in deciseconds (1 second timeout)
tcsetattr(cfd, TCSANOW, &options); // set options right now
cfsetispeed(&options, B115200); // input baud rate
cfsetospeed(&options, B115200); // output baud rate
options.c_cflag &= ~(CSIZE | PARENB | // clear size bits, no parity
CSTOPB | CRTSCTS); // 1 stop bit, no hw flow control
options.c_cflag |= CS8; // now set size: 8-bit characters
options.c_cflag &= ~(IXON | IXOFF | IXANY); // no sw flow control
if (write(cfd, msgOut, 3) < 3) {
logIt(fprintf(lfd, "Sending of output message failed\n"));
close(cfd);
continue;
}
if (read(cfd, msgIn, 4) != 4) {
logIt(fprintf(lfd, "Didn't get expected amount of return data\n"));
close(cfd);
continue;
}
if (msgIn[3] != HOST_ID) {
logIt(fprintf(lfd, "Got the wrong HOST_ID response\n"));
close(cfd);
continue;
}
logIt(fprintf(lfd, "Port found - communication established\n"));
tapState = UP;
break; // we're done - break out of the loop
}
fclose(ifd); // close and remove the file we created
remove("usbSerial");
}
from within Code::Blocks the debugger is also hosed - once the program is started it cannot be paused or stopped
It is far more likely that you do not understand your tools than that you have created an unkillable program.
It's easy enough to figure this out: divide and conquer. You've got a whole pile of unrelated components here. Start separating them and find out which pieces work fine in isolation and which continue to behave badly when disconnected from everything else. Then you'll have your culprit.
Specifically here, that means try running your program outside the IDE, then under command line gdb instead of GDB via the IDE.
Also, it should be possible to run your program without starting the web server piece, so that you can run the serial part of the app in isolation. This is not only good for debugging by minimizing confounding variables, it also encourages a loosely-coupled program design, which is a good thing in its own right.
In the end, you may find that the thing keeping your program from stopping is the web framework, Code::Blocks, or the way GDB operates on the Pi under Code::Blocks, rather than anything to do with the USB to serial adapter.
once the problem happens the only solution seems to be to reboot the Pi
If your program is still running in the background, then of course your next instance will fail if it tries to open the same USB port.
Don't guess, find out:
$ sudo lsof | grep ttyACM
or:
$ lsof -p $(pidof myprogram)
(Substitute pgrep if your system doesn't have pidof.)
I've done things like this before using a scripting language (Tcl) but this time around I'm looking for a performance boost from a non-interpreted language
Your serial port is running at 115,200 bps. Divide that by 10 to account for the stop and start bits, then flip the fraction to get seconds per byte, and you come to 87 microseconds per byte. And you only achieve that when the serial port is running flat-out, sending or receiving 11,500 bytes per second. Wanna take a guess at how many lines of code Tcl can interpret in 87 microseconds? Tcl isn't super-fast, but 87 microseconds is an eternity even in Tcl land.
Then on the other side of the connection, you have HTTP and a [W]LAN, likely adding another hundred milliseconds or so of delay per transaction.
Your need for speed is an illusion.
Now come back and talk to me again when you need to talk to 100 of these asynchronously, and then maybe we can start to justify C over Tcl.
(And I say this as one whose day job involves maintaining a large C++ program that does a lot of serial and network I/O.)
Now lets get to the many problems with this code:
system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list
ifd = fopen("usbSerial", "r");
Don't use a temporary where a pipe will suffice; use popen() here instead.
while(1) {
This is simply wrong. Say while (!feof(ifd)) { here, else you will attempt to read past the end of the file.
This, plus the next error, is likely the key to your major symptoms.
if (fgets(line, 127, ifd) == NULL) {
break;
There are several problems here:
You're assuming things about the meaning of the return value that do not follow from the documentation. The Linux fopen(3) man page isn't super clear on this; the BSD version is better:
The fgets() and gets() functions do not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred.
Because fgets() is Standard C, and not Linux- or BSD-specific, it is generally safe to consult other systems' manual pages. Even better, consult a good generic C reference, such as Harbison & Steele. (I found that much more useful than K&R back when I was doing more pure C than C++.)
Bottom line, simply checking for NULL doesn't tell you everything you need to know here.
Secondarily, the hard-coded 127 constant is a code bomb waiting to go off, should you ever shrink the size of the line buffer. Say sizeof(line) here.
(No, not sizeof(line) - 1: fgets() leaves space for the trailing null character when reading. Again, RTFM carefully.)
The break is also a problem, but we'll have to get further down in the code to see why.
Moving on:
strcat(port, ptr); // append the "ttyACMn" part of the line
Two problems here:
You're blindly assuming that strlen(ptr) <= sizeof(port) - 6. Use strncat(3) instead.
(The prior line's strcpy() (as opposed to strncpy()) is justifiable because you're copying a string literal, so you can see that you're not overrunning the buffer, but you should get into the habit of pretending that the old C string functions that don't check lengths don't even exist. Some compilers will actually issue warnings when you use them, if you crank the warning level up.)
Or, better, give up on C strings, and start using std::string. I can see that you're trying to stick to C, but there really are things in C++ that are worth using, even if you mostly use C. C++'s automatic memory management facilities (not just string, but also auto_ptr/unique_ptr and more) fall into this category.
Plus, C++ strings operate more like Tcl strings, so you'll probably be more comfortable with them.
Factual assertions in comments must always be true, or they are likely mislead you later, potentially hazardously so. Your particular USB to serial adapter may use /dev/ttyACMx, but not all do. There's another common USB device class used by some serial-to-USB adapters that causes them to show up under Linux as ttyUSBx. More generally, a future change may change the device name in some other way; you might port to BSD, for example, and now your USB to serial device is called /dev/cu.usbserial, blowing your 15-byte port buffer. Don't assume.
Even with the BSD case aside, your port buffer should not be smaller than your line buffer, since you are concatenating the latter onto the former. At minimum, sizeof(port) should be sizeof(line) + strlen("/dev/"), just in case. If that seems excessive, it is only because 128 bytes for the line buffer is unnecessarily large. (Not that I'm trying to twist your arm to change it. RAM is cheap; programmer debugging time is expensive.)
Next:
fcntl(cfd, F_SETFL, 0); // blocking mode
File handles are blocking by default in Unix. You have to ask for a nonblocking file handle. Anyway, blasting all the flags is bad style; you don't know what other flags you're changing here. Proper style is to get, modify, then set, much like the way you're doing with tcsetattr():
int flags;
fcntl(cfd, F_GETFL, &flags);
flags &= ~O_NONBLOCK;
fcntl(cfd, F_SETFL, flags);
Well, you're kind of using tcsetattr() correctly:
tcsetattr(cfd, TCSANOW, &options);
...followed by further modifications to options without a second call to tcsetattr(). Oops!
You weren't under the impression that modifications to the options structure affect the serial port immediately, were you?
if (write(cfd, msgOut, 3) < 3) {
logIt(fprintf(lfd, "Sending of output message failed\n"));
close(cfd);
continue;
}
Piles of wrong here:
You're collapsing the short-write and error cases. Handle them separately:
int bytes = write(cfd, msgOut, 3);
if (bytes == 0) {
// can't happen with USB, but you may later change to a
// serial-to-Ethernet bridge (e.g. Digi One SP), and then
// it *can* happen under TCP.
//
// complain, close, etc.
}
else if (bytes < 0) {
// plain failure case; could collapse this with the == 0 case
// close, etc
}
else if (bytes < 3) {
// short write case
}
else {
// success case
}
You aren't logging errno or its string equivalent, so when (!) you get an error, you won't know which error:
logIt(fprintf(lfd, "Sending of output message failed: %s (code %d)\n",
strerror(errno), errno));
Modify to taste. Just realize that write(2), like most other Unix system calls, has a whole bunch of possible error codes. You probably don't want to handle all of them the same way. (e.g. EINTR)
After closing the FD, you're leaving it set to a valid FD value, so that on EOF after reading one line, you leave the function with a valid but closed FD value! (This is the problem with break above: it can implicitly return a closed FD to its caller.) Say cfd = -1 after every close(cfd) call.
Everything written above about write() also applies to the following read() call, but also:
if (read(cfd, msgIn, 4) != 4) {
There's nothing in POSIX that tells you that if the serial device sends 4 bytes that you will get all 4 bytes in a single read(), even with a blocking FD. You are especially unlikely to get more than one byte per read() with slow serial ports, simply because your program is lightning fast compared to the serial port. You need to call read() in a loop here, exiting only on error or completion.
And just in case it isn't obvious:
remove("usbSerial");
You don't need that if you switch to popen() above. Don't scatter temporary working files around the file system where a pipe will do.
I tried to make a connection to my mail server which is in local area network. The ip of mail server is 192.168.1.1. So, I tried the
following program to test that.
Program:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
int main()
{
struct sockaddr_in sa;
struct in_addr ip;
int fd=socket(AF_INET,SOCK_STREAM,0);
if(inet_pton(AF_INET,"192.168.1.1",&ip)==-1){
printf("Unable to convert ip to binary\n");
perror("");
exit(1);
}
sa.sin_family=AF_INET;
sa.sin_port=25;
sa.sin_addr=ip;
if(connect(fd,(struct sockaddr*)&sa,sizeof(sa))==-1){
printf("Unable to connect to server\n");
perror("");
exit(1);
}
else{
printf("Successfully connected to server...\n");
}
}
Output:
$ ./a.out
Unable to connect to server
Connection refused
$
But via telnet, it is successfully connected as shown below.
$ telnet 192.168.1.1 25
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
220 mail.msys.co.in ESMTP Postfix (Debian/GNU)
^]
telnet> Connection closed.
$
So, what is the mistake I done here. Is there anything wrong in my program. I request you to help me solve this problem and why it occurs.
Disregarding any other problems, what causes the direct breakage in the question is (almost certainly, barring "unexpected" host architecture):
sa.sin_port=25;
What you need is something like this:
sa.sin_port = htons(25);
Ie, you have the wrong byte order for the port number, meaning it will be interpreted as some other number entirely.
From htons(3):
The htons() function converts the unsigned short integer hostshort from
host byte order to network byte order.
[snip]
On the i386 the host byte order is Least Significant Byte first,
whereas the network byte order, as used on the Internet, is Most Sig‐
nificant Byte first.
Even if you were developing on an architecture where the host byte order matched the network byte order (ie, both MSB), you'd want to do the conversion to allow for portability.
How do I get a forked, execve() child process that can run 'vi', etc. and redirect all IO to the parent process?
I'm trying to pass shells through from an embedded Linux process to the PC software interface connected over the network. The IO for the shell process is packaged into app-specific messages for network transport over our existing protocol.
First I was just redirecting IO using simply pipe2(), fork(), dup2(), and execve(). This didn't give me a tty on the remote side, so screen, etc. didn't work.
Now I'm using forkpty, and screen mostly works, but many many other don't (vi, stty, etc). It appears the current problem is that the child process doesn't control the tty.
I've been experimenting with TIOCSCTTY, but haven't had much luck.
Here's more or less what I've got:
bool ExternalProcess::launch(...)
{
...
winsize winSize;
winSize.ws_col = 80;
winSize.ws_row = 25;
winSize.ws_xpixel = 10;
winSize.ws_ypixel = 10;
_pid = forkpty(&_stdin, NULL, NULL, &winSize);
//ioctl(_stdin, TIOCNOTTY, NULL);
if (!_pid && (_pid != -1))
{
// this is the child process
char tty[4096];
strncpy(tty, ttyname(STDIN_FILENO), sizeof(tty));
tty[sizeof(tty)-1]=0;
FILE* fp = fopen("debug.txt", "wt"); // no error checking - temporary test code
fprintf(fp, "slave TTY %s", tty);
//if (ioctl(_stdin, TIOCSCTTY, NULL) < 0)
if (ioctl(STDIN_FILENO, TIOCSCTTY, NULL) < 0)
{
fprintf(fp, "ioctl() TIOCSCTTY %s\n", strerror(errno));
fflush(fp);
}
else
{
fprintf(fp, "SET CONTROLLING TTY!");
fflush(fp);
}
fclose(fp);
// command, args, env populated elsewhere
execve(command, args, env);
...
// fail path
_exit(-1);
return false;
}
_stdout = _stdin;
...
// enter select() loop reading/writing _stdin, _stdout
}
I am getting results in the debug file like:
slave TTY /dev/pts/5
SET CONTROLLING TTY!
but still many apps are failing with tcsetattr() errors. Am I right in thinking this is a controlling tty problem? How do I fix it?
EDIT
Minor correction. When I do the ioctl TIOCSCTTY on STDIN_FILENO, then it works as in the debug file above, but the IO redirection back to the parent process is disrupted.
EDIT 2
Okay, I'm starting to understand this better. Looking at the kernel source for the ioctl behind tcsetattr(), the processes I am calling are being sent SIGTTIN and SIGTTOU when trying to change the tty.
Only a foreground process can do that, and they're running as if they're background processes. I tried setting those signals to SIG_IGN after forking and before the execve(), but that didn't work. The semantics of this I understand, but it's safe in my redirection scenario for the execve()'d processes to act as if they're foreground processes. The question is... how to make it so? I will continue to search in the kernel code for clues.
Ugh! It's bash, the shell I was calling with execve().
If it detects that stderr is not attached to a tty, then it enters this special mode where child processes cause SIGTTOU.
I found a mention of this problem here.
When I stopped redirecting stderr away from the tty, then it now seems to work as planned.
Assume a web application (in my case, bismon; a GPLv3+ program which is work in progress, so alpha state, and not even released; I am incrementally writing bismon-chariot-doc.pdf which is a draft report describing it; skip the first few bureaucratic pages.) which is (or should be) a single web page application. That web application uses a Web socket for asynchronous messages sent from web application (the web "server") to all tabs of the same page. That websocket is not used for communication from web browser tabs to web server. There is one bismon multi-threaded server process (running on Linux), and it is a specialized web server (the same one for both tabs in the question).
Assume the user is using a recent web browser on Linux (e.g. Firefox 60.5 at least). He/She is doing "open in new tab" on some hyperlink (which points to the same web application server process and the same page). My guess is that an existing WebSocket might be shared between the two tabs (if that is impossible, why?). Then what happens when the Web server is sending a JSON message on that WebSocket? Do the two tabs get it "in parallel"? Why and how? If that is impossible, why and how?
The closest MCVE I have is my onionwebsocket.c example on github (an adaptation of libonion's examples/websockets/websockets.c code). Code given below, of course a recent libonion (an HTTP server library) is required. Each tab is handled by its own thread. It does not share websockets between tabs, but I can't understand how even I would try to.
I would like websockets (with data asynchronously flowing from server to browser) to implicitly have some "locking" (or serialization) of each WebSocket message. I am not even sure if it is possible or makes sense. How do browsers do in practice? AFAIK, each tab of my Firefox has its own process (but actually not, it looks a tab corresponds to a browser pthread), so how would these tabs share some "state" like a WebSocket? How two tabs in the same browser share data and websockets? How do they "synchronize"? What is happening inside bismon when some thread is writing to the same WebSocket, with the browser having two tabs handling incoming messages from it?
For completeness, here is the onionwebsocket.c code I've got (but I don't understand all the subtleties of WebSockets - I find their specification too breve.).
/**
onionwebsocket.c: my adaptation of Libonion's
examples/websockets/websockets.c to learn more about websockets.
Libonion is on https://github.com/davidmoreno/onion
Copyright (C) 2010-2019 David Moreno Montero, Basile Starynkevitch
and others
This [library] example is free software; you can redistribute it
and/or modify it under the terms of, at your choice:
a. the Apache License Version 2.0.
b. the GNU General Public License as published by the
Free Software Foundation; either version 2.0 of the License,
or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of both licenses, if not see
<http://www.gnu.org/licenses/> and
<http://www.apache.org/licenses/LICENSE-2.0>.
*/
#include <features.h>
#include <onion/log.h>
#include <onion/onion.h>
#include <onion/websocket.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <stdatomic.h>
#include <stdio.h>
#ifndef HAVE_GNUTLS
// on Debian, install libcurl4-gnutls-dev & gnutls-dev pacakges before
// building libonion
#error without HAVE_GNUTLS but onion needs it
#endif
onion_connection_status websocket_example_cont (void *data,
onion_websocket * ws,
ssize_t data_ready_len);
onion_connection_status
websocket_example (void *data, onion_request * req, onion_response * res)
{
onion_websocket *ws = onion_websocket_new (req, res);
if (!ws)
{
time_t nowtim = time (NULL);
char timbuf[80];
static atomic_int acnt;
atomic_fetch_add (&acnt, 1);
memset (timbuf, 0, sizeof (timbuf));
strftime (timbuf, sizeof (timbuf), "%c", localtime (&nowtim));
onion_response_write0 (res,
"<html><body><h1 id='h1id'>Easy echo</h1>\n");
onion_response_printf (res,
"<p>Generated <small><tt>%s</tt></small>, count %d, pid %d</p>\n",
timbuf, acnt, (int) getpid ());
onion_response_write0 (res,
"<pre id=\"chat\"></pre>"
" <script>\ninit = function(){\n"
"msg=document.getElementById('msg');\n"
"msg.focus();\n\n"
"ws=new WebSocket('ws://'+window.location.host);\n"
"ws.onmessage=function(ev){\n document.getElementById('chat').textContent+=ev.data+'\\n';\n"
"};}\n"
"window.addEventListener('load', init, false);\n</script>\n"
"<input type=\"text\" id=\"msg\" onchange=\"javascript:ws.send(msg.value); msg.select(); msg.focus();\"/><br/>\n"
"<button onclick='ws.close(1000);'>Close connection</button>\n"
"<p>To <a href='#h1id'>top</a>.\n"
"Try to <i>open in new tab</i> that link</p>\n"
"</body></html>");
printf ("websocket created acnt=%d timbuf=%s\n", acnt, timbuf);
fflush (NULL);
return OCS_PROCESSED;
}
onion_websocket_printf (ws,
"Hello from server. Write something to echo it. ws#%p",
ws);
onion_websocket_set_callback (ws, websocket_example_cont);
return OCS_WEBSOCKET;
}
onion_connection_status
websocket_example_cont (void *data, onion_websocket * ws,
ssize_t data_ready_len)
{
char tmp[256];
if (data_ready_len > sizeof (tmp))
data_ready_len = sizeof (tmp) - 1;
int len = onion_websocket_read (ws, tmp, data_ready_len);
if (len <= 0)
{
ONION_ERROR ("Error reading data: %d: %s (%d)", errno, strerror (errno),
data_ready_len);
return OCS_NEED_MORE_DATA;
}
tmp[len] = 0;
onion_websocket_printf (ws, "Echo: %s (ws#%p)", tmp, ws);
ONION_INFO ("Read from websocket ws#%p: %d: %s", ws, len, tmp);
return OCS_NEED_MORE_DATA;
}
int
main ()
{
onion *o = onion_new (O_THREADED);
onion_set_port (o, "8087");
onion_set_hostname (o, "localhost");
onion_url *urls = onion_root_url (o);
onion_url_add (urls, "", websocket_example);
onion_listen (o);
onion_free (o);
return 0;
}
/****************
** for Emacs...
** Local Variables: ;;
** compile-command: "gcc -o onionwebsocket -Wall -rdynamic -I/usr/local/include/ -O -g -DHAVE_GNUTLS onionwebsocket.c -L /usr/local/lib $(pkg-config --cflags --libs gnutls) -lonion" ;;
** End: ;;
****************/
To run that example, use http://localhost:8087/ in your Linux browser after starting the ./onionwebsocket program whose compilation command is near the end of the code.
Ideally, I would like all the tabs to share the same websocket connection and the webserver to send a "broadcasted" message to all tabs of the same browser (hopefully that the actual "broadcast" or "multicast" happens on the browser side). But I believe it is not possible (but I don't understand why).
I'm reading here that "the Web Content process is the tab". I don't understand what that means. On my Debian/Instable I don't have it, but I do observe with ps auxw | grep firefox several firefox-esr processes with for example arguments similar to -contentproc -childID 1 -isForBrowser which seems to be undocumented options of the Firefox browser.
This answer could be related, but I don't understand why and how.
In other words, what is supposed to happen if the same WebSocket is used by two tabs and is receiving (browser-side) the same JSON message? Or is that impossible? Why?
PS. I do admit I am too lame to dive into the 50MLOC of firefox