Named Pipe IPC: Protocols & Status Codes
I'm basically starting from scratch with my implementation, since I've gone way too deep with my initial approach and need something that I can actually finally work with.
I'm writing an IPC implementation via named pipes. I'm open to other ideas (I don't know if anonymous pipes are appropriate here), but named pipes are definitely what I've settled on.
I cannot use additional NuGet packages and I'm on .NET Standard 2.0 (C# 13, PolySharp).
My requirements are the following:
I will have multiple server implementations. Each server fulfills a different purpose and has multiple methods (I will call them endpoints) pertaining to that purpose.
A server endpoint can be the equivalent to one of these:
Action
, Action<T>
, Func<TResult>
, Func<T, TResult>
. To clarify; an endpoint can optionally receive some request data, and can optionally return some response data.
Each server will be paired up with a client implementation that should simplify calling server endpoints for the user. These clients should return some Result
type to signal success or failure. If the server responds with a failure, the kind should be communicated to the client. This includes unhandled server exceptions.
My idea was to send data via JSON. The client serializes the request; sends it to the server; the server deserializes the data and calls the corresponding endpoint implementation. I'm not sure how to handle figuring out this correspondence.
I'm looking for some ideas on how to implement this in a manner that is reasonably robust and can be extended to more server-client-pairs without many problems.160 Replies
cc @Tanner Gooding @cap5lut if you'd like to take a peek
i'm gonna provide some more implementation ideas in a bit and you can tell me what i should do instead :p
To be a bit less vague, this is about game interop. Unity, Unreal, GameMaker, etc. I need a different server-client-pair for each of those.
To determine the correspondence between the request data the server receives and the actual handler for that endpoint, I was thinking of simply using an enum or multipe enums. The thing that's in the way here is that all servers share some base requests, like
Close
. This makes using multiple enums a bit cumbersome.
Should I use strings instead?
In terms of responses; I was initially using a different enum type for every endpoint in the server. This makes the entire implementation a big mess and was my main source of issues before.
I could possibly use integer status codes, but I'm afraid that will fall flat when it comes to unhandled server exceptions. Should I just send plain strings containing the reason for failure?My idea was to send data via JSONIf you can't use nuget packages, and you don't have S.T.J, then the only json serializer you have is pretty rubbish 😛
oh, i can use that one actually. i use source generation with it. some others that i can use (provided by the app that i'm writing this plugin for) are
System.Memory
and System.Buffers
, for exampleIs there a fully SG'd json library which doesn't need any runtime libraries at all? Which one?
no no i just use stj
Ah no, I see what you mean
i can technically use any package
i il weave everything
but i really want to keep the file size low
It looks like you've got a few different problems here:
1. Defining the different messages types
2. Mapping messages types into RPC methods to call
3. Serializing exceptions
Correct?
i think that's a good summary
How are you defining your schema/interface? I.e. what messages can be sent between a given server and client?
(also note that json-rpc is a thing. Might be worth looking at what they do)
The normal way to approach something like this is that you define your schema centrally (i.e. what the message types are, what the parameters are, etc), then code-generate the server and client from that schema
The code generation handles converting method call -> message on the sender, and message -> method call on the receiver
i'm not sure how i should define them. simply the question of whether to use interfaces or not, whether to use classes or structs...
some of the data i would send would be like
i really don't wanna write a source generator for this, and rpc libraries tend to be pretty large
Yeah, they're large for a reason! Chucking messages back and forth is a lot easier
it's unfortunately not an option for me. i really want the download of the plugin to be quick
i mean it's an option, but not the first i'd like to choose
Just to play devil's advocate -- can't you include any large libraries in your host application?
the host application isn't mine
Ah, your IPC isn't with the host application?
the ipc is between my library (loaded by the host app) and some game
i'm essentially writing both sides, though
Ah gotcha
Can you just pass messages around, rather than going full-on RPC?
that's exactly what i want to do
the server side of course needs handlers
I thought you said you were turning method calls -> messages on the sender, and turning a message -> a method call on the receiver, with automatically serializing exceptions from that method call, etc?
well, i thought i'd have some abstract base client, which contains the raw logic for sending over the request. there would then be several concrete clients which contain methods that hide this raw logic:
where
IpcClient.CallServerEndpoint
would serialize the request and just send it over the pipeYeah, it's the other side which gets a bit painful -- working out what the message type is, mapping that to a method to call, extracting the parameters, marshalling exceptions back, making sure that the response ends up in the same place that the request came from. Do-able, but boilerplatey if you're writing it by hand. At that point, you might as well just deal with the messages directly
Anyway, none of this is answering your original questions, I think
Ah no, it does cover:
I'm not sure how to handle figuring out this correspondence.The short answer is: 1. Lots of hand-written boilerplate 2. Some method to register a method to a message type, but which reduces some of the boilerplate but probably introduces some runtime reflection / codegen 3. Compile-time code generation from a schema 4. Don't, and deal with the messages directly (4 is what I do, FWIW)
u basically have this:
1) transport (fixed via named pipes i guess)
2) serialization (a bit of a struggle but u do it somehow)
3) application layer protocol
4) business logic
3 seems to be what u are stuck at, right?
i've basically nuked the entire project :p
so i'm at 0
well, then u have these 4 layers to work on 😂
which is what this thread is about :)
well, with 1 and 2 there arent problems are there?
just in the sense that i was looking for possible alternatives if they make the process easier, more performant, more consistent, more in line with modern technologies
im not sure what ya mean by that. 1) would mean that u transfer a bunch of bytes, 2) would mean that u interpret that bunch of bytes.
"consistency and modern technologies" would be mostly in the application and business layer
the (de)serialization of 2) as well tho, but thats usually not the problematic thing
i still don't understand what your fear is about implementing this, or what is it that you don't know that require so much to think about
You don't know how deep in the mud I was with my attempted implementation
It wasbad bad. I'll share some of it tomorrow if you care
Well things like, do I use messagepack instead of json, do I use some custom format I implement myself, do I use anonymous pipes instead of named ones, do I use tcp, mem mapped files, mutexes, etc
i would abstract the transport layer and data (de)serialization away, so that u can first use simple stuff, like tcp and json and then make it configurable for faster alternatives like messagepack and named pipes/shared memory
and the actual application layer protocol for rpc u can build on top of that
i understand but for example how can you still be undecided about ipc vs tcp? given that as long your wrappers implements Stream they should be interchangeable, they are for two different use cases anyway, local vs remote communcation
also the approach to serialization and designing (for the higher level protocol) would change if we are talking about modeling 2 structures or 10000; maybe it could change even between 1000 and 10000; it's kind of an important information to have
well "ipc vs tpc" doesn't even make sense in the first place, no? you can do ipc via tcp. or via named pipes. or via websockets. or via memory mapped files. or...
and i mean, that's what this thread is for, to have people ask clarifying questions like this :p there won't be too many structures to model. i figure 5-20 per server implementation (of which there will be ~3-5)
the number of different high level data types doesnt matter, their serialization boils down to the primitives you want to support.
at the transport layer you are just sending bytes over, maybe with some sequence and fragment number to support something like UDP where packets could arrive out of order, or simply dont arrive.
the "hard" part here is only to discern which bytes belong to which message
once thats done and dusted it goes to how to do the serialization, no matter if thats reflection based or source generated or manually writing it, which itself isnt that problematic either, and can also be abstracted away.
ones all that is fixed u just need the application layer protocol, where the hard part is designing it.
basically here the different types come into play, for requests, for responses.
u want some generic status codes + optional data to process messages, pretty much like HTTP status codes.
the number of different high level data types doesnt matteri think it could matter, depending on the amount of effort required and tooling available i think it makes sense as a question because if you write a non generic ipc in your service then to transform it to work between different machines instead of locally you have to refactor it or reengineer it, so it would be better if you knew that beforehand didn't you say some time ago that you had a lot of these messages to send/receive? or was it another issue
i'm not sure i remember that
i still don't have this figured out
i keep trying to rewrite it, but i just end up in the same situations
i don't know how to do this
What are you having trouble with?
genuinely i don't even know how to start anymore at this point. i've been on and off trying to find a good implementation for this for months
months!
my brain is completely fried when it comes to this project
i do not know what to do
i don't even know what i'm envisioning anymore
i don't even know where to begin explaining it
like i want something like this or something
i don't even know anymore
i don't know what to do
even if i were to drop this coupling, i have no idea how to send the data
i need naot json serialization
so what do i just send
?
and then the other side has to read
?
i can't use the latter on both sides, because jsonserializer would try to serialize the
JsonElement
struct and its internals
i'm completely losti imagined this thread would wake up again
why naot?
but you don't need to use JsonElement on both side, you use the correct models/dto for the job
JsonElement would be a step for the deserializer to get to the correct model
I need to pass information about what "endpoint" I'm calling (just a string, or an enum) as well as optionally the request model. That means I need a type that cannot be generic (what generic arg would I pass if I'm not passing a model?), but still contains both the endpoint identifier and some data property (null if not specified). The other side then needs to read that data property as a JsonElement and check whether it's null or something
Because the server side is a library loaded into another process for communication. Of course the library needs to be native to be loaded
Doesn't STJ have support for $type?
Yes it does, with
JsonDerivedType
I assume that works with naot
As I said before, you'll have a much easier time if you think in terms of sending messages around rather than method calls: doing RPC adds quite a lot of overheadi don't know, it looks to me that you're worrying/focusing too much about the technicalities and the 'rules' (like json is text, you can send almost whatever you want) and not on what these actions/messages are used for; by that i mean structure follows function, if you need nested types you nest types, if you need 200 flat fields you make a flat class with 200 nullable primitive fields; if generics don't work, don't use it, or maybe you can have two discriminators instead of just one (or honestly i think there are better ways, usually); the more practical you can be the more we can help i guess
@this_is_pain @canton7 i don't think either of you understand what the problem is?
i need to send request types like this:
the other side needs to read this structure, but it of course needs to do so in one go.
reading
would drop the data that may or may not be there.
reading
would over-read if the first struct above was sent, leading to an exception.
I don't think you read my response...
please go ahead and explain how
$type
would help hereThat's literally the problem that $type is there to solve
please, have at it
don't think i need to mention the obvious, but requests do not derive from one another
there is absolutely no relation between
and
Let's say you have two message types,
SayHelloRequest
and SayGoodbyeRequest
. You could define your RequestMessage
message as:
Then:
Gets serialized as:
don't think i need to mention the obvious, but requests do not derive from one anotherMaybe that's why you're causing yourself so much trouble 🤷♂️ You're necessarily doing polymorphic messages. STJ requires that your different message types inherit from a common base. That's how it works. If you're insisting on doing something which STJ supports, but without following its requirements, yes you're going to have a hard time. I'm not sure what to say in response to that.
and explain how you would receive this on the other side
Just deserialize as
RequestMessage
, then type switch over Payload
and explain how you would handle request messages that don't have a payload
Then they have an empty payload. Every message needs a type.
wdym type switch? how do i access
$type
?STJ creates the correct type in the
Payload
member. If you serialized a SayHelloRequestPayload
, that gets deserialized as a SayHelloRequestPayload
So you can do switch (message.Payload) { case SayHelloRequestPayload sayHello: ... }
, or use the visitor pattern, or a Dictionary<Type, ...>
, or whateversorry if i'm a bit agitated, i've just been trying things for way too long and i'm mostly upset with myself for not trying this before
so request messages without data still need some way to be distinguished
think of it like
Func<TRet>
or Action
no input, but (optionally) some output☝️
well yeah, but i still need some identifier
The identifier is the type
if there's no payload, there's no type
Which is why you need a payload, even an empty one
I feel like we're going in circles here
an empty payload sounds like a dumb idea no offense
As I said before, don't think in terms of calling methods or RPC or similar: think in terms of messages
I mean, I'm literally telling you how this is done. If you want to think you know better and invent your own system, go ahead, but please don't ask us for help
(and bearing in mind how much trouble you're having inventing your own system, taking a lead from how a lot of other people solve this might not be the worst idea)
(You can use a singleton instance of the payload if you want, although STJ will still try to deserialize it as a new instance)
i guess... it just seems so different from what i would usually do
alright well, i think i might be able to do something with that
now for the handling and responses on the server side
how would you "register" the handlers?
The simplest is to literally have a big switch statement which switches over all possible request message types, and calls an appropriate function in each case. If you want to make it a bit more decoupled, you could have a
Dictionary<Type, Action<RequestPayloadBase>>
where you register delegates which handle each message typei think i would like to have some base server (just handles receiving basic requests and sending basic responses), a... more concrete abstract class(?) that registers handlers and defines simpler abstract methods for each "endpoint", and then the actual server implementation:
god damn it yeah i've been doing that
Or yeah, you can start using reflection to discover handler methods, etc, but that becomes a bit more magic. I like explicitness.
god damn it yes i've realized
man i love the magic but
i want tight coupling
like a proxy interface would be kinda sick but like how do you even make that good
Tbh, I'd probably use the visitor pattern. It's a bit of boilerplate, but a lot of stuff just falls out
do you have a link or an example
i haven't heard of it
Then in the receiver:
Implementing the
Visit
method on all of the payload types is a little painful, but tbh VS generates most of it for you, and you get compiler errors unless/until you define everything, which is really nice -- there's no way to accidentally forget to support a particular message typei am truly shocked at this design and that i'm not smart enough to have come up with that
tunnel vision
i mean that's crazy
Visitor's one of those patterns that you probably wouldn't come up with yourself, but it's really useful when you have a known set of types, and you want to find out what you've got, and process it, in a strongly-typed way
It's less relevant now that C# has
switch
over types, but it's still useful because it guarantees that you've handled all cases
(you can make a visitor base class with virtual methods if you want implementors to be able to pick and choose what they handle: ExpressionVisitor
does that for instance)yeah fuck that's crazy smart
so simple
very effective, thanks
So, empty payload types do make sense 🙂
alright well, i need some response type next
i want to communicate different types of failures
like something is not found, something was null
Yeah. Start with messages again, then see how you can work them into your visitor stuff
Tbh I like just having a separate call to send a response message, rather than forcing it through a method's return type, but whatever, both work
i would need to see both i think
Maybe?
The rest is left as an exercise to the reader
no god mutable type
The other option is:
Serialization models can be mutable: that's fine. But make it immutable if you want, there's no difference
You could have the error be a separate field on
ResponseMessage
, or you could make an error response be one of the possible ResponseMessagePayload
subclasses. Both work.
You probably want to have some sort of correlation token to tie together a request and response. The sender puts some integer value as the correlation token in the request, and the receiver mirrors that same value back in the response. That lets the sender tie together the response with the request that they sent
(Which is important with errors, as otherwise you don't necessarily know what request message an error is in response to)
Alternatively you could put the error on ResponsePayloadBase
No right or wrong answers there
(Then on the sender side you have a Dictionary<int, TaskCompletionSource>
which maps together a correlation token with a TCS. That TCS is what the sender is awaiting, and you complete it when you get a response back with the matching correlation token)
Does that all make sense?I'm taking a little break, I'll catch up on it later, thanks :)
One thing though, I was looking to design it in such a way that the payload base, the serialization code and the pipe stream writing code is contained in one project, and the actual implementations (visitor, client, server, payloads) are in separate projects (one project per "kind of server").
How would you design this generically? I haven't yet figured out a good way
That's fine? You can split this up into:
* RequestMessage / ResponseMessage (these can be generic over the base payload type, or you can use inheritance)
* The networking code to transmit RequestMessage / ResponseMessage, retries, etc
* A particular payload and all of its subclasses
* The types which implement
IRequestMessageVisitor
etci can't figure it out
Proj1
Proj2
(mind the primary ctors)
but passing TVisitor
to the base ctor of Server<TVisitor>
seems wrong when the implementing server is itself the visitor
especially since
is not even valid...
(for obvious reasons)One way:
Then in proj2:
Or you could do:
Then in proj2:
Or you could do something in the middle:
Then in proj2:
i'm not following these examples. they all have a server which only handles... one message?
i need the server to handle an arbitrary amount of requests
Yes, you're reading them wrong (or I'm not being clear enough)
i mean i'm probably reading them wrong
Where do you think we're only handling one message type?
In all cases, proj1 defines the RequestMessage but not the payload type, and has logic to handle the RequestMessage itself, but not the individual payload types. Then proj2 introduces the different payload types and how to handle those
oh yeah you define
RequestPayloadBase
in proj 2, that's exactly not what i want
RequestPayloadBase
should be in proj 1
it's the base after allRe-read it
Wait
Why do you want RequestPayloadBase in each project? I thought you wanted the individual payload types to be defined in proj2?
i don't, that's what you wrote
>Then in proj2
>
public class RequestPayloadBase
and the actual implementations (visitor, client, server, payloads) are in separate projects (one project per "kind of server").
payloads
i mean just look at the code you wrote idk?
what else should i say lol
I'm saying that you wanted the payloads to be defined in proj2. You said so.
If you don't want that, then I'm confused as to what you want
yes, but not the payload base
it's the base, all other projects need it
Ok, stop. You're blindly wanting the impossible again
Remember, the payload base is something that STJ needs. It's part of the mechanism for doing polymorphism in STJ
You need to add attributes to the payload base to tell STJ what all of the different payload types are
If you put the payload base in proj1, then all of the different payload types would also, obviously, have to be in proj1. Because otherwise how could you name them in the attributes on the payload base
So you can put the request message in proj1, but if the individual payloads need to be in proj2, then the payload base also has to be in proj2
(Note that the payload base is probably empty. It's literally just a marker class, and a place to stick attributes)
but then where is
Payload.Visit
coming fromWhat do you mean?
Ah yeah, it does implement the visitor pattern, that's right. I forgot that bit
alright
But that needs to be in proj2 as well, because the visitor interface is specific to the different payload types, and they're in proj2 too
You could put this in proj1:
Then in proj2:
But I really don't see the point
oh
JsonDerivedTypeAttribute
doesn't exist
that's
really bad.NET 7 apparently? Or NS2.0 with a nuget package
so i'm on uhh
2.0 :/
NS2.0? Or .net core 2.0?
ns

i'm on a pretty old version of the package (don't remember why)
probably compatibility with other packages (
System.Memory
, System.Buffers
)You can probably write a custom serializer which does the same thing?
i'm not all that picky, but it seems a tall order to do what stj does
that's for converters?
That's what I meant -- used the wrong word
ah, i suppose
Proj1
Proj2
i have this for now, will deal with any package incompatibilities later
the only thing that bothers me here is that the visitor implementation is publicYou can impl it explicitly, or on an inner class
I don't think you want
in TVisitor
? No need to be contravariantwith variance i almost always add it when it's possible
the way you are explaining this makes it a problem of the physical layer, not of the de/serializer
🤔
like sending length+data or bom+data+eom instead of raw bytes
hm, annoying issue: https://github.com/dotnet/runtime/issues/81840
seems like i need to length-prefix all of my sent data and read as an array of bytes instead
Yeah, that's expected when using any streaming transport. Tcp and serial are the same
The word you're looking for is "framing". Normally you'd have some sort of "start of message" marker so that the receiver can re-synchronise if it becomes desynced, and a crc (although that's probably not necessary for named pipes)
i just do it like this
i'm decently far along now, need to work on the responses next, really not sure how i wanna do that
there's also the problem of having to handle a close command as well as internal server errors, which should be kinda shared between all server impls
You can put extra fields on your base RequestMessage/ResponseMessage
and then?
And then use them to encode errors / close messages / etc
oh i thought you meant for the length prefixing stuff still
For the length prefixing - looks fine. As long as you don't need to resync if the receiver misses some bytes, or has to drop some bytes. You can probably get away with a uint16 if you care, and stackalloc will be a bit cheaper, but meh, it's fine
can't stackalloc, since streams on ns2.0 don't take spans
Ah fair enough
One way to model server errors / internal messages would be to do the same polymorphism as you do for payload but on the whole message. I'm on mobile so typing code is hard, but you have a base ResponseMessage class, and polymorphic subclasses for 1) a user payload (as you do now), 2) an error, 3) a close message, etc
Or you can just stick extra fields on your current ResponseMessage and have an invariant that only one can be populated at a time
hm, i still need some help figuring out lots of parts on the client side as well as the responses, and also how i should implement the server polling for new data
for the client, i'm envisioning something like this
i've changed some of the names, this is what i actually have in my code.
so i can then just call
client.GetMonoImage("Assembly-CSharp")
i'm not sure if the server should be in charge of polling for new requests itself, or if that should be handled outside of the server;
basically should the loop be in the server here or should i handle this polling in my app code?
then there's the obvious question about how to design the response message.
is something like this just enough?
and perhaps for later; i'd like to add some logging via some ILogger
interface. i wanna implement console logging, file logging, and debug logging (Debug.WriteLine
)(just as an indication, how many messages do you expect that will travel on this channel? like 100/s or 1/s or 1/h)
it really depends on the user. some might only need 3 or 4
MonoField
s from 1 MonoClass
in 1 MonoImage
others may request 100 fields from a total of 15 classes in 2 images
the important bit is that the user is expected to request all of the data they need once and only oncebut aren't these commands periodical?
ah, that answers my question
so at (their) app startup, request all of the things they need (can be hundreds of requests sent as quickly as possible), and then the server might lay dormant. they can continue requesting more data, but it's not the expected use case
i might expand the library to support generic memory reading from the target process, but i think a memory mapped file may be more useful there
but so then why do speak about polling?
you mean in a general sense, not periodical polling
well the server still needs to read each new request (of those possible hundreds at app startup) in a row
so it needs to wait for a new request to be sent
in a loop
that's my definition of polling for new messages
(can be hundreds of requests sent as quickly as possible)wouldn't you consider batching requests then? (i mean sure ok first the whole system needs to work, but still)
Possibly, yeah
The idea was kinda
Beyond that is a bit convoluted and idk if explaining it makes sense.
Users will want to read the value of different class' fields. For that, they need to build a path of offsets (from the start of the class instance to the individual fields) that needs to be entirely dereferenced to actually read the value.
Say the game contains something like this
The user first needs the static data chunk address of
GameManager
, add the offset of GameManager._instance
to it, dereference, add the offset of GameManager._player
to the result, dereference that, and finally add the offset of Player._hp
to read the value at the result.
So something like this;
So once they have all the addresses and offsets, they "build" their path and want to "watch" the value at the end of that path. I can do this via ReadProcessMemory calls, but each offset (each dereference) is one RPM call
That's where I thought mem mapped files would work well (just continuously make the server update the value)so imagine player dies and user reloads save, to re-bind hp field the program would have to re-calculate at least the last offset, i guess
No, the offsets always stay the same
Neither, IMO. You want an additional layer which reading the incoming stream, decoding responses, matching them to pending requests, and completing the relevant TCSs (see my messages about correlation tokens from yesterday)
(that layer sits below your IpcClient/IpcServer of course)
What's lower than an abstract base class?
Uh...
We architect things in layers.
abstract
is irrelevant to thatthe answer was: another abstract class
Higher layers call down into lower layers. Higher layers deal with more abstract stuff, lower layers deal with more nitty-gritty detail
Hah! I wouldn't here, though. For clarity.
Composition over inheritance etc
idk how you'd do it then
Have a class which wraps the named pipe. It takes a message in (and sends it over the pipe), and it had a constantly running task which reads messages from the pipe
That's what the abstract ipcserver class does
Exactly that, wraps the pipe
On the client, you wrap that in a class which takes requests, sends them, gets responses, matches the responses up to pending requests
Then the IpcClient wraps that
Don't just bung everything in one class. That's basic architecture. Identify what the different responsibilities are, and build your abstractions
I really don't follow at all
What's confusing you?
i'm just not really that sure what you're suggesting in the first place i guess. it doesn't make sense to me to add an additional wrapper around just the pipe that does the "polling" and the de/serialization. my base server is already that wrapper. the actual child servers then use the wrapper in the form of a base class. adding another class here just seems like overcomplicating it.
i'd rather address the other stuff i brought up
Which bit are you still struggling with? I thought I addressed your latest round
this basically summed it up
but if you have anything to say to this, I'm open to hear your reasoning in a bit more detail, maybe with an example so I have something concrete in front of me.
sorry if it just ends up in you repeating yourself, I'm just trying to understand why you think it's a better idea to further decouple the pipe and the server like that
That's just basic separation of concerns. They can get quite large: in my current project, the bit thay handles sequencing, aborting, and timing out messages is 350 lines; the thing that handles framing / deftaming messages is 160 lines; the thing that handles reading and writing byes to tcp is 270 lines; and the thing that handles deciding what to connect to, reconnections, disconnection, connection checks, etc, is 300 lines
(and that's just a client: the server is an embedded device, so that's in C)
My answer to thay was just "yes, both sides need to have a loop which just reads the pipe for new received bytes". Both the server and the client
Were there any other questions in there? "lots of parts of the client side" is kinda hard to answer
You probably want to support having multiple messages in flight at once, so the server can take a while to respond to a message if it wants. For that you need one component which reads bytes from the pipe and assembles them back into messages, then a component which takes those messages, matches them up to pending request, and notifies the requester that it's got a response
Well I wanted to know how I could go about the client implementation with the way I was envisioning it in this message
https://discord.com/channels/143867839282020352/1339224833359347822/1346481941335113749
ero
for the client, i'm envisioning something like this
i've changed some of the names, this is what i actually have in my code.
Quoted by
<@542772576905199626> from #Named Pipe IPC: Protocols & Status Codes (click here)
React with ❌ to remove this embed.
Or if you think it's perhaps a bad idea to make the client take raw arguments instead of entire RequestMessages
And whether taking entire RequestMessages would allow for other patterns once again
That seems fine.
You probably want somewhere which links together a request message type and a response message type. Thay might be in the call to
SendRequest
itself, or you could add the type of the response to the type of the request (as a generic type param), or have some other type which links them together. Either way, that means your SendRequest
method can return the actual type of the response, and the user doesn't need to worry
I'd just send the payload, not the whole request message
The bits of the request message other than the payload are for use by your library, not for the user to use
So, this should be a request message payload type with a response message payload typeThat sounds reasonable, what I would like to know is what that could enable me to do in terms of patternizing (?? idk what word I'm looking for here) the exchange of the request and response data
Coupling them together and such
Eg in my current project I do
GetFooResponsePayload response = await TransmitAsync(Command.GetFoo, new GetFooRequestPayload(...))
. Where Command.GetFoo
is a static new Command<GetFooRequestPayload, GetFooResponsePayload>()
(actually it contains some other data as well, but that's not relevant)
In terms of coupling the types, see above, or you could define your GetFooRequestPayload as a RequestPayloadBase<GetFooResponseMessage>
and tie them together in the type system that way
In terms of pairing a response message back to the request which triggered it, search this thread for "correlation token"
Or you could just call SendAsync<FooRequestPayload, FooReaponsePayload>(new FooRequestPayload(...))
and tie them together at the call site
(I've done all of those at one point or another)i can't figure it out idk?
this can surely not be it
can't even use it in the
IpcServer
base class then anymore because i have no base payload
my brain is fried
i don't get it
@canton7 I guess
yeah i can't figure it out
i really tried
I beg you @canton7 get me out of this hell hole