Golang grpc.server: Understanding notions of server, and services - go

I am trying to understand the notions of Listener, Server and Services in the context of gRPC, Protobuf.
Let's use example on https://grpc.io/docs/languages/go/basics/ as a reference.
Here we have
Listener: lis
gRPC Server: grpcServer := grpc.NewServer()
Service: RouteGuide service
It appears we can also have more than one service registered to the same server.
That is, besides RouteGuide Service, we can have say SomeOther Service, SomeOtherOther Service.
And we can register all the three and expect the server to be able to serve methods belong to this three services (RouteGuide, SomeOther, SomeOtherOther).
Let's say RouteGuide, SomeOther, SomeOtherOther each have their own proto files specific to them. And all of the protos are in the same namespace (package value).
grpcServer := grpc.NewServer(opts...)
newRouteGuideService := pb.NewRouteGuideServer()
pb.RegisterRouteGuideServer(grpcServer, newRouteGuideService)
someOtherService := pb.NewSomeOtherServer()
pb.RegisterSomeOtherServer(grpcServer, someOtherService)
someOtherOtherService := pb.NewSomeOtherOtherServer()
pb.RegisterSomeOtherOtherService(grpcServer, someOtherOtherService)
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", 80))
grpcServer.Serve(lis)
It appears the term Server is, so to speak, overloaded here.
Not only grpcServer, but also RouteGuide, SomeOther, and SomeOtherOther are also referred to as Servers.
I am trying to figure out how to model or understand the notions.
Can we say, the server gRPCServer through listener lis listens on port 80 and can serve the three services RouteGuide, SomeOther, SomeOtherOther which are registered with it (gRPCServer)?
Is having one server service multiple services error prone?
What are the caveats to keep in mind when using one server with multiple services?

Remember that Golang is Compositional. So if you lay this out in a stripped down pseudo manner:
// Simplified Example
//The interfaces would be made by your proto definition
type Service1 interface {
// does service 1 stuff
ServiceHandler1()
ServiceHandler2()
}
type S1 struct{}
func(*S1) ServiceHandler1() {
//do svc1 stuff
}
type S2 struct{}
func(*S2) ServiceHandler2() {
//do svc2 stuff
}
type S3 struct{
// compose s1, s2
S1
S2
}
The server is the top level of the GRPC that has a listener, then you register the services that satisfy the interface required by your GRPC proto definition.
So in the case above, I could have ServiceHandler1 and ServiceHander2 defined in proto rpc. Registering S1 wouldn't satisfy it, registering S2 wouldn't, but registering S3 would.
Similarly you can break up the rpc proto into two services, one for method 1 and one for method 2 and treat them independently. In this case you could register, S1 for service 1, S2 for service 2, and S3 as either of them.
When a request comes in:
Request --> Listener --> GRPC Server --> Multiplexed to Handler --> Spawns goroutine to Handle Request
TLDR; doesn't matter where the things come from as long as they satisfy the Service Interface. Registrations can even be changed at runtime dynamically.

The listener listens for connections on a given port, and when a client connects, it creates a new goroutine to handle that particular session. The listener continues listening on the port for new incoming connections while the goroutine handles that particular client connection.
The session simply listens for the requests from the connected client and dispatches them to one of the registered services. The incoming request includes the name of the service, the function to call, and the arguments to that function. When the service handles the request, the response is sent back to the client.
So, as you say, the server listens on a port and can serve all the registered services through that port. This is not error-prone, and not that different from having one registered service defining all those functions.

Can we say, the server gRPCServer through listener lis listens on port
80 and can serve the three services RouteGuide, SomeOther,
SomeOtherOther which are registered with it (gRPCServer)?
Yes. I could not describe better.
Is having one server service multiple services error prone?
No, no problem at all. You will eventually need to have a server serving multiple services, specially if you need side services (e.g. health checker, metrics, caching, etc.). So your clients do not have to know all methods from the main service.
What are
the caveats to keep in mind when using one server with multiple
services?
(opinion) I have nothing in mind at this moment, but of course you need to wisely choose which services will be served together because you do not want to overload your server or mix logic.

Related

How to make two Telethon clients each use a different IP

My server exposes two IPs.
How to create two Telethon clients so that clientA use IP1 and clientB uses IP2?
Is it even possible without fiddling Telegram code?
And if not, what's the reason?
The TelegramClient constructor has a local_addr argument that allows you to specify which address the client should use to send requests from:
local_addr (str | tuple, optional):
Local host address (and port, optionally) used to bind the socket to locally. You only need to use this if you have multiple network cards and want to use a specific one.
https://docs.telethon.dev/en/latest/modules/client.html

Problem with gRPC setup. Getting an intermittent RPC unavailable error

I have a grpc server and client that works as expected most of the time, but do get a "transport is closing" error occasionally:
rpc error: code = Unavailable desc = transport is closing
I'm wondering if it's a problem with my setup. The client is pretty basic
connection, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
pb.NewAppClient(connection)
defer connection.Close()
and calls are made with a timeout like
ctx, cancel := context.WithTimeout(ctx, 300*time.Millisecond)
defer cancel()
client.MyGRPCMethod(ctx, params)
One other thing I'm doing is checking the connection to see if it's either open, idle or connecting, and reusing the connection if so. Otherwise, redialing.
Nothing special configuration is happening with the server
grpc.NewServer()
Are there any common mistakes setting up a grpc client/server that I might be making?
After much search, I have finally come to an acceptable and logical solution to this problem.
The root-cause is this: The underlying TCP connection is closed abruptly, but neither the gRPC Client nor Server are 'notified' of this event.
The challenge is at multiple levels:
Kernel's management of TCP sockets
Any intermediary load-balancers/reverse-proxies (by Cloud Providers or otherwise) and how they manage TCP sockets
Your application layer itself and it's networking requirements - whether it can reuse the same connection for future requests not
My solution turned out to be fairly simple:
server = grpc.NewServer(
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 5 * time.Minute, // <--- This fixes it!
}),
)
This ensures that the gRPC server closes the underlying TCP socket gracefully itself before any abrupt kills from the kernel or intermediary servers (AWS and Google Cloud Load Balancers both have larger timeouts than 5 minutes).
The added bonus you will find here is also that any places where you're using multiple connections, any leaks introduced by clients that forget to Close the connection will also not affect your server.
My $0.02: Don't blindly trust any organisation's (even Google's) ability to design and maintain API. This is a classic case of defaults-gone-wrong.
One other thing I'm doing is checking the connection to see if it's either open, idle or connecting, and reusing the connection if so. Otherwise, redialing.
grpc will manage your connections for you, reconnecting when needed, so you should never need to monitor it after creating it unless you have very specific needs.
"transport is closing" has many different reasons for happening; please see the relevant question in our FAQ and let us know if you still have questions: https://github.com/grpc/grpc-go#the-rpc-failed-with-error-code--unavailable-desc--transport-is-closing
I had about the same issue earlier this year . After about 15 minuets I had servers close the connection.
My solution which is working was to create my connection with grpc.Dial once on my main function then create the pb.NewAppClient(connection) on each request. Since the connection was already created latency wasn't an issue. After the request was done I closed the client.

Is there a way to make a websocket reachable through a SRV DNS record?

I swear I googled this. I was wondering if there is any way to connect to a WebSocket service by resolving a SRV DNS query. In principle, this sounds reasonable to me, e.g., in a situation where the port where the service is going to be listening depends on the host and there is not a fixed port.
For example:
Server A listens with a WebSocket on port 1234.
Server B listens with a WebSocket on port 1235.
Server NS assigns a CNAME to A, and a CNAME to B. It also adds a SRV entry that points to A's and B's CNAMEs, and also points to each port.
When connecting, an user should then connect to something like srvws://websockethost rather than ws://aorbcname:aorbport.
Is it even possible to do such a thing? Is there any planning at all about this? Is there any alternative to solve this kind of problem, where I need to communicate ports along with the DNS query?
Update: looking around I found this draft: https://datatracker.ietf.org/doc/html/draft-ibc-websocket-dns-srv-02
But I am not really sure how to interpret this. Is this a standard? Was this even approved? Is this just a proposal?
In RFC 2782 A DNS RR for specifying the location of services (DNS SRV) it states
Currently, one must either know the exact address of a server to
contact it, or broadcast a question.
The SRV RR allows administrators to use several servers for a single
domain, to move services from host to host with little fuss, and to
designate some hosts as primary servers for a service and others as
backups.
The format of a SRV RR is
_Service._Proto.Name TTL Class SRV Priority Weight Port Target
There is no technical reason that you couldn't use a SRV record to point to a WS. As you point out it has been the subject of an IETF draft. That doesn't appear to have gone any further, though the reasons aren't clear from its history it does appear to have been merged with RFC 6455 The WebSocket Protocol There is a discussion concerning the inclusion of IETF draft DNS SRV Resource Records for the WebSocket Protocol that has the following
SRV would be a perfect choice for many people
[...] It would be fully optional from the admin and user
perspective. The website owner could decide to use SRV or not. The
only requeriment, of course, os that WS clients support it
So while there is no technical specification, there is certainly no reason why you can't / shouldn't. The idea has been proposed and allowed to die because ultimately it is up to you if you want to use a SRV record to find a WS service that is perfectly within protocol.
And in my opinion, it would solve a number of problems.
Edited to add
After some more digging around on the IETF message boards. (curiosity as to why it wasn't implemented got the better of me) I found this message from the guy who proposed it
I was proposing it, but after long discussions in the maillist I've
understood that mandating DNS SRV in WS clients would break too much
assumptions in HTTP world (which commonly just sees above HTTP layer
and not below).
The existence of HTTP proxies is also a big handicap since those
proxies should be upgraded/modified in order to perform DNS SRV
resolution just in case the HTTP request is a WebSocket handshake.
This last argument is enough to not mandate SRV resolution.
So while it sounds a good idea, the people who really understand this stuff (and write the standards for it) found some issues that suggested simply using standard HTTP / A record look ups.

Network programming in Go

I'm studying Go for network programming. The problem is Go documentation is too simple. For example, I don't know when to use net.DialTCP, and when to use TCPListener object to AcceptTCP, what's the difference? How about client communicate with another client? Not client to server.
Connecting
In Go, you use the Dial function from net to connect to a remote machine.
net.Dial("tcp","google.com:80")
net.Dial("udp","tracker.thepiratebay.org:6969")
net.Dial("ip","kremvax.su")
net.Dial("unix","/dev/log")
This gives you an abstract Conn object that represents the connection you just established. Conn implements the ReadWriteCloser interface from io and a couple of other functions. You can use this object to send and receive data.
Listening
To listen, i.e. open a port, you use the Listen function from net. Calling Listen gives you a Listener object. Use Accept to accept incoming connections. Accept returns another Conn object that can be used as above.
ls, err := net.Listen("tcp",":1337")
if err != nil {
// port probably blocked, insert error handling here
}
conn, err := ls.Accept()
if err != nil {
// error handling
}
conn.Write("Hello, world!")
DialTCP and ListenTCP
These functions give you more control over TCP connections. I suggest you to only use them if they are definitly needed for your program as Dial and Listen are simpler, more generic and easily allow you to adapt your program to other types of network connections.
net.DialTCP is used on the client side to create a connection to remote server.
net.TCPListener.AcceptTCP is used on the server side to accept new connection (possibly initiated by net.DialTCP if client is written in Go). Note that listener may accept multiple connections, one by one, thus serving multiple clients at once (e.g. each in different goroutine).
Depending on whether you are writing client or server, you use net.DialTCP or net.TCPListener
Maybe you should learn about network programming in general first? Then these would make more sense I think.

Assign specific port to RPC server\client

I am writting a RPC client server application on windows. I have gone through RPC sample programs MS has given. But none of them mention port specifically. This probably because RPC uses dynamic port above 1024. But what if I wanted to assign specific port/port range to specific service (my server and client app for example). How can I do that? I can use RPCCFG to assign range but that range will be for all RPC programs (http://support.microsoft.com/kb/908472) right? How can I control a single program? I know it's possible because exchange seem to able to do it for Client Access Service?
Thanks in advance,
-Neel.
You can define ports in the code or use a config file which you read in the code.
status = RpcServerUseProtseqEp(
(char *)"ncacn_ip_tcp", // Use TCP/IP
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, // Backlog queue length for TCP/IP.
(char *)"4747", // TCP/IP port to use.
NULL); // No security.
Success. Jasper

Resources