├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── examples
└── pingpong
│ └── pingpong.go
├── netchan.go
├── netchan_test.go
├── select.go
└── utils.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.3
5 | - tip
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Ahmed W.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of the {organization} nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # netchan [](http://godoc.org/github.com/OneOfOne/netchan) [](https://travis-ci.org/OneOfOne/netchan)
2 | --
3 |
4 | netchan is a proof-of-concept channels over network, works exactly like you would expect.
5 |
6 | Create a new channel, connect to remote hosts and/or listen on one or more ports and use it like you would use a normal channel.
7 |
8 | ## Install
9 |
10 | go get github.com/OneOfOne/netchan
11 |
12 | ## Usage
13 |
14 | check `examples/*`
15 |
16 | ## Project Status
17 | This is alpha software, ymmv.
18 |
19 | All contributions and pull requests are also very welcome.
20 |
21 | ## License
22 |
23 | [Apache v2.0](http://opensource.org/licenses/Apache-2.0)
24 |
25 | Copyright 2014 [OneOfOne](https://github.com/OneOfOne/)
26 |
27 | Licensed under the Apache License, Version 2.0 (the "License");
28 | you may not use this file except in compliance with the License.
29 | You may obtain a copy of the License at
30 |
31 | http://www.apache.org/licenses/LICENSE-2.0
32 |
33 | Unless required by applicable law or agreed to in writing, software
34 | distributed under the License is distributed on an "AS IS" BASIS,
35 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
36 | See the License for the specific language governing permissions and
37 | limitations under the License.
38 |
--------------------------------------------------------------------------------
/examples/pingpong/pingpong.go:
--------------------------------------------------------------------------------
1 | // this is a simple ping/pong example
2 | // run one instance as a server with: go run pingpong.go -s [-addr 10101]
3 | // then run one (or more) clients with: go run pingpong [-addr 10101]
4 | package main
5 |
6 | import (
7 | "flag"
8 | "fmt"
9 | "log"
10 | "math/rand"
11 | "os"
12 | "runtime"
13 | "time"
14 |
15 | "github.com/OneOfOne/netchan"
16 | )
17 |
18 | var (
19 | servPort = flag.String("addr", "10101", "server port")
20 | serv = flag.Bool("s", false, "if true it will act as a server")
21 | ch = netchan.New(1)
22 | N = runtime.NumCPU()
23 | )
24 |
25 | func dieIf(err error) {
26 | if err != nil {
27 | fmt.Fprintln(os.Stderr, "error:", err)
28 | os.Exit(1)
29 | }
30 | }
31 | func servLoop(i int) {
32 | for {
33 | id, msg, st := ch.Recv(true)
34 | if st != netchan.StatusOK {
35 | dieIf(fmt.Errorf("invalid recv, dying (%s): [id=%x] %q %s", st, id, msg))
36 | }
37 | log.Printf("[i=%d] received %q from %x\n", i, msg, id)
38 | ch.Send(true, fmt.Sprintf("pong.%d", i))
39 | }
40 | }
41 |
42 | func clientLoop(i int) {
43 | for {
44 | time.Sleep(time.Duration(rand.Intn(1000)+1000) * time.Millisecond)
45 | ch.Send(true, fmt.Sprintf("ping.%d", i))
46 | _, msg, st := ch.Recv(true)
47 | log.Printf("[i=%d] received %q from remote\n", i, msg)
48 | if st != netchan.StatusOK {
49 | os.Exit(0)
50 | }
51 | }
52 | }
53 |
54 | func main() {
55 | runtime.GOMAXPROCS(N)
56 | flag.Parse()
57 | addr := "127.0.0.1:" + *servPort
58 | log.SetFlags(0)
59 | if *serv {
60 | dieIf(ch.ListenAndServe("tcp4", addr))
61 | for i := 0; i < N; i++ {
62 | go servLoop(i)
63 | }
64 | } else {
65 | dieIf(ch.Connect("tcp4", addr))
66 | for i := 0; i < N; i++ {
67 | go clientLoop(i)
68 | }
69 | }
70 | select {} //block forever
71 | }
72 |
--------------------------------------------------------------------------------
/netchan.go:
--------------------------------------------------------------------------------
1 | package netchan
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "crypto/rand"
7 | "encoding/gob"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "log"
12 | "net"
13 |
14 | oerr "github.com/OneOfOne/go-utils/errors"
15 | osync "github.com/OneOfOne/go-utils/sync"
16 | )
17 |
18 | var (
19 | // ErrInvalidResponse is returned when the client or server sent something
20 | // unexpected during the handshake.
21 | ErrInvalidResponse = errors.New("invalid response")
22 | )
23 |
24 | type pktType byte
25 |
26 | const (
27 | pktInvalid pktType = iota
28 | pktHandshake
29 | pktHandshakeConfirm
30 | pktData
31 | pktStatus
32 | )
33 |
34 | // ChannelStatus defines the return values from Send/Recv
35 | type ChannelStatus byte
36 |
37 | const (
38 | // StatusNotFound returned when a valid receiver (or sender) coudln't be found
39 | StatusNotFound ChannelStatus = iota
40 |
41 | // StatusNotReady means all channel buffers were full, try again later
42 | StatusNotReady
43 |
44 | // StatusClosed means the channel is closed and should no longer be used
45 | StatusClosed
46 |
47 | // StatusOK means the message was received (or sent) successfully
48 | StatusOK
49 | )
50 |
51 | func (i ChannelStatus) String() string {
52 | switch i {
53 | case StatusNotFound:
54 | return "NotFound"
55 | case StatusNotReady:
56 | return "NotReady"
57 | case StatusOK:
58 | return "OK"
59 | case StatusClosed:
60 | return "Closed"
61 | }
62 | return "Invalid"
63 | }
64 |
65 | type pkt struct {
66 | Type pktType
67 | Value interface{}
68 | srcID []byte
69 | }
70 |
71 | func init() {
72 | gob.Register(pkt{})
73 | }
74 |
75 | func genID() (id []byte) {
76 | id = make([]byte, 16)
77 | rand.Read(id)
78 | return
79 | }
80 |
81 | type connChan struct {
82 | id []byte
83 | net.Conn
84 | r, s chan *pkt
85 |
86 | *gob.Encoder
87 | *gob.Decoder
88 |
89 | ctrl chan *connChan
90 | }
91 |
92 | func newConnChan(conn net.Conn, r, s chan *pkt, ctrl chan *connChan) (ch *connChan) {
93 | ch = &connChan{
94 | Conn: conn,
95 |
96 | r: r,
97 | s: s,
98 |
99 | Encoder: gob.NewEncoder(conn),
100 | Decoder: gob.NewDecoder(bufio.NewReaderSize(conn, 16)), //using larger buffers make the connection block sometimes.
101 |
102 | ctrl: ctrl,
103 | }
104 | return
105 | }
106 |
107 | func (c *connChan) clientHandshake(id []byte) (err error) {
108 | c.id = id
109 | if err = c.Encode(&pkt{Type: pktHandshake, Value: id}); err != nil {
110 | return
111 | }
112 | p := &pkt{}
113 | if err = c.Decode(p); err != nil {
114 | return
115 | }
116 | if p.Type != pktHandshakeConfirm {
117 | return ErrInvalidResponse
118 | }
119 | go c.receiver()
120 | go c.sender()
121 | return
122 | }
123 |
124 | func (c *connChan) serverHandshake() (err error) {
125 | p := &pkt{}
126 | if err = c.Decode(p); err != nil {
127 | return
128 | }
129 | if p.Type != pktHandshake {
130 | return ErrInvalidResponse
131 | }
132 | c.id = p.Value.([]byte)
133 | p.Type = pktHandshakeConfirm
134 | if err = c.Encode(p); err != nil {
135 | return
136 | }
137 | go c.receiver()
138 | go c.sender()
139 | return
140 | }
141 |
142 | func (c *connChan) receiver() {
143 | L:
144 | for {
145 | p := &pkt{srcID: c.id}
146 | if err := c.Decode(&p); err != nil {
147 | break
148 | }
149 |
150 | switch p.Type {
151 | case pktData:
152 | c.r <- p
153 | case pktStatus:
154 | if st, ok := p.Value.(ChannelStatus); ok && st == StatusClosed {
155 | break L
156 | }
157 | default: //might remove this
158 | panic(fmt.Sprintf("%v", p))
159 | }
160 | }
161 | c.ctrl <- c
162 | }
163 |
164 | func (c *connChan) sender() {
165 | for {
166 | select {
167 | case p := <-c.s:
168 | if p == nil || p.Type == pktInvalid { // screw it all
169 | break
170 | }
171 | if err := c.Encode(p); err != nil {
172 | c.s <- p
173 | break
174 | }
175 | }
176 | }
177 | }
178 |
179 | func (c *connChan) Send(val interface{}) (st ChannelStatus) {
180 | p := &pkt{Type: pktData, Value: val}
181 | return c.send(p)
182 | }
183 |
184 | func (c *connChan) send(p *pkt) (st ChannelStatus) {
185 | st = StatusOK
186 | if err := c.Encode(p); err != nil {
187 | st = StatusClosed
188 | }
189 | return
190 | }
191 |
192 | func (c *connChan) Close() error {
193 | c.send(&pkt{Type: pktStatus, Value: StatusClosed})
194 | return c.Conn.Close()
195 | }
196 |
197 | type Channel struct {
198 | id []byte
199 | lk osync.SpinLock
200 |
201 | listeners []net.Listener
202 |
203 | // using a map as a slice because we're that cool,
204 | // also halfway guarenteed random selection
205 | nchans map[*connChan]struct{}
206 |
207 | r, s chan *pkt
208 |
209 | ctrl chan *connChan
210 | log *log.Logger
211 | }
212 |
213 | func (ct *Channel) addChan(ch *connChan) {
214 | ct.lk.Lock()
215 | ct.nchans[ch] = struct{}{}
216 | ct.lk.Unlock()
217 | }
218 |
219 | func (ct *Channel) sucideLine() {
220 | for ch := range ct.ctrl {
221 | ct.printf("channel [id=%x] disconnected from %v", ch.id, ch.RemoteAddr())
222 | ct.lk.Lock()
223 | delete(ct.nchans, ch)
224 | ct.lk.Unlock()
225 | if ct.IsClosed() {
226 | break
227 | }
228 | }
229 | close(ct.ctrl)
230 | }
231 |
232 | func (ct *Channel) printf(format string, val ...interface{}) {
233 | ct.lk.Lock()
234 | if ct.log != nil {
235 | ct.log.Printf(format, val...)
236 | }
237 | ct.lk.Unlock()
238 | }
239 |
240 | // IsClosed returns if the channel is closed or not
241 | func (ct *Channel) IsClosed() (st bool) {
242 | ct.lk.Lock()
243 | st = len(ct.nchans) == 0 && len(ct.listeners) == 0
244 | ct.lk.Unlock()
245 | return
246 | }
247 |
248 | // Recv returns who sent the message, the message and the status of the channel
249 | // optionally blocks until there's a message to receive.
250 | func (ct *Channel) Recv(block bool) (id []byte, val interface{}, st ChannelStatus) {
251 | if ct.IsClosed() {
252 | st = StatusClosed
253 | return
254 | }
255 | var (
256 | p *pkt
257 | ok bool
258 | )
259 | if block {
260 | p, ok = <-ct.r
261 | } else {
262 | select {
263 | case p, ok = <-ct.r:
264 | default:
265 | st = StatusNotReady
266 | }
267 | }
268 | if ok {
269 | st = StatusOK
270 | id = p.srcID
271 | val = p.Value
272 | } else if st == StatusNotFound {
273 | st = StatusClosed
274 | }
275 | return
276 | }
277 |
278 | // Send sends a message over the network, optionally blocks until it finds a receiver.
279 | func (ct *Channel) Send(block bool, val interface{}) (st ChannelStatus) {
280 | if ct.IsClosed() {
281 | return StatusClosed
282 | }
283 |
284 | p := &pkt{Type: pktData, Value: val}
285 | if block {
286 | select {
287 | case ct.s <- p:
288 | st = StatusOK
289 | }
290 | } else {
291 | select {
292 | case ct.s <- p:
293 | st = StatusOK
294 | default:
295 | st = StatusNotReady
296 | }
297 |
298 | }
299 | return
300 | }
301 |
302 | // SendTo like send but tries to send to a specific client,
303 | // returns StatusNotFound if the client doesn't exist anymore.
304 | // note that SendTo always blocks until the message is sent or it errors out
305 | func (ct *Channel) SendTo(id []byte, val interface{}) (st ChannelStatus) {
306 | if ct.IsClosed() {
307 | return StatusClosed
308 | }
309 | st = StatusNotFound
310 | var nch *connChan
311 |
312 | ct.lk.Lock()
313 | for ch := range ct.nchans {
314 | if bytes.Equal(ch.id, id) {
315 | nch = ch
316 | }
317 | }
318 |
319 | ct.lk.Unlock()
320 | if nch != nil {
321 | st = nch.Send(val)
322 | }
323 | return
324 | }
325 |
326 | func (ct *Channel) SendAll(block bool, val interface{}) (st []ChannelStatus) {
327 | ct.lk.Lock()
328 | chs := make([]*connChan, len(ct.nchans))
329 | st = make([]ChannelStatus, len(chs))
330 | i := 0
331 | for ch := range ct.nchans {
332 | chs[i] = ch
333 | }
334 | ct.lk.Unlock()
335 | for i, ch := range chs {
336 | st[i] = ch.Send(val)
337 | }
338 | return
339 | }
340 |
341 | // Connect connects to a remote channel
342 | // it can be called multiple times with different addresses
343 | // (or really the same one if you're that kind of a person)
344 | func (ct *Channel) Connect(network, addr string) error {
345 | conn, err := net.Dial(network, addr)
346 | if err != nil {
347 | return err
348 | }
349 | return ct.ConnectTo(conn)
350 | }
351 |
352 | // ConnectTo conencts to a specific net.Conn
353 | func (ct *Channel) ConnectTo(conn net.Conn) error {
354 | ch := newConnChan(conn, ct.r, ct.s, ct.ctrl)
355 | if err := ch.clientHandshake(ct.id); err != nil {
356 | ct.printf("error during handshake to %s: %v", conn.RemoteAddr(), err)
357 | return err
358 | }
359 | ct.addChan(ch)
360 |
361 | ct.printf("channel [id=%x] connected to %s", ch.id, conn.RemoteAddr())
362 | return nil
363 | }
364 |
365 | // ListenAndServe listens on the specific network and address waiting for remote channels
366 | // it can be called multiple times with different addresses
367 | func (ct *Channel) ListenAndServe(network, addr string) error {
368 | l, err := net.Listen(network, addr)
369 | if err != nil {
370 | return err
371 | }
372 | go ct.Serve(l)
373 | return nil
374 | }
375 |
376 | // Serve well, it serves a listener, ok golint?
377 | func (ct *Channel) Serve(l net.Listener) error {
378 | ct.lk.Lock()
379 | ct.listeners = append(ct.listeners, l)
380 | ct.lk.Unlock()
381 | ct.printf("serving on %s", l.Addr())
382 | for {
383 | conn, err := l.Accept()
384 | if err != nil {
385 | return err
386 | }
387 | ch := newConnChan(conn, ct.r, ct.s, ct.ctrl)
388 | if err := ch.serverHandshake(); err != nil {
389 | ct.printf("error during handshake from %s: %v", conn.RemoteAddr(), err)
390 | continue
391 | }
392 | ct.addChan(ch)
393 | ct.printf("channel [id=%x] connected from %s", ch.id, conn.RemoteAddr())
394 | }
395 | }
396 |
397 | // Close closes all the listeners and connected channels
398 | func (ct *Channel) Close() error {
399 | var errs oerr.MultiError
400 |
401 | // kill listeners first if any
402 | ct.lk.Lock()
403 | for _, l := range ct.listeners {
404 | errs.Append(l.Close())
405 | }
406 | ct.listeners = nil
407 | nchans := make([]*connChan, 0, len(ct.nchans))
408 |
409 | die := &pkt{}
410 | for ch := range ct.nchans {
411 | ct.s <- die
412 | nchans = append(nchans, ch)
413 | }
414 | ct.lk.Unlock()
415 | for _, ch := range nchans {
416 | if err := ch.Close(); err != nil {
417 | errs.Append(Error{Id: ch.id, Addr: ch.RemoteAddr(), Err: err})
418 | }
419 | }
420 | select { //because why the hell not
421 | case ct.s <- die:
422 | default:
423 | }
424 |
425 | close(ct.r)
426 | close(ct.s)
427 | //close(ct.ctrl)
428 | return &errs
429 | }
430 |
431 | // Stats returns information about the current connections
432 | func (ct *Channel) Stats() (activeConnections, activeListeners int) {
433 | ct.lk.Lock()
434 | activeConnections, activeListeners = len(ct.nchans), len(ct.listeners)
435 | ct.lk.Unlock()
436 | return
437 | }
438 |
439 | // Logger returns the *log.Logger instance the channel is using.
440 | func (ct *Channel) SetLogger(l *log.Logger) {
441 | ct.lk.Lock()
442 | ct.log = l
443 | ct.lk.Unlock()
444 | }
445 |
446 | // Sender defines a write-only channel
447 | type Sender interface {
448 | // Send sends a message over the network, optionally blocks until it finds a receiver.
449 | Send(block bool, val interface{}) (st ChannelStatus)
450 |
451 | // SendTo like send but tries to send to a specific client,
452 | // returns StatusNotFound if the client doesn't exist anymore.
453 | // note that SendTo always blocks until the message is sent or it errors out
454 | SendTo(id []byte, val interface{}) (st ChannelStatus)
455 | }
456 |
457 | // Receiver defines a read-only channel
458 | type Receiver interface {
459 | // Recv returns who sent the message, the message and the status of the channel
460 | // optionally blocks until there's a message to receive.
461 | Recv(block bool) (id []byte, val interface{}, st ChannelStatus)
462 | }
463 |
464 | // Interface is the common interface for all network channels
465 | type Interface interface {
466 | Sender
467 | Receiver
468 | io.Closer
469 |
470 | // ConnectTo conencts to a remote channel using the specific net.Conn
471 | ConnectTo(conn net.Conn) error
472 |
473 | // Serve well, serves a listener.
474 | // it blocks until the server is terminated.
475 | Serve(l net.Listener) error
476 |
477 | // IsClosed returns true if the channel is not usable anymore
478 | IsClosed() bool
479 | }
480 |
481 | func (ct *Channel) String() string {
482 | ac, al := ct.Stats()
483 | return fmt.Sprintf("Channel [id=%x, active=%d, listeners=%d]", ct.id, ac, al)
484 | }
485 |
486 | // ListenAndServe is an alias for New(initialCap).ListenAndServe(network, addr)
487 | func ListenAndServe(initialCap int, network, addr string) (ct *Channel, err error) {
488 | ct = New(initialCap)
489 | return ct, ct.ListenAndServe(network, addr)
490 | }
491 |
492 | // Connect is an alias for New(1).Connect(network, addr)
493 | func Connect(network, addr string) (ct *Channel, err error) {
494 | ct = New(1)
495 | return ct, ct.Connect(network, addr)
496 | }
497 |
498 | // New returns a new Channel with the initial cap size for the receiver and connection pool
499 | func New(initialCap int) *Channel {
500 | ct := &Channel{
501 | id: genID(),
502 |
503 | nchans: make(map[*connChan]struct{}, initialCap),
504 |
505 | r: make(chan *pkt, initialCap),
506 | s: make(chan *pkt, initialCap),
507 |
508 | ctrl: make(chan *connChan, 0),
509 |
510 | //log: log.New(os.Stdout, "netchan: ", log.LstdFlags),
511 | }
512 | go ct.sucideLine()
513 | return ct
514 | }
515 |
516 | // don't silently break the api
517 | var _ Interface = (*Channel)(nil)
518 |
--------------------------------------------------------------------------------
/netchan_test.go:
--------------------------------------------------------------------------------
1 | package netchan
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "net"
7 | "runtime"
8 | "strings"
9 | "sync"
10 | "testing"
11 | )
12 |
13 | var (
14 | listenPort = flag.String("addr", "0", "listen port for tests")
15 | sch = New(1)
16 | wg sync.WaitGroup
17 | )
18 |
19 | func init() {
20 | runtime.GOMAXPROCS(runtime.NumCPU())
21 | }
22 |
23 | // this is super ugly, t.Parallel didn't work for some reason
24 | func TestServerClient(t *testing.T) {
25 | l, err := net.Listen("tcp", "127.0.0.1:"+*listenPort)
26 | if err != nil {
27 | t.Error(err)
28 | t.FailNow()
29 | }
30 | if *listenPort == "0" {
31 | *listenPort = strings.Split(l.Addr().String(), ":")[1]
32 | }
33 | t.Log("listening on", l.Addr())
34 | go sch.Serve(l)
35 | wg.Add(20)
36 | go testServerChannel(t)
37 | go testClientChannel(t)
38 | wg.Wait()
39 | sch.Close()
40 | }
41 | func testServerChannel(t *testing.T) {
42 | for i := 0; i < 10; i++ {
43 | go func(i int) {
44 | id, msg, st := sch.Recv(true)
45 | if st != StatusOK {
46 | t.Fatalf("unexpected status : %x %q %s", id, msg, st)
47 | }
48 | t.Logf("sch recieved %q from %x", msg, id)
49 | sch.Send(true, fmt.Sprintf("sch:%d", i))
50 | wg.Done()
51 | }(i)
52 | }
53 | //wg.Wait()
54 | }
55 |
56 | func testClientChannel(t *testing.T) {
57 | ch, err := Connect("tcp4", "127.0.0.1:"+*listenPort)
58 | if err != nil {
59 | t.Fatal(err)
60 | }
61 | for i := 0; i < 10; i++ {
62 | go func(i int) {
63 | ch.Send(true, fmt.Sprintf("cch:%d", i))
64 | id, msg, st := ch.Recv(true)
65 | if st != StatusOK {
66 | t.Fatalf("unexpected status : %x %q %s", id, msg, st)
67 | }
68 | t.Logf("cch recieved %q", msg)
69 | wg.Done()
70 | }(i)
71 | }
72 | //wg.Wait()
73 | }
74 |
75 | func TestSelectRecv(t *testing.T) {
76 | nch, lch := New(1), make(chan interface{}, 0)
77 | go func() {
78 | lch <- 10
79 | }()
80 | if _, val := SelectRecv([]Receiver{nch, LocalReceiver(lch)}); val != 10 {
81 | t.Fatalf("expected %v, received %v", 10, val)
82 | }
83 | }
84 |
85 | func TestSelectSend(t *testing.T) {
86 | nch, lch := New(1), make(chan interface{}, 0)
87 | go func() {
88 | if SelectSend([]Sender{nch, LocalSender(lch)}, 10) == nil {
89 | t.Fatal("Select returned nil")
90 | }
91 |
92 | }()
93 | val := <-lch
94 | if val != 10 {
95 | t.Fatalf("expected %v, received %v", 10, val)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/select.go:
--------------------------------------------------------------------------------
1 | package netchan
2 |
3 | import "reflect"
4 |
5 | // SelectRecv loops on multiple receivers until it gets a value,
6 | // it blocks until a value is returned or all channels are closed.
7 | // this function doesn't panic.
8 | func SelectRecv(recvs []Receiver) (r Receiver, val interface{}) {
9 | defer func() {
10 | if recover() != nil {
11 | r = nil
12 | val = nil
13 | }
14 | }()
15 | cases := make([]reflect.SelectCase, len(recvs))
16 | for i, rch := range recvs {
17 | cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv}
18 | switch rch := rch.(type) {
19 | case *Channel:
20 | cases[i].Chan = reflect.ValueOf(rch.r)
21 | case LocalReceiver:
22 | cases[i].Chan = reflect.ValueOf((<-chan interface{})(rch))
23 | default:
24 | panic("SelectRecv only supports Channel and/or LocalReceiver")
25 | }
26 | }
27 | i, v, ok := reflect.Select(cases)
28 | if ok {
29 | r = recvs[i]
30 | val = v.Interface()
31 | if val, ok := val.(*pkt); ok {
32 | return r, val.Value
33 | }
34 | return
35 | }
36 | return
37 | }
38 |
39 | // SelectSend loops on multiple senders and returns the first channel that
40 | // was able to send.
41 | // it returns the target that sent the value or returns nil if all channels were closed
42 | // this function doesn't panic.
43 | func SelectSend(targets []Sender, val interface{}) (sch Sender) {
44 | defer func() {
45 | if recover() != nil {
46 | sch = nil
47 | }
48 | }()
49 | cases := make([]reflect.SelectCase, len(targets))
50 | p := reflect.ValueOf(&pkt{Type: pktData, Value: val})
51 | for i, sch := range targets {
52 | cases[i] = reflect.SelectCase{Dir: reflect.SelectSend}
53 | switch sch := sch.(type) {
54 | case *Channel:
55 | cases[i].Chan = reflect.ValueOf(sch.s)
56 | cases[i].Send = p
57 | case LocalSender:
58 | cases[i].Chan = reflect.ValueOf((chan<- interface{})(sch))
59 | cases[i].Send = reflect.ValueOf(val)
60 | default:
61 | panic("SelectSend only supports Channel and/or LocalSender")
62 | }
63 | }
64 | i, _, _ := reflect.Select(cases)
65 | return targets[i]
66 | }
67 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package netchan
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | )
7 |
8 | type Error struct {
9 | Id []byte
10 | Addr net.Addr
11 | Err error
12 | }
13 |
14 | func (err Error) Error() string {
15 | return fmt.Sprintln("Channel [id=%x, remote=%s] error = %v", err.Id, err.Addr, err.Err)
16 | }
17 |
18 | // LocalSender wraps a local channel as a Sender interface,
19 | // keep in mind that it will use defer to prevent panics.
20 | // SendTo will always return StatusNotFound
21 | type LocalSender chan<- interface{}
22 |
23 | func (ls LocalSender) Send(block bool, val interface{}) (st ChannelStatus) {
24 | defer func() {
25 | if recover() != nil {
26 | st = StatusClosed
27 | }
28 | }()
29 | if block {
30 | ls <- val
31 | return StatusOK
32 | }
33 | select {
34 | case ls <- val:
35 | return StatusOK
36 | default:
37 | return StatusNotReady
38 | }
39 | }
40 |
41 | func (ls LocalSender) SendTo(id []byte, val interface{}) (st ChannelStatus) {
42 | return StatusNotFound
43 | }
44 |
45 | // LocalReceiver wraps a local channel as a receiver interface,
46 | // the id will always be nil
47 | type LocalReceiver <-chan interface{}
48 |
49 | func (lr LocalReceiver) Recv(block bool) (id []byte, val interface{}, st ChannelStatus) {
50 | defer func() {
51 | if recover() != nil {
52 | st = StatusClosed
53 | }
54 | }()
55 | if block {
56 | val = <-lr
57 | st = StatusOK
58 | return
59 | }
60 | select {
61 | case val = <-lr:
62 | st = StatusOK
63 | default:
64 | st = StatusNotReady
65 | }
66 | return
67 | }
68 |
--------------------------------------------------------------------------------