| // Copyright 2009 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| /* | |
| The netchan package implements type-safe networked channels: | |
| it allows the two ends of a channel to appear on different | |
| computers connected by a network. It does this by transporting | |
| data sent to a channel on one machine so it can be recovered | |
| by a receive of a channel of the same type on the other. | |
| An exporter publishes a set of channels by name. An importer | |
| connects to the exporting machine and imports the channels | |
| by name. After importing the channels, the two machines can | |
| use the channels in the usual way. | |
| Networked channels are not synchronized; they always behave | |
| as if there is a buffer of at least one element between the | |
| two machines. | |
| TODO: at the moment, the exporting machine must send and | |
| the importing machine must receive. This restriction will | |
| be lifted soon. | |
| */ | |
| package netchan | |
| import ( | |
| "log" | |
| "net" | |
| "os" | |
| "reflect" | |
| "sync" | |
| ) | |
| // Export | |
| // A channel and its associated information: a direction | |
| type exportChan struct { | |
| ch *reflect.ChanValue | |
| dir Dir | |
| } | |
| // An Exporter allows a set of channels to be published on a single | |
| // network port. A single machine may have multiple Exporters | |
| // but they must use different ports. | |
| type Exporter struct { | |
| listener net.Listener | |
| chanLock sync.Mutex // protects access to channel map | |
| chans map[string]*exportChan | |
| } | |
| type expClient struct { | |
| *encDec | |
| exp *Exporter | |
| } | |
| func newClient(exp *Exporter, conn net.Conn) *expClient { | |
| client := new(expClient) | |
| client.exp = exp | |
| client.encDec = newEncDec(conn) | |
| return client | |
| } | |
| // TODO: ASSUMES EXPORT MEANS SEND | |
| // Sent once per channel from importer to exporter to report that it's listening to a channel | |
| type request struct { | |
| name string | |
| dir Dir | |
| count int | |
| } | |
| // Reply to request, sent from exporter to importer on each send. | |
| type response struct { | |
| name string | |
| error string | |
| } | |
| // Wait for incoming connections, start a new runner for each | |
| func (exp *Exporter) listen() { | |
| for { | |
| conn, err := exp.listener.Accept() | |
| if err != nil { | |
| log.Stderr("exporter.listen:", err) | |
| break | |
| } | |
| log.Stderr("accepted call from", conn.RemoteAddr()) | |
| client := newClient(exp, conn) | |
| go client.run() | |
| } | |
| } | |
| // Send a single client all its data. For each request, this will launch | |
| // a serveRecv goroutine to deliver the data for that channel. | |
| func (client *expClient) run() { | |
| req := new(request) | |
| for { | |
| if err := client.decode(req); err != nil { | |
| log.Stderr("error decoding client request:", err) | |
| // TODO: tear down connection | |
| break | |
| } | |
| log.Stderrf("export request: %+v", req) | |
| if req.dir == Recv { | |
| go client.serveRecv(req) | |
| } else { | |
| log.Stderr("export request: can't handle channel direction", req.dir) | |
| resp := new(response) | |
| resp.name = req.name | |
| resp.error = "export request: can't handle channel direction" | |
| client.encode(resp, nil) | |
| break | |
| } | |
| } | |
| } | |
| // Send all the data on a single channel to a client asking for a Recv | |
| func (client *expClient) serveRecv(req *request) { | |
| exp := client.exp | |
| resp := new(response) | |
| resp.name = req.name | |
| var ok bool | |
| exp.chanLock.Lock() | |
| ech, ok := exp.chans[req.name] | |
| exp.chanLock.Unlock() | |
| if !ok { | |
| resp.error = "no such channel: " + req.name | |
| log.Stderr("export:", resp.error) | |
| client.encode(resp, nil) // ignore any encode error, hope client gets it | |
| return | |
| } | |
| for { | |
| if ech.dir != Send { | |
| log.Stderr("TODO: recv export unimplemented") | |
| break | |
| } | |
| val := ech.ch.Recv() | |
| if err := client.encode(resp, val.Interface()); err != nil { | |
| log.Stderr("error encoding client response:", err) | |
| break | |
| } | |
| if req.count > 0 { | |
| req.count-- | |
| if req.count == 0 { | |
| break | |
| } | |
| } | |
| } | |
| } | |
| // NewExporter creates a new Exporter to export channels | |
| // on the network and local address defined as in net.Listen. | |
| func NewExporter(network, localaddr string) (*Exporter, os.Error) { | |
| listener, err := net.Listen(network, localaddr) | |
| if err != nil { | |
| return nil, err | |
| } | |
| e := &Exporter{ | |
| listener: listener, | |
| chans: make(map[string]*exportChan), | |
| } | |
| go e.listen() | |
| return e, nil | |
| } | |
| // Addr returns the Exporter's local network address. | |
| func (exp *Exporter) Addr() net.Addr { return exp.listener.Addr() } | |
| func checkChan(chT interface{}, dir Dir) (*reflect.ChanValue, os.Error) { | |
| chanType, ok := reflect.Typeof(chT).(*reflect.ChanType) | |
| if !ok { | |
| return nil, os.ErrorString("not a channel") | |
| } | |
| if dir != Send && dir != Recv { | |
| return nil, os.ErrorString("unknown channel direction") | |
| } | |
| switch chanType.Dir() { | |
| case reflect.BothDir: | |
| case reflect.SendDir: | |
| if dir != Recv { | |
| return nil, os.ErrorString("to import/export with Send, must provide <-chan") | |
| } | |
| case reflect.RecvDir: | |
| if dir != Send { | |
| return nil, os.ErrorString("to import/export with Recv, must provide chan<-") | |
| } | |
| } | |
| return reflect.NewValue(chT).(*reflect.ChanValue), nil | |
| } | |
| // Export exports a channel of a given type and specified direction. The | |
| // channel to be exported is provided in the call and may be of arbitrary | |
| // channel type. | |
| // Despite the literal signature, the effective signature is | |
| // Export(name string, chT chan T, dir Dir) | |
| // where T must be a struct, pointer to struct, etc. | |
| func (exp *Exporter) Export(name string, chT interface{}, dir Dir) os.Error { | |
| ch, err := checkChan(chT, dir) | |
| if err != nil { | |
| return err | |
| } | |
| exp.chanLock.Lock() | |
| defer exp.chanLock.Unlock() | |
| _, present := exp.chans[name] | |
| if present { | |
| return os.ErrorString("channel name already being exported:" + name) | |
| } | |
| exp.chans[name] = &exportChan{ch, dir} | |
| return nil | |
| } |