I’d like to know the source address & port of incoming datagrams. Some googling has suggested using socket.receive_message along with Gio.SocketMsgFlags.PEEK. That much has worked and I’m able to ascertain source address & port OK.
The problem now is how to access the message itself. Should it be in the array of structs passed to receive_message as vector? I’m not sure how to create & pass that in the first place
Trying to log that tells me that it is [boxed instance wrapper GIName:Gio.InputVector jsobj@0x1e6211e89140 native@0x1543320] which I don’t know how to convert to a string (I expect it to be JSON string).
Could someone look over this minimal test code and push me in the right direction please?
Pretty much what you want to do here is “peek” the message to get the host/port, then read as normal. That’s really the point of peeking the message - reading information without affecting it.
onConditionIn(socket, dataInputStream) {
let host, data, json;
// Try to peek the remote address
try {
host = socket.receive_message(
[],
Gio.SocketMsgFlags.PEEK,
null
)[1].address.to_string();
} catch (e) {
logError(e);
}
// Whether or not we peeked the address, we need to read the data or we will
// keep hitting G_IO_IN
try {
dataInputStream.read_line_utf8(null)[0];
// Discard the data if we failed to peek the address
if (host === undefined)
return;
json = JSON.parse(data);
} catch (e) {
logError(e);
}
return GLib.SOURCE_CONTINUE;
}
In the example above, a Gio.DataInputStream is used instead of a bare Gio.UnixInputStream. In the case of reading JSON from a socket, it will usually have a line-feed marking the end of the JSON so you can use Gio.DataInputStream.read_line_utf8() and pass the result to JSON.parse().
It is actually your article that has been the basis of networking in my app. and all I really wanted to do was to add PEEKing for source address & port, but I became sidetracked with how socket.receive_message() was supposed to work.
Anyway, I tried stuff along the lines of your suggestion - however the problem with that was that the dataInputStream.read_line_utf8(null) call would always block. While I was playing around with other incantations (e.g. read_line_async), I realized that I had made a mistake with the PEEK flag - I was wrongly using Gio.PEEK instead of Gio.SocketMsgFlags.PEEK. Correcting that enabled me to go back to my original code, adding reading & text decoding after receive_message().
My resulting full working example of how to obtain the message, as well as source address & port is:
#!/usr/bin/env gjs
const { GLib, Gio } = imports.gi;
let loop = GLib.MainLoop.new(null, false);
var socket = new Gio.Socket({"family":Gio.SocketFamily.IPV4, "type":Gio.SocketType.DATAGRAM, "protocol":Gio.SocketProtocol.UDP});
socket.init(null);
socket.set_blocking(false);
socket.bind(Gio.InetSocketAddress.new(Gio.InetAddress.new_any(Gio.SocketFamily.IPV4), 48895), false);
socket_source_stream = new Gio.UnixInputStream({fd:socket.fd, close_fd:false});
socket_source = socket.create_source(GLib.IOCondition.IN, null);
socket_source.attach(null);
socket_source.set_callback( () => {
try {
// PEEK for source address & port
let ret = socket.receive_message([], Gio.SocketMsgFlags.PEEK, null);
log(`ret addr = ${ret[1].address.to_string()}`);
log(`ret port = ${ret[1].port}`);
// Now read the data
const bytes = socket_source_stream.read_bytes(4096, null).toArray();
const decoder = new TextDecoder();
log(`Received: ${decoder.decode(bytes)}`);
} catch (err) {
log(`XXXXX ${err}`);
}
return GLib.SOURCE_CONTINUE;
});
loop.run();