Socket.receive_message usage?

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 :thinking:
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?

#!/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.bind(Gio.InetSocketAddress.new(Gio.InetAddress.new_any(Gio.SocketFamily.IPV4), 48895), true);
socket.set_blocking(false);
bsock_source = socket.create_source(GLib.IOCondition.IN, Gio.Cancellable.new());
bsock_source_stream = new Gio.UnixInputStream({fd:socket.get_fd(), close_fd:false});
bsock_source.set_callback( () => {
  try {
        /*
        const bytes = bsock_source_stream.read_bytes(4096, null).toArray();
        const decoder = new TextDecoder();
        log(`Received: ${decoder.decode(bytes)}`);
        */
        var msg_vectors = [];
        msg_vectors.push(new Gio.InputVector);
        let ret = socket.receive_message(msg_vectors, Gio.PEEK, null);
        log(`ret addr = ${ret[1].get_address().to_string()}`);
        log(`ret port = ${ret[1].get_port()}`);
        log(`message = ${msg_vectors}`);

    return GLib.SOURCE_CONTINUE;
  } catch (err) {
    log(`XXXXX ${err}`);
    return GLib.SOURCE_REMOVE;
  }
});

bsock_source.attach(null);
loop.run();

The three commented out lines in the callback work correctly to reveal the expected message but don’t allow me to find the source address & port.

Thanks for any help,
chris

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.

There’s an example of this in GSConnect that goes something like this:

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().

Thanks for the reply Andy.

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();

Thanks again,
chris

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.