A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions. Comments welcome.
I'll start assembling this in exp/netchan in the next few days.
-rob
// Package netchan makes Go channels available for communication over // a network. package netchan
// Addr represents a network address for a single networked channel. type Addr struct { // Net is the network protocol: "tcp" Net string // Addr is the network address: "machine:1234" Addr string // Name identifies the individual channel at this network address: "name" Name string // "theChannelName"
}
// Listener provides the functionality to publish and manage networked // channels. type Listener struct {
}
// Listen establishes a Listener on this machine with the specified // network and address. func Listen(net, addr string) (*Listener, error)
// Publish makes the channel available for communication to other // machines. Although the type of the argument is interface{}, the // channel must be a bidirectional channel such as chan string. The // return value is the Addr that other machines can use to connect // to the channel and send data to it. After Publish, other machines // on the network can send to the channel and this machine can receive // those messages. Also, after Publish only the receive end of the // channel should be used by the caller. func (l *Listener) Publish(name string, channel interface{}) (Addr, error)
// Unpublish removes network address for the channel with the specified // address. After Unpublish returns, no further messages can be // received on the channel (until it is published again). func (l *Listener) Unpublish(Addr) error
// Dial connects the channel to the networked channel with the specified // address. Although the type of the argument is interface{}, the // channel must be a bidirectional channel, such as chan string, and // the type of the element must be compatible with the type of the // channel being connected to. (Details of compatibility TBD.) After // Dial, this machine can send data on the channel to be received by // the published network channel on the remote machine. Also, after // Dial only the send end of the channel should be used by the caller. func Dial(addr Addr, channel interface{}) error
// NewName returns a name that is guaranteed with extremely high // probability to be globally unique. func NewName() string
/*
// Simple example; for brevity errors are listed but not examined.
type msg1 struct { Addr Addr Who string
}
type msg2 struct { Greeting string Who string
}
func server() { // Announce a greeting service. l, err := netchan.Listen(":12345") c1 := make(chan msg1) addr, err := l.Publish(c1, "GreetingService") for { // Receive a message from someone who wants to be greeted. m1 := <-c1 // Create a new channel and connect it to the client. c2 := make(chan msg2) err = netchan.Dial(m1.Addr, c2) // Send the greeting. c2 <- msg2{"Hello", m1.Who} // Close the channel. close(c2) }
}
func client() { // Announce a service so we can be contacted by the greeting service. l, err := netchan.Listen(":23456") // The name of the greeting service must be known separately. greet := netchan.Addr{"tcp", "server.com:12345", "GreetingService"} c1 := make(chan msg1) c2 := make(chan msg2) // Connect to the greeting service and ask for a message. err = netchan.Dial(greet, c1) // Publish a place to receive the greeting before we ask for it. addr, err := netchan.Publish(c2, netchan.NewName()) c1 <- msg1{addr, "Eduardo"} // Receive the message and tear down. reply := <-c2 fmt.Println(reply.Greeting, ", ", reply.Who) // "Hello, Eduardo" // Tell the library we're done with this channel. netchan.Unpublish(c2) // No more data will be sent on c2. close(c2)
> // Publish makes the channel available for communication to other > // machines. Although the type of the argument is interface{}, the > // channel must be a bidirectional channel such as chan string.
I think you need to spell out the channel restrictions a bit more. The examples imply that the channel may not be, e.g., "chan chan int". Is that right? I assume "chan func()" would also be problematic. What about "chan *int"? What happens if I use "chan map[int]int" and then send the same map twice?
>> // Publish makes the channel available for communication to other >> // machines. Although the type of the argument is interface{}, the >> // channel must be a bidirectional channel such as chan string.
> I think you need to spell out the channel restrictions a bit more. The > examples imply that the channel may not be, e.g., "chan chan int". Is > that right? I assume "chan func()" would also be problematic. What > about "chan *int"? What happens if I use "chan map[int]int" and then > send the same map twice?
It'll be gob rules, since that's what the transport will be. I hope to make chan chan int work at some point, but no promises. One reason I did this was that when I tried to implement that in the old netchan I got lost in a hall of mirrors. This simplification might make it possible to send a channel (in some restricted form, no doubt) across the network. But again, no promises.
So in short: the channel element type must be gob-encodable.
On Fri, Dec 2, 2011 at 5:02 PM, Rob 'Commander' Pike <r...@google.com> wrote:
> A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions. Comments welcome.
> I'll start assembling this in exp/netchan in the next few days.
I'm not not much of an expert, but 2 remarks
* should netchan.Addr use net.Addr in some way?
* what happens when the sender closes the netchannel? Can we use the same sender-closes semantics in with netchannels?
-- Han-Wen Nienhuys Google Engineering Belo Horizonte han...@google.com
> A proposal follows. The old netchan was too intricate. After talking with > Russ and Ken, I've dramatically simplified it, made it unidirectional, and > reduced it to a handful of simple functions. Comments welcome.
> I'll start assembling this in exp/netchan in the next few days.
> -rob
> // Package netchan makes Go channels available for communication over > // a network. > package netchan
> // Addr represents a network address for a single networked channel. > type Addr struct { > // Net is the network protocol: "tcp" > Net string > // Addr is the network address: "machine:1234" > Addr string > // Name identifies the individual channel at this network address: > "name" > Name string // "theChannelName" > }
> // Listener provides the functionality to publish and manage networked > // channels. > type Listener struct { > }
> // Listen establishes a Listener on this machine with the specified > // network and address. > func Listen(net, addr string) (*Listener, error)
> // Publish makes the channel available for communication to other > // machines. Although the type of the argument is interface{}, the > // channel must be a bidirectional channel such as chan string. The > // return value is the Addr that other machines can use to connect > // to the channel and send data to it. After Publish, other machines > // on the network can send to the channel and this machine can receive > // those messages. Also, after Publish only the receive end of the > // channel should be used by the caller. > func (l *Listener) Publish(name string, channel interface{}) (Addr, error)
> // Unpublish removes network address for the channel with the specified > // address. After Unpublish returns, no further messages can be > // received on the channel (until it is published again). > func (l *Listener) Unpublish(Addr) error
> // Dial connects the channel to the networked channel with the specified > // address. Although the type of the argument is interface{}, the > // channel must be a bidirectional channel, such as chan string, and > // the type of the element must be compatible with the type of the > // channel being connected to. (Details of compatibility TBD.) After > // Dial, this machine can send data on the channel to be received by > // the published network channel on the remote machine. Also, after > // Dial only the send end of the channel should be used by the caller. > func Dial(addr Addr, channel interface{}) error
> // NewName returns a name that is guaranteed with extremely high > // probability to be globally unique. > func NewName() string
> /*
> // Simple example; for brevity errors are listed but not examined.
> type msg1 struct { > Addr Addr > Who string > } > type msg2 struct { > Greeting string > Who string > }
> func server() { > // Announce a greeting service. > l, err := netchan.Listen(":12345") > c1 := make(chan msg1) > addr, err := l.Publish(c1, "GreetingService") > for { > // Receive a message from someone who wants to be greeted. > m1 := <-c1 > // Create a new channel and connect it to the client. > c2 := make(chan msg2) > err = netchan.Dial(m1.Addr, c2) > // Send the greeting. > c2 <- msg2{"Hello", m1.Who} > // Close the channel. > close(c2) > } > }
> func client() { > // Announce a service so we can be contacted by the greeting > service. > l, err := netchan.Listen(":23456") > // The name of the greeting service must be known separately. > greet := netchan.Addr{"tcp", "server.com:12345", "GreetingService"} > c1 := make(chan msg1) > c2 := make(chan msg2) > // Connect to the greeting service and ask for a message. > err = netchan.Dial(greet, c1) > // Publish a place to receive the greeting before we ask for it. > addr, err := netchan.Publish(c2, netchan.NewName()) > c1 <- msg1{addr, "Eduardo"} > // Receive the message and tear down. > reply := <-c2 > fmt.Println(reply.Greeting, ", ", reply.Who) // "Hello, Eduardo" > // Tell the library we're done with this channel. > netchan.Unpublish(c2) > // No more data will be sent on c2. > close(c2) > }
On Dec 2, 2011, at 11:29 AM, Han-Wen Nienhuys wrote:
> On Fri, Dec 2, 2011 at 5:02 PM, Rob 'Commander' Pike <r...@google.com> wrote: >> A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions. Comments welcome.
>> I'll start assembling this in exp/netchan in the next few days.
> I'm not not much of an expert, but 2 remarks
> * should netchan.Addr use net.Addr in some way?
I don't see why. It would be better not to require true networking, as opposed to mere connectivity, although the current baby-step API doesn't have full generality yet. Also the address must be serializable.
> * what happens when the sender closes the netchannel? Can we use the > same sender-closes semantics in with netchannels?
What is a netchannel? If you close the channel, the receiver will behave as it would if the channel were local.
On Dec 2, 2011, at 1:35 PM, Brad Fitzpatrick wrote:
> I can't tell from this whether you plan to hard-code net.Dial & net.Listen, or can this work with any net.Conn & net.Listener I supply?
> I'd want this to work over TCP-like connections (e.g. tunneled over ssh, websocket, or the various TCP-in-TCP things)
There should certainly be ways to construct Listeners given existing I/O such as pipes, not just networks, but just getting basics up and running is enough for now. I'm not even sure the name Listener is right.
It's odd that client() closes c2, given that it (apparently) only receives on it, and the convention that only senders should close. What are the consequences of having Unpublish close the channel? In other words, what are the use cases for Publishing a channel after Unpublishing it?
On Dec 2, 2011, at 3:08 PM, Jonathan Amsterdam wrote:
> It's odd that client() closes c2, given that it (apparently) only > receives on it, and the convention that only senders should close. > What are the consequences of having Unpublish close the channel? In > other words, what are the use cases for Publishing a channel after > Unpublishing it?
Unpublish could perhaps do the closing, since it's the end inside the mux that needs to be closed. That precludes using the channel again, though.
On Dec 2, 6:12 pm, Rob 'Commander' Pike <r...@google.com> wrote:
> On Dec 2, 2011, at 3:08 PM, Jonathan Amsterdam wrote:
> > It's odd that client() closes c2, given that it (apparently) only > > receives on it, and the convention that only senders should close. > > What are the consequences of having Unpublish close the channel? In > > other words, what are the use cases for Publishing a channel after > > Unpublishing it?
> Unpublish could perhaps do the closing, since it's the end inside the mux that needs to be closed. That precludes using the channel again, though.
If Unpublish does the closing, you can defer Unpublish immediately after calling Publish.
Corrections to example code:
Calls to Listen need to specify the Net (protocol): func Listen(net, addr string) (*Listener, error) l, err := netchan.Listen(":12345") -> l, err := netchan.Listen("tcp", ":12345")
Is the Listen-Publish distinction necessary? If not, these could be collapsed into one Publish(addr, channel) call, and you can eliminate the Listener type altogether. Could multiple Publish calls to the same addr.Net, addr.Addr (with different addr.Names) be collapsed within the library automatically?
One last thought. This line: greet := netchan.Addr{"tcp", "server.com:12345", "GreetingService"} made me think of a URL like: greet := "tcp://server.com:12345/GreetingService" I wonder whether using URLs to identify endpoints may make sense longer term. Not sure, so probably best to leave as-is.
On Dec 2, 9:02 pm, Rob 'Commander' Pike <r...@google.com> wrote:
> // Listen establishes a Listener on this machine with the specified > // network and address. > func Listen(net, addr string) (*Listener, error)
It would be useful to have another function be able to pass in an existing net.Listener, like net/http's Serve.
This comes up with systemd on Linux, where one handles a listen socket passed in by systemd with FileListener. Other people might also have a reason for custom Listeners.
> // Dial connects the channel to the networked channel with the specified > // address. Although the type of the argument is interface{}, the > // channel must be a bidirectional channel, such as chan string, and > // the type of the element must be compatible with the type of the > // channel being connected to. (Details of compatibility TBD.) After
right now, gob encoder sends data type spec before sending data. however the type info is hidden from API. could gob expose type info and allow user code to compare data types?
correct me if I'm wrong, but this new setup makes bi-directional comms more complicated as it requires a manual handshake and second connection?
What if the "client" end is behind a NAT or other such state sensitive router/firewall. The ability to ride on established tcp connections is important.
All in all this seems to improve the simplicity of the netchan library, but it doesn't seem to improve the simplicity of using it. I had _hoped_ for single connection bi-directional netchans, you can assemble something like this with the old/netchan using message routing handlers now. But with this new version a many to one or many to many connection may be unmanageably complex and racy.
Honestly, and I hate being critical you guys do great work, this kind of reduction in functionality seems a step backwards.
I will sketch out how I am using old/netchan versus how I would have to use exp/netchan if anyone is interested.
(1) This model appears to centralize request dispatch on the server side, assuming multiple clients exist. Consequently, should we wish to avoid re-dialing for each send, the server has to manually maintain the set of (client name, server-side send channel) pairs.
(2) Are there particular problems with following the conventional socket model: listening socket is receive only, accepted and client-side sockets are two-way? Curious. My apologies if this has been answered somewhere already. Thanks.
The problem is to find a way to provide Go channel semantics on top of network hardware and software that, as always, finds a way to defeat all attempts at clean design.
> correct me if I'm wrong, but this new setup makes bi-directional comms more complicated as it requires a manual handshake and second connection?
> What if the "client" end is behind a NAT or other such state sensitive router/firewall. The ability to ride on established tcp connections is important.
> All in all this seems to improve the simplicity of the netchan library, but it doesn't seem to improve the simplicity of using it. I had _hoped_ for single connection bi-directional netchans, you can assemble something like this with the old/netchan using message routing handlers now. But with this new version a many to one or many to many connection may be unmanageably complex and racy.
> Honestly, and I hate being critical you guys do great work, this kind of reduction in functionality seems a step backwards.
> I will sketch out how I am using old/netchan versus how I would have to use exp/netchan if anyone is interested.
> -Simon Watt
Don't conflate the API with the implementation. Networking by the package will certainly be handled with much more efficiency than one might assume by assuming every Dial is a new connection. Once a connection is established, it would be used transparently. Far from me to claim that NATs aren't a problem, but I believe the proposal can be implemented in a way that NATs won't break the implementation or render it impractical.
On Sat, Dec 3, 2011 at 9:50 AM, si guy <sjw...@gmail.com> wrote: > Hey, looks great but I have a concern.
> correct me if I'm wrong, but this new setup makes bi-directional comms > more complicated as it requires a manual handshake and second connection?
> What if the "client" end is behind a NAT or other such state sensitive > router/firewall. The ability to ride on established tcp connections is > important.
> All in all this seems to improve the simplicity of the netchan library, > but it doesn't seem to improve the simplicity of using it. I had _hoped_ > for single connection bi-directional netchans, you can assemble something > like this with the old/netchan using message routing handlers now. But with > this new version a many to one or many to many connection may be > unmanageably complex and racy.
> Honestly, and I hate being critical you guys do great work, this kind of > reduction in functionality seems a step backwards.
> I will sketch out how I am using old/netchan versus how I would have to > use exp/netchan if anyone is interested.
Perhaps a client must be able to specify it wants to listen on the same addr it dialed from (isn't it 99% of use cases?). Then the package will be able to reuse the same underlying TCP connection in both directions. Moreover it removes the problem of choosing unique ports on client side, for example, currently if a client wants to communicate with N servers, it needs to choose N unique unused ports manually.
On Sat, Dec 3, 2011 at 12:04 PM, Dmitry Vyukov <dvyu...@google.com> wrote: > On Sat, Dec 3, 2011 at 9:50 AM, si guy <sjw...@gmail.com> wrote:
>> Hey, looks great but I have a concern.
>> correct me if I'm wrong, but this new setup makes bi-directional comms >> more complicated as it requires a manual handshake and second connection?
>> What if the "client" end is behind a NAT or other such state sensitive >> router/firewall. The ability to ride on established tcp connections is >> important.
>> All in all this seems to improve the simplicity of the netchan library, >> but it doesn't seem to improve the simplicity of using it. I had _hoped_ >> for single connection bi-directional netchans, you can assemble something >> like this with the old/netchan using message routing handlers now. But with >> this new version a many to one or many to many connection may be >> unmanageably complex and racy.
>> Honestly, and I hate being critical you guys do great work, this kind of >> reduction in functionality seems a step backwards.
>> I will sketch out how I am using old/netchan versus how I would have to >> use exp/netchan if anyone is interested.
> Perhaps a client must be able to specify it wants to listen on the same > addr it dialed from (isn't it 99% of use cases?). Then the package will be > able to reuse the same underlying TCP connection in both directions. > Moreover it removes the problem of choosing unique ports on client side, > for example, currently if a client wants to communicate with N servers, it > needs to choose N unique unused ports manually.
Something along the lines (not necessary the exact API, just the idea):
This part in the proposal looks somewhat problematic: l, err := netchan.Listen(*":23456"*) How do I choose the addr on a client? Is it a fixed well-known port that everybody can connect to? I do not want such ports on clients, I merely want the server can dial back.
Ah, I hadn't considered the re-use of a connection on a Dial(), I guess I was alarmed by the removal of a published sending channel.
If that's to be the case then all of my issues seem to evaporate with one exception:
Is there still a need for a Drain/Sync in the package? I know that channel sends are not supposed to fail, but maybe that rule can be cleanly broken in this (special) case? Or are the the new channels synchronous. I am thinking along the lines of speed over safety, and a program that can handle lost data and rollback appropriately (which is easier in go than most languages).
> On Dec 2, 2011, at 11:29 AM, Han-Wen Nienhuys wrote:
>> On Fri, Dec 2, 2011 at 5:02 PM, Rob 'Commander' Pike <r...@google.com> wrote: >>> A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions. Comments welcome.
>>> I'll start assembling this in exp/netchan in the next few days.
>> I'm not not much of an expert, but 2 remarks
>> * should netchan.Addr use net.Addr in some way?
> I don't see why. It would be better not to require true networking, as opposed to mere connectivity, although the current baby-step API doesn't have full generality yet. Also the address must be serializable.
>> * what happens when the sender closes the netchannel? Can we use the >> same sender-closes semantics in with netchannels?
> What is a netchannel? If you close the channel, the receiver will behave as it would if the channel were local.
Channel close behavior was not clear for me too. In the presence of network communication I expect that it can be more complicated and subtle. So I think we need to explicitly statement the behavior. What if remote side closes the chan? What if it disconnects/terminates/network is down?
Similar concern with sync chans? Is it still the case that send unblocks only when remote side starts processing the message? If chan capacity is 2, does that mean that at most 2 messages are in flight?
Of course. However, I suggest that such an optimization as 'keepalive', should it be implemented, default to off. The semantics and the performance characteristics will both be easier to understand that way.
> Don't conflate the API with the implementation. Networking by the package > will certainly be handled with much more efficiency than one might assume > by assuming every Dial is a new connection. Once a connection is > established, it would be used transparently. Far from me to claim that NATs > aren't a problem, but I believe the proposal can be implemented in a way > that NATs won't break the implementation or render it impractical.
I feel sure you have already thought about this, but I don't see discussion of it anywhere. Will there be a transformation of some kind when sending an Addr over the channel? It seems like there will have to be...
Am I understanding correctly that this address would be something like {"tcp", ":12345", "demo"}? Because sending that directly to a server, intending for the server to use it to respond, would be problematic. It seems like the client would have to explicitly know that, when sending its own address, to flag it for special treatment, and the server would have to either edit it so the Addr is the same as that of the incoming connection, or some special netchan.Addr that explicitly chooses the net.Conn over which a subsequent Dial should be attempted. This then gets even more complicated if you try to proxy the netchan, as the address has to somehow be transformed again when being sent along to the real server.
On Sat, Dec 3, 2011 at 1:39 PM, Kyle Lemons <kev...@google.com> wrote: > Don't conflate the API with the implementation. Networking by the package >> will certainly be handled with much more efficiency than one might assume >> by assuming every Dial is a new connection. Once a connection is >> established, it would be used transparently. Far from me to claim that NATs >> aren't a problem, but I believe the proposal can be implemented in a way >> that NATs won't break the implementation or render it impractical.
> I feel sure you have already thought about this, but I don't see > discussion of it anywhere. Will there be a transformation of some kind > when sending an Addr over the channel? It seems like there will have to > be...
> Am I understanding correctly that this address would be something like > {"tcp", ":12345", "demo"}? Because sending that directly to a server, > intending for the server to use it to respond, would be problematic. It > seems like the client would have to explicitly know that, when sending its > own address, to flag it for special treatment, and the server would have to > either edit it so the Addr is the same as that of the incoming connection, > or some special netchan.Addr that explicitly chooses the net.Conn over > which a subsequent Dial should be attempted. This then gets even more > complicated if you try to proxy the netchan, as the address has to somehow > be transformed again when being sent along to the real server.
Perhaps I misunderstood what you mean by editing or what is edited, but editing of a client addr can't take place, because a client can listen on an addr and then pass it to a server *and* accept external independent connections on it at the same time (well, at least the client has to honestly listen on the original addr). It is ridiculous, but AFAIS the API does not preclude that. I would like to say "listen on the same addr from which you dial to the server", nobody except the server knowns the addr - that's OK, I want to accept connections only from it anyway.
Many of the other comments seem to be asking for channel interfaces to different underlying transports. I wonder whether instead of netchan, which provides Listen and Dial methods like package net, we should just provide a package to hook up channels to any io.Reader or io.Writer, using gob as the underlying transport (perhaps named package "iochan" or "chanio" or "gobchan"). This allows users to set up their net.Conns or local pipes and add any encryption, compression, etc. on top of them, then provide a channel interface to their programs. A package like netchan could be built atop iochan pretty easily.
S
On Dec 3, 2:15 am, Rob 'Commander' Pike <r...@google.com> wrote:
> The problem is to find a way to provide Go channel semantics on top of network hardware and software that, as always, finds a way to defeat all attempts at clean design.